1813 lines
60 KiB
Plaintext
1813 lines
60 KiB
Plaintext
; ********************************************************************
|
||
; Program: Git Companion
|
||
; Description: Help using git in your purebasic projects with a GUI
|
||
; Author: Yann Lebrun
|
||
; Date: August, 2025
|
||
; License: Free, unrestricted, credit
|
||
; appreciated but not required.
|
||
;
|
||
; Note: Please share improvement !
|
||
; ********************************************************************
|
||
|
||
#EnableDebug=#True
|
||
|
||
Global GitIgnoreHelpHTML.s = ~"<!DOCTYPE html>" + #LF$ +
|
||
~"<html lang=\"fr\">" + #LF$ +
|
||
~"<head>" + #LF$ +
|
||
~" <meta charset=\"UTF-8\">" + #LF$ +
|
||
~" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" + #LF$ +
|
||
~" <title>Aide .gitignore</title>" + #LF$ +
|
||
~" <style>" + #LF$ +
|
||
~" body {" + #LF$ +
|
||
~" font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;" + #LF$ +
|
||
~" margin: 0;" + #LF$ +
|
||
~" padding: 20px;" + #LF$ +
|
||
~" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);" + #LF$ +
|
||
~" color: #333;" + #LF$ +
|
||
~" line-height: 1.6;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" .container {" + #LF$ +
|
||
~" max-width: 900px;" + #LF$ +
|
||
~" margin: 0 auto;" + #LF$ +
|
||
~" background: white;" + #LF$ +
|
||
~" border-radius: 15px;" + #LF$ +
|
||
~" padding: 30px;" + #LF$ +
|
||
~" box-shadow: 0 20px 40px rgba(0,0,0,0.1);" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" h1 {" + #LF$ +
|
||
~" color: #2c3e50;" + #LF$ +
|
||
~" text-align: center;" + #LF$ +
|
||
~" margin-bottom: 30px;" + #LF$ +
|
||
~" font-size: 2.5em;" + #LF$ +
|
||
~" background: linear-gradient(45deg, #667eea, #764ba2);" + #LF$ +
|
||
~" -webkit-background-clip: text;" + #LF$ +
|
||
~" -webkit-text-fill-color: transparent;" + #LF$ +
|
||
~" background-clip: text;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" h2 {" + #LF$ +
|
||
~" color: #34495e;" + #LF$ +
|
||
~" border-left: 4px solid #667eea;" + #LF$ +
|
||
~" padding-left: 15px;" + #LF$ +
|
||
~" margin-top: 30px;" + #LF$ +
|
||
~" font-size: 1.4em;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" .syntax-box {" + #LF$ +
|
||
~" background: #f8f9fa;" + #LF$ +
|
||
~" border: 1px solid #e9ecef;" + #LF$ +
|
||
~" border-radius: 8px;" + #LF$ +
|
||
~" padding: 15px;" + #LF$ +
|
||
~" margin: 15px 0;" + #LF$ +
|
||
~" font-family: 'Consolas', 'Monaco', monospace;" + #LF$ +
|
||
~" font-size: 0.9em;" + #LF$ +
|
||
~" overflow-x: auto;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" .example {" + #LF$ +
|
||
~" background: linear-gradient(135deg, #667eea20, #764ba220);" + #LF$ +
|
||
~" border-left: 4px solid #667eea;" + #LF$ +
|
||
~" padding: 15px;" + #LF$ +
|
||
~" margin: 15px 0;" + #LF$ +
|
||
~" border-radius: 0 8px 8px 0;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" .tip {" + #LF$ +
|
||
~" background: #e8f5e8;" + #LF$ +
|
||
~" border: 1px solid #4caf50;" + #LF$ +
|
||
~" border-radius: 8px;" + #LF$ +
|
||
~" padding: 15px;" + #LF$ +
|
||
~" margin: 15px 0;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" .warning {" + #LF$ +
|
||
~" background: #fff3e0;" + #LF$ +
|
||
~" border: 1px solid #ff9800;" + #LF$ +
|
||
~" border-radius: 8px;" + #LF$ +
|
||
~" padding: 15px;" + #LF$ +
|
||
~" margin: 15px 0;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" code {" + #LF$ +
|
||
~" background: #f1f3f4;" + #LF$ +
|
||
~" padding: 2px 6px;" + #LF$ +
|
||
~" border-radius: 4px;" + #LF$ +
|
||
~" font-family: 'Consolas', 'Monaco', monospace;" + #LF$ +
|
||
~" color: #d73a49;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" ul {" + #LF$ +
|
||
~" padding-left: 20px;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" li {" + #LF$ +
|
||
~" margin: 8px 0;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" .pattern {" + #LF$ +
|
||
~" display: inline-block;" + #LF$ +
|
||
~" background: #667eea;" + #LF$ +
|
||
~" color: white;" + #LF$ +
|
||
~" padding: 3px 8px;" + #LF$ +
|
||
~" border-radius: 4px;" + #LF$ +
|
||
~" font-family: 'Consolas', 'Monaco', monospace;" + #LF$ +
|
||
~" font-size: 0.9em;" + #LF$ +
|
||
~" margin: 2px;" + #LF$ +
|
||
~" }" + #LF$ +
|
||
~" </style>" + #LF$ +
|
||
~"</head>" + #LF$ +
|
||
~"<body>" + #LF$ +
|
||
~" <div class=\"container\">" + #LF$ +
|
||
~" <h1>📁 Guide .gitignore</h1>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <p>Le fichier <code>.gitignore</code> permet de spécifier quels fichiers et dossiers Git doit ignorer lors du versioning.</p>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <h2>🎯 Syntaxe de base</h2>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <div class=\"example\">" + #LF$ +
|
||
~" <strong>Ignorer des fichiers spécifiques :</strong>" + #LF$ +
|
||
~" <div class=\"syntax-box\">" + #LF$ +
|
||
~"config.txt # Ignore le fichier config.txt<br>" + #LF$ +
|
||
~"*.log # Ignore tous les fichiers .log<br>" + #LF$ +
|
||
~"temp/ # Ignore le dossier temp et tout son contenu" + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <h2>🔧 Patterns et wildcards</h2>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <ul>" + #LF$ +
|
||
~" <li><span class=\"pattern\">*</span> : correspond à n'importe quelle chaîne (sauf <code>/</code>)</li>" + #LF$ +
|
||
~" <li><span class=\"pattern\">**</span> : correspond à n'importe quel nombre de répertoires</li>" + #LF$ +
|
||
~" <li><span class=\"pattern\">?</span> : correspond à un seul caractère</li>" + #LF$ +
|
||
~" <li><span class=\"pattern\">[abc]</span> : correspond à a, b ou c</li>" + #LF$ +
|
||
~" </ul>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <h2>🚫 Négation avec !</h2>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <div class=\"syntax-box\">" + #LF$ +
|
||
~"*.log<br>" + #LF$ +
|
||
~"!important.log # Inclut ce fichier malgré la règle précédente" + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <h2>📍 Chemins</h2>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <div class=\"example\">" + #LF$ +
|
||
~" <div class=\"syntax-box\">" + #LF$ +
|
||
~"/build # Ignore build à la racine uniquement<br>" + #LF$ +
|
||
~"build/ # Ignore tous les dossiers build<br>" + #LF$ +
|
||
~"docs/**/*.pdf # Ignore tous les PDF dans docs et ses sous-dossiers" + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <h2>💡 Exemples courants</h2>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <div class=\"syntax-box\">" + #LF$ +
|
||
~"# Fichiers de build<br>" + #LF$ +
|
||
~"dist/<br>" + #LF$ +
|
||
~"build/<br>" + #LF$ +
|
||
~"<br>" + #LF$ +
|
||
~"# Dépendances<br>" + #LF$ +
|
||
~"node_modules/<br>" + #LF$ +
|
||
~"vendor/<br>" + #LF$ +
|
||
~"<br>" + #LF$ +
|
||
~"# Configuration locale<br>" + #LF$ +
|
||
~".env<br>" + #LF$ +
|
||
~"config.local.json<br>" + #LF$ +
|
||
~"<br>" + #LF$ +
|
||
~"# Logs et caches<br>" + #LF$ +
|
||
~"*.log<br>" + #LF$ +
|
||
~".cache/<br>" + #LF$ +
|
||
~"<br>" + #LF$ +
|
||
~"# Fichiers d'IDE<br>" + #LF$ +
|
||
~".vscode/<br>" + #LF$ +
|
||
~".idea/<br>" + #LF$ +
|
||
~"<br>" + #LF$ +
|
||
~"# Fichiers système<br>" + #LF$ +
|
||
~".DS_Store<br>" + #LF$ +
|
||
~"Thumbs.db" + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <div class=\"tip\">" + #LF$ +
|
||
~" <strong>💡 Conseil :</strong> Utilisez des commentaires avec <code>#</code> pour documenter vos règles." + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <div class=\"warning\">" + #LF$ +
|
||
~" <strong>⚠️ Important :</strong> Le .gitignore ne s'applique qu'aux fichiers non-trackés. Pour ignorer des fichiers déjà versionnés, utilisez <code>git rm --cached <fichier></code>." + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <h2>🔍 Tester vos règles</h2>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" <div class=\"syntax-box\">" + #LF$ +
|
||
~"git check-ignore -v <fichier> # Vérifie si un fichier est ignoré<br>" + #LF$ +
|
||
~"git status --ignored # Affiche les fichiers ignorés" + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~" " + #LF$ +
|
||
~" </div>" + #LF$ +
|
||
~"</body>" + #LF$ +
|
||
~"</html>"
|
||
|
||
|
||
; =============================================================================
|
||
;-Enumeration Gadget
|
||
; =============================================================================
|
||
Enumeration
|
||
; --- existants (inchangés) ---
|
||
#gdtPnl
|
||
#WinMain
|
||
#GdtLblRepo
|
||
#GgtFieldRepo
|
||
#GdtBtnBrowseRepo
|
||
#GdtBtnInit
|
||
#GdtBtnRefresh
|
||
#GdtListStatus
|
||
|
||
; Dépôt (cadres)
|
||
#GdtFrmLocal
|
||
#GdtFrmRemote
|
||
#GdtFrmFiles
|
||
|
||
; Distants au-dessus de la liste
|
||
#GdtLblRemote
|
||
#GdtFieldRemote
|
||
#GdtLblBranch
|
||
#GdtSlctBranch
|
||
#GdtBtnNewBranch
|
||
#GdtBtnClone
|
||
#GdtBtnPull
|
||
#GdtBtnPush
|
||
|
||
; Liste + actions locales directement en dessous
|
||
#GdtBtnRestore
|
||
#GdtBtnRename
|
||
#GdtBtnDelete
|
||
#GdtBtnIgnore
|
||
|
||
; Commit (1 ligne large)
|
||
#GdtLblMessage
|
||
#GdtFieldMessage
|
||
#GdtBtnCommit
|
||
|
||
; Aide sous le panel
|
||
#GdtFrmHelp
|
||
#GdtHelp
|
||
;Onglet History
|
||
|
||
#GdtListHistory
|
||
#GdtTxtCommitInfo
|
||
#GdtBtnRestoreCommit
|
||
; Onglet .gitIgnore
|
||
#GdtTxtGitIgnore
|
||
#GdtBtnSaveGitIgnore
|
||
; Onglet Config
|
||
#GdtFrmConfig
|
||
#GdtLblUserName
|
||
#GdtFieldUserName
|
||
#GdtLblUserEmail
|
||
#GdtFieldUserEmail
|
||
#GdtLblScope
|
||
#GdtSlctScope
|
||
#GdtBtnSaveCfg
|
||
EndEnumeration
|
||
|
||
; =============================================================================
|
||
;-Module Translate
|
||
; =============================================================================
|
||
|
||
DeclareModule Translate
|
||
Declare.s T(key.s, DefaultLng.s = "")
|
||
Declare LoadLanguage(filename.s)
|
||
Declare SaveLanguage(filename.s)
|
||
EndDeclareModule
|
||
|
||
Module Translate
|
||
; ===============================================
|
||
; Système Multilingue Minimaliste pour PureBasic
|
||
; ===============================================
|
||
|
||
; Map global pour stocker les traductions
|
||
Global NewMap Translations.s()
|
||
|
||
; ===============================================
|
||
; Fonction principale de traduction
|
||
; ===============================================
|
||
Procedure.s T(key.s, DefaultLng.s = "")
|
||
; Retourner la traduction si elle existe
|
||
If FindMapElement(Translations(), key)
|
||
ProcedureReturn Translations(key)
|
||
Else
|
||
If DefaultLng <> ""
|
||
Translations(key) = DefaultLng
|
||
Else
|
||
Translations(key) = "!!!"+Key
|
||
EndIf
|
||
EndIf
|
||
|
||
ProcedureReturn Translations(key)
|
||
EndProcedure
|
||
|
||
; ===============================================
|
||
; Chargement d'un fichier de langue
|
||
; Format: clé=valeur (une par ligne)
|
||
; ===============================================
|
||
Procedure LoadLanguage(filename.s)
|
||
Protected file, line$, pos, key$, value$
|
||
|
||
file = ReadFile(#PB_Any, filename)
|
||
If file
|
||
While Eof(file) = 0
|
||
line$ = Trim(ReadString(file))
|
||
|
||
; Ignorer les lignes vides et les commentaires
|
||
If line$ <> "" And Left(line$, 1) <> ";" And Left(line$, 1) <> "#"
|
||
pos = FindString(line$, "=", 1)
|
||
If pos > 0
|
||
key$ = Trim(Left(line$, pos - 1))
|
||
value$ = Trim(Right(line$, Len(line$) - pos))
|
||
|
||
; Gérer les caractères d'échappement simples
|
||
value$ = ReplaceString(value$, "\n", Chr(10))
|
||
value$ = ReplaceString(value$, "\t", Chr(9))
|
||
|
||
Translations(key$) = value$
|
||
EndIf
|
||
EndIf
|
||
Wend
|
||
CloseFile(file)
|
||
ProcedureReturn #True
|
||
Else
|
||
ProcedureReturn #False
|
||
EndIf
|
||
EndProcedure
|
||
|
||
; ===============================================
|
||
; Sauvegarde des traductions actuelles
|
||
; ===============================================
|
||
Procedure SaveLanguage(filename.s)
|
||
Protected file, key$
|
||
|
||
file = CreateFile(#PB_Any, filename)
|
||
If file
|
||
WriteStringN(file, "; Fichier de traduction généré automatiquement")
|
||
WriteStringN(file, "; Format: clé=valeur")
|
||
WriteStringN(file, "")
|
||
|
||
; Parcourir toutes les traductions
|
||
ForEach Translations()
|
||
key$ = MapKey(Translations())
|
||
WriteStringN(file, key$ + "=" + Translations())
|
||
Next
|
||
|
||
CloseFile(file)
|
||
ProcedureReturn #True
|
||
Else
|
||
ProcedureReturn #False
|
||
EndIf
|
||
EndProcedure
|
||
EndModule
|
||
|
||
; =============================================================================
|
||
;-STRUCTURES / STRUCTURES
|
||
; =============================================================================
|
||
|
||
Structure RunProgramCall
|
||
exec.s ; program to execute / Programme à executer
|
||
args.s ; Command arguments / Arguments de la commande
|
||
workdir.s ; Working directory / Répertoire de travail
|
||
output.s ; Standard output / Sortie standard
|
||
errors.s ; Error output / Sortie d'erreur
|
||
exitcode.i ; Exit code / Code de sortie
|
||
EndStructure
|
||
|
||
Structure GitBranchInfo
|
||
oid.s ; SHA du commit actuel
|
||
head.s ; Nom de la branche ou "(detached)"
|
||
upstream.s ; Branche upstream
|
||
ahead.l ; Commits en avance
|
||
behind.l ; Commits en retard
|
||
EndStructure
|
||
|
||
Structure GitFileEntry
|
||
type.c ; Type: '1'=normal, '2'=rename/copy, 'u'=unmerged, '?'=untracked, '!'=ignored
|
||
statusIndex.c ; Statut index (X): '.', 'M', 'A', 'D', 'R', 'C', 'T', 'U'
|
||
statusWorktree.c; Statut worktree (Y): '.', 'M', 'A', 'D', 'R', 'C', 'T', 'U', '?', '!'
|
||
submodule.c ; Info submodule: 'N'=normal, 'S'=submodule, etc.
|
||
modeHead.s ; Mode fichier HEAD (ex: "100644")
|
||
modeIndex.s ; Mode fichier index
|
||
modeWorktree.s ; Mode fichier worktree
|
||
hashHead.s ; SHA HEAD
|
||
hashIndex.s ; SHA index
|
||
hashStage1.s ; SHA stage 1 (pour conflits)
|
||
hashStage2.s ; SHA stage 2 (pour conflits)
|
||
hashStage3.s ; SHA stage 3 (pour conflits)
|
||
similarity.s ; Score similarité pour R/C (ex: "R100")
|
||
path.s ; Chemin fichier
|
||
originalPath.s ; Chemin original (pour renommages)
|
||
EndStructure
|
||
|
||
Structure listFilesGit
|
||
name.s
|
||
status.s
|
||
statusDescription.s
|
||
indexStatus.s
|
||
workingTreeStatus.s
|
||
EndStructure
|
||
|
||
Structure GitStatus
|
||
branchInfo.GitBranchInfo
|
||
List files.GitFileEntry()
|
||
EndStructure
|
||
|
||
Structure GitCommitRow
|
||
fullHash.s
|
||
shortHash.s
|
||
date.s
|
||
authorName.s
|
||
authorEmail.s
|
||
refs.s
|
||
subject.s
|
||
filesSummary.s
|
||
EndStructure
|
||
|
||
Structure info
|
||
isGit.b
|
||
isInit.b
|
||
EndStructure
|
||
|
||
Structure main
|
||
info.info
|
||
GitCall.RunProgramCall
|
||
GitStatus.GitStatus
|
||
currentPath.s
|
||
CurrentBranch.s
|
||
List GitHistory.GitCommitRow()
|
||
List listFilesGit.listFilesGit()
|
||
EndStructure
|
||
|
||
;-Global
|
||
|
||
Global main.main
|
||
main\GitCall\exec="git"
|
||
;If FileSize(main\GitCall\exec)=-1
|
||
; MessageRequester("ERROR Git","NO FIND GIT")
|
||
; End
|
||
;EndIf
|
||
|
||
main\CurrentPath=GetCurrentDirectory()
|
||
main\CurrentBranch="main"
|
||
UseModule Translate
|
||
|
||
; =============================================================================
|
||
;-FUNCTION DECLARATIONS / DÉCLARATIONS DE FONCTIONS
|
||
; =============================================================================
|
||
Declare RefreshFileList(null.i)
|
||
|
||
Procedure.i RunExe(*call.RunProgramCall)
|
||
Protected prg.i, line$, lineError$, out$, err$
|
||
|
||
If FileSize(*call\workdir)<>-2 ; Correct bad git detect if bad workdir
|
||
*call\workdir=GetCurrentDirectory()
|
||
EndIf
|
||
Debug "[RunExe] "+*call\exec+" "+*call\args
|
||
prg = RunProgram(*call\exec, *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 '" + *call\exec+"'."
|
||
*call\exitcode = -1
|
||
ProcedureReturn *call\exitcode
|
||
EndIf
|
||
|
||
; Read output while program is running / Lire la sortie pendant l'exécution
|
||
While ProgramRunning(prg)
|
||
While AvailableProgramOutput(prg)
|
||
line$ = ReadProgramString(prg)
|
||
If line$ <> "" : out$ + line$ + #LF$ : EndIf
|
||
lineError$ = ReadProgramError(prg)
|
||
If lineError$ <> "" : err$ + lineError$ + #LF$ : EndIf
|
||
Wend
|
||
Delay(5)
|
||
Wend
|
||
|
||
; Read remaining output / Lire le reste de la sortie
|
||
While AvailableProgramOutput(prg)
|
||
line$ = ReadProgramString(prg)
|
||
If line$ <> "" : out$ + line$ + #LF$ : EndIf
|
||
Wend
|
||
|
||
; Read remaining errors / Lire le reste des erreurs
|
||
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 "[Exec] code=" + Str(*call\exitcode)
|
||
If *call\output <> "" : Debug "[std]" + *call\output : EndIf
|
||
If *call\errors <> "" : Debug "[stderr] " + *call\errors : EndIf
|
||
EndIf
|
||
|
||
ProcedureReturn *call\exitcode
|
||
EndProcedure
|
||
|
||
Procedure Max(nb1, nb2)
|
||
If nb1 > nb2
|
||
Result = nb1
|
||
Else
|
||
Result = nb2
|
||
EndIf
|
||
|
||
ProcedureReturn Result
|
||
EndProcedure
|
||
|
||
|
||
Procedure.i EnsureGitAvailable()
|
||
Debug "EnsureGitAvailable()"
|
||
main\GitCall\args = "--version"
|
||
Debug main\GitCall\output
|
||
If RunExe(@main\GitCall) = 0 And FindString(main\GitCall\output, "git version", 1)
|
||
main\info\isGit=#True
|
||
ProcedureReturn 1
|
||
EndIf
|
||
main\info\isGit=#False
|
||
MessageRequester("PBIDE-GitTool", T("NoGitDeteced"), #PB_MessageRequester_Warning)
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
; X (1er caractère) = État dans l'index (staging area)
|
||
; Y (2ème caractère) = État dans le working directory (répertoire de travail)
|
||
;
|
||
; Valeurs possibles pour chaque position
|
||
; Caractères de base :
|
||
;
|
||
; . = aucun changement
|
||
; M = modifié (Modified)
|
||
; A = ajouté (Added) - nouveau fichier
|
||
; D = supprimé (Deleted)
|
||
; R = renommé (Renamed)
|
||
; C = copié (Copied)
|
||
; T = type changé (Type changed) - ex: fichier → lien symbolique
|
||
; U = non mergé (Unmerged) - conflit de merge
|
||
; ? = non suivi (Untracked) - uniquement en position Y
|
||
; ! = ignoré (Ignored) - uniquement en position Y
|
||
;-status
|
||
Procedure.s ExtractField(line.s, position.l)
|
||
; Extrait un champ à une position donnée (séparé par espaces)
|
||
Protected count.l = CountString(line, " ")
|
||
If position <= count + 1
|
||
ProcedureReturn StringField(line, position, " ")
|
||
EndIf
|
||
ProcedureReturn ""
|
||
EndProcedure
|
||
|
||
Procedure ParseBranchHeader(line.s, *status.GitStatus)
|
||
; Parse les headers de branche (lignes commençant par #)
|
||
Protected parts.s, key.s, value.s
|
||
|
||
; Supprimer le "# " du début
|
||
line = Mid(line, 3)
|
||
|
||
; Séparer clé et valeur
|
||
Protected spacePos.l = FindString(line, " ")
|
||
If spacePos > 0
|
||
key = Left(line, spacePos - 1)
|
||
value = Mid(line, spacePos + 1)
|
||
|
||
Select key
|
||
Case "branch.oid"
|
||
*status\branchInfo\oid = value
|
||
|
||
Case "branch.head"
|
||
*status\branchInfo\head = value
|
||
|
||
Case "branch.upstream"
|
||
*status\branchInfo\upstream = value
|
||
|
||
Case "branch.ab"
|
||
; Format: "+X -Y"
|
||
Protected ahead$ = StringField(value, 1, " ")
|
||
Protected behind$ = StringField(value, 2, " ")
|
||
If Left(ahead$, 1) = "+"
|
||
*status\branchInfo\ahead = Val(Mid(ahead$, 2))
|
||
EndIf
|
||
If Left(behind$, 1) = "-"
|
||
*status\branchInfo\behind = Val(Mid(behind$, 2))
|
||
EndIf
|
||
EndSelect
|
||
EndIf
|
||
EndProcedure
|
||
|
||
Procedure ParseFileEntry(line.s, *status.GitStatus)
|
||
; Parse une entrée de fichier
|
||
Protected newEntry.GitFileEntry
|
||
Protected tabPos.l, parts.s
|
||
|
||
; Type d'entrée (premier caractère)
|
||
newEntry\type = Asc(Left(line, 1))
|
||
|
||
Select newEntry\type
|
||
Case '1' ; Fichier normal
|
||
; Format: 1 XY sub mH mI mW hH hI path
|
||
newEntry\statusIndex = Asc(Mid(line, 3, 1))
|
||
newEntry\statusWorktree = Asc(Mid(line, 4, 1))
|
||
newEntry\submodule = Asc(Mid(line, 6, 1))
|
||
newEntry\modeHead = ExtractField(line, 4)
|
||
newEntry\modeIndex = ExtractField(line, 5)
|
||
newEntry\modeWorktree = ExtractField(line, 6)
|
||
newEntry\hashHead = ExtractField(line, 7)
|
||
newEntry\hashIndex = ExtractField(line, 8)
|
||
newEntry\path = ExtractField(line, 9)
|
||
|
||
Case '2' ; Renommage/Copie
|
||
; Format: 2 XY sub mH mI mW hH hI X<score> path<tab>origPath
|
||
newEntry\statusIndex = Asc(Mid(line, 3, 1))
|
||
newEntry\statusWorktree = Asc(Mid(line, 4, 1))
|
||
newEntry\submodule = Asc(Mid(line, 6, 1))
|
||
newEntry\modeHead = ExtractField(line, 4)
|
||
newEntry\modeIndex = ExtractField(line, 5)
|
||
newEntry\modeWorktree = ExtractField(line, 6)
|
||
newEntry\hashHead = ExtractField(line, 7)
|
||
newEntry\hashIndex = ExtractField(line, 8)
|
||
newEntry\similarity = ExtractField(line, 9)
|
||
|
||
; Chemins séparés par une tabulation
|
||
parts = ExtractField(line, 10)
|
||
tabPos = FindString(parts, #TAB$)
|
||
If tabPos > 0
|
||
newEntry\originalPath = Left(parts, tabPos - 1)
|
||
newEntry\path = Mid(parts, tabPos + 1)
|
||
Else
|
||
newEntry\path = parts
|
||
EndIf
|
||
|
||
Case 'u' ; Conflit non résolu
|
||
; Format: u XY sub m1 m2 m3 mW h1 h2 h3 path
|
||
newEntry\statusIndex = Asc(Mid(line, 3, 1))
|
||
newEntry\statusWorktree = Asc(Mid(line, 4, 1))
|
||
newEntry\submodule = Asc(Mid(line, 6, 1))
|
||
newEntry\modeHead = ExtractField(line, 4) ; stage 1
|
||
newEntry\modeIndex = ExtractField(line, 5) ; stage 2
|
||
newEntry\modeWorktree = ExtractField(line, 6) ; stage 3
|
||
; Mode working tree à la position 7
|
||
newEntry\hashStage1 = ExtractField(line, 8)
|
||
newEntry\hashStage2 = ExtractField(line, 9)
|
||
newEntry\hashStage3 = ExtractField(line, 10)
|
||
newEntry\path = ExtractField(line, 11)
|
||
|
||
Case '?' ; Fichier non suivi
|
||
newEntry\statusWorktree = '?'
|
||
newEntry\path = Mid(line, 3) ; Tout après "? "
|
||
|
||
Case '!' ; Fichier ignoré
|
||
newEntry\statusWorktree = '!'
|
||
newEntry\path = Mid(line, 3) ; Tout après "! "
|
||
EndSelect
|
||
|
||
; Ajouter l'entrée à la liste
|
||
AddElement(*status\files())
|
||
*status\files() = newEntry
|
||
EndProcedure
|
||
|
||
Procedure.s GetStatusDescription(status.s)
|
||
Select status
|
||
Case "M "
|
||
ProcedureReturn "Modified in index";Modifié dans index seulement
|
||
Case " M"
|
||
ProcedureReturn "Modified in working tree";Modifié dans working tree seulement
|
||
Case "MM"
|
||
ProcedureReturn "Modified in index and working tree";Modifié dans index ET working tree
|
||
Case "A "
|
||
ProcedureReturn "Added to index";Ajouté à l'index
|
||
Case "AM"
|
||
ProcedureReturn "Added to index, modified in working tree";Ajouté à l'index, modifié dans working tree
|
||
Case " D"
|
||
ProcedureReturn "Deleted from working tree";Supprimé du working tree
|
||
Case "D "
|
||
ProcedureReturn "Deleted from index";Supprimé de l'index
|
||
Case "AD"
|
||
ProcedureReturn "Added to index, deleted from working tree";Ajouté à l'index, supprimé du working tree
|
||
Case " R"
|
||
ProcedureReturn "Renamed in working tree";Renommé dans working tree
|
||
Case "R "
|
||
ProcedureReturn "Renamed in index";Renommé dans index
|
||
Case " C"
|
||
ProcedureReturn "Copied in working tree";Copié dans working tree
|
||
Case "C "
|
||
ProcedureReturn "Copied in index";Copié dans index
|
||
Case "DD"
|
||
ProcedureReturn "Deleted by both (conflict)";Supprimé dans les deux (conflit)
|
||
Case "AU"
|
||
ProcedureReturn "Added by us (conflict)";Ajouté par eux (conflit)
|
||
Case "UD"
|
||
ProcedureReturn "Deleted by them (conflict)";Supprimé par eux (conflit)
|
||
Case "UA"
|
||
ProcedureReturn "Added by them (conflict)";Ajouté par eux (conflit)
|
||
Case "DU"
|
||
ProcedureReturn "Deleted by us (conflict)";Supprimé par nous (conflit)
|
||
Case "AA"
|
||
ProcedureReturn "Added by both (conflict)";Ajouté des deux côtés (conflit)
|
||
Case "UU"
|
||
ProcedureReturn "Modified by both (conflict)";Modifié des deux côtés (conflit)
|
||
Case "??"
|
||
ProcedureReturn "Untracked" ;Non suivi
|
||
Case "!!"
|
||
ProcedureReturn "Ignored" ;Ignoré
|
||
Default
|
||
ProcedureReturn "Unknown status ->"+status
|
||
EndSelect
|
||
EndProcedure
|
||
|
||
Procedure GetGitStatus()
|
||
; Vider la liste existante
|
||
ClearList(main\listFilesGit())
|
||
|
||
; Configuration pour git status --porcelain --ignored
|
||
main\Gitcall\args = "status --porcelain --ignored"
|
||
code = RunExe(@main\Gitcall)
|
||
|
||
If code = 0
|
||
; Parser la sortie ligne par ligne
|
||
output$ = main\Gitcall\output
|
||
|
||
; Diviser en lignes
|
||
Dim lines.s(0)
|
||
lineCount = 0
|
||
currentPos = 1
|
||
|
||
; Compter les lignes
|
||
For i = 1 To Len(output$)
|
||
If Mid(output$, i, 1) = #LF$ Or Mid(output$, i, 1) = #CR$
|
||
lineCount + 1
|
||
EndIf
|
||
Next
|
||
|
||
; Redimensionner le tableau
|
||
If lineCount > 0
|
||
ReDim lines.s(lineCount)
|
||
|
||
; Remplir le tableau avec les lignes
|
||
lineIndex = 0
|
||
startPos = 1
|
||
|
||
For i = 1 To Len(output$)
|
||
If Mid(output$, i, 1) = #LF$ Or Mid(output$, i, 1) = #CR$
|
||
If i > startPos
|
||
lines(lineIndex) = Mid(output$, startPos, i - startPos)
|
||
lineIndex + 1
|
||
EndIf
|
||
startPos = i + 1
|
||
; Gérer CRLF
|
||
If Mid(output$, i, 1) = #CR$ And i < Len(output$) And Mid(output$, i + 1, 1) = #LF$
|
||
i + 1
|
||
startPos = i + 1
|
||
EndIf
|
||
EndIf
|
||
Next
|
||
|
||
; Traiter la dernière ligne si elle n'a pas de retour à la ligne
|
||
If startPos <= Len(output$)
|
||
lines(lineIndex) = Mid(output$, startPos)
|
||
EndIf
|
||
EndIf
|
||
|
||
; Parser chaque ligne
|
||
For i = 0 To ArraySize(lines())
|
||
line$ = lines(i)
|
||
If line$ <> ""
|
||
; Le format est : XY filename
|
||
; X = index status, Y = working tree status
|
||
If Len(line$) >= 3
|
||
AddElement(main\listFilesGit())
|
||
|
||
|
||
; Extraire le status (2 premiers caractères)
|
||
main\listFilesGit()\status = Left(line$, 2)
|
||
main\listFilesGit()\indexStatus = Left(line$, 1)
|
||
main\listFilesGit()\workingTreeStatus = Mid(line$, 2, 1)
|
||
|
||
; Extraire le nom du fichier (à partir du 4ème caractère)
|
||
main\listFilesGit()\name = Mid(line$, 4)
|
||
|
||
; Obtenir la description du status
|
||
main\listFilesGit()\statusDescription = GetStatusDescription(main\listFilesGit()\status)
|
||
EndIf
|
||
EndIf
|
||
Next
|
||
|
||
Debug "Récupération des status Git réussie. " + Str(ListSize(main\listFilesGit())) + " fichiers trouvés."
|
||
ProcedureReturn #True
|
||
|
||
Else
|
||
Debug "Erreur Git ("+Str(code)+") "+main\GitCall\errors
|
||
ProcedureReturn #False
|
||
EndIf
|
||
EndProcedure
|
||
|
||
; Récupère le nombre immédiatement avant un mot-clé dans une ligne
|
||
Procedure.i ExtractNumberBefore(word.s, line.s)
|
||
Protected pos = FindString(line, word, 1)
|
||
If pos = 0 : ProcedureReturn -1 : EndIf
|
||
Protected i = pos - 1
|
||
; remonter jusqu'au début du bloc de chiffres
|
||
While i > 0 And Mid(line, i, 1) = " "
|
||
i - 1
|
||
Wend
|
||
Protected j = i
|
||
While j > 0 And Mid(line, j, 1) >= "0" And Mid(line, j, 1) <= "9"
|
||
j - 1
|
||
Wend
|
||
Protected num$ = Mid(line, j + 1, i - j)
|
||
If num$ <> "" : ProcedureReturn Val(num$) : EndIf
|
||
ProcedureReturn -1
|
||
EndProcedure
|
||
|
||
; Construit "N fichiers, +X/-Y" à partir d’un bloc courtstat
|
||
Procedure.s BuildFilesSummary(statBlock.s)
|
||
Protected files = -1, ins = -1, del = -1, i, n, line$
|
||
n = CountString(statBlock, #LF$) + 1
|
||
For i = 1 To n
|
||
line$ = Trim(StringField(statBlock, i, #LF$))
|
||
If FindString(line$, "changed", 1)
|
||
files = ExtractNumberBefore("file", line$)
|
||
If files = -1 : files = ExtractNumberBefore("files", line$) : EndIf
|
||
ins = ExtractNumberBefore("insertion", line$)
|
||
del = ExtractNumberBefore("deletion", line$)
|
||
Break
|
||
EndIf
|
||
Next
|
||
Protected out$
|
||
If files >= 0
|
||
out$ = Str(files) + " fichiers"
|
||
If ins >= 0 Or del >= 0
|
||
out$ + ", "
|
||
If ins >= 0 : out$ + "+" + Str(ins) : EndIf
|
||
If del >= 0 : out$ + "/-" + Str(del) : EndIf
|
||
EndIf
|
||
EndIf
|
||
If out$ = "" : out$ = "-" : EndIf
|
||
ProcedureReturn out$
|
||
EndProcedure
|
||
|
||
; Remplit #GdtListHistory avec l'historique Git
|
||
; MaxCount : nombre max de commits (par défaut 200)
|
||
Procedure.i GetCommitHistory(MaxCount.i = 200)
|
||
Protected code.i, args.s, out$, rec$, headerLine$, rest$, idx.i
|
||
Protected fs.s, header.s, date$, author$, refs$, subj$
|
||
Protected full$, short$, an$, ae$, rs$ = Chr(30), us$ = Chr(31)
|
||
Protected start.i, stop.i, pos.i
|
||
|
||
If EnsureGitAvailable() = 0
|
||
MessageRequester("Git log", "Git introuvable.", #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
DisableGadget(#GdtListHistory, #True)
|
||
ClearGadgetItems(#GdtListHistory)
|
||
ClearList(main\GitHistory())
|
||
|
||
; -- Commande log :
|
||
; - séparateur enregistrements RS (0x1E) en tête de chaque commit
|
||
; - séparateur champs US (0x1F)
|
||
; - --shortstat pour "N files changed, X insertions, Y deletions"
|
||
args = "log --max-count=" + Str(MaxCount) + " --decorate=short --shortstat " +
|
||
"--date=format-local:" + Chr(34) + "%Y-%m-%d %H:%M" + Chr(34) + " " +
|
||
"--pretty=format:" + Chr(34) + "%x1e%H%x1f%h%x1f%ad%x1f%an%x1f%ae%x1f%s%x1f%D" + Chr(34)
|
||
|
||
main\GitCall\args = args
|
||
code = RunExe(@main\GitCall)
|
||
If code <> 0
|
||
MessageRequester("Git log", "Échec: " + #LF$ + main\GitCall\errors, #PB_MessageRequester_Error)
|
||
DisableGadget(#GdtListHistory, #False)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
out$ = main\GitCall\output
|
||
If out$ = ""
|
||
DisableGadget(#GdtListHistory, #False)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
|
||
; On ajoute un RS final pour faciliter le découpage
|
||
out$ + rs$
|
||
|
||
pos = 1
|
||
While pos <= Len(out$)
|
||
; chercher le début d’un enregistrement
|
||
start = FindString(out$, rs$, pos)
|
||
If start = 0 : Break : EndIf
|
||
start + 1
|
||
; chercher le prochain RS (fin de l’enregistrement)
|
||
stop = FindString(out$, rs$, start)
|
||
If stop = 0 : Break : EndIf
|
||
|
||
rec$ = Mid(out$, start, stop - start)
|
||
pos = stop
|
||
|
||
If rec$ = "" : Continue : EndIf
|
||
|
||
; La première ligne = les champs formatés ; le reste = bloc shortstat
|
||
headerLine$ = StringField(rec$, 1, #LF$)
|
||
rest$ = Mid(rec$, Len(headerLine$) + 2)
|
||
|
||
; Découper les champs (US = 0x1F)
|
||
full$ = StringField(headerLine$, 1, us$)
|
||
short$ = StringField(headerLine$, 2, us$)
|
||
date$ = StringField(headerLine$, 3, us$)
|
||
an$ = StringField(headerLine$, 4, us$)
|
||
ae$ = StringField(headerLine$, 5, us$)
|
||
subj$ = StringField(headerLine$, 6, us$)
|
||
refs$ = StringField(headerLine$, 7, us$)
|
||
|
||
header = short$
|
||
If Trim(refs$) <> "" : header + " (" + refs$ + ")" : EndIf
|
||
author$ = an$ + " <" + ae$ + ">"
|
||
fs = BuildFilesSummary(rest$)
|
||
|
||
; Mémoriser + insérer la ligne dans la ListIcon
|
||
AddElement(main\GitHistory())
|
||
main\GitHistory()\fullHash = full$
|
||
main\GitHistory()\shortHash = short$
|
||
main\GitHistory()\date = date$
|
||
main\GitHistory()\authorName = an$
|
||
main\GitHistory()\authorEmail = ae$
|
||
main\GitHistory()\refs = refs$
|
||
main\GitHistory()\subject = subj$
|
||
main\GitHistory()\filesSummary = fs
|
||
|
||
idx = CountGadgetItems(#GdtListHistory)
|
||
AddGadgetItem(#GdtListHistory, idx, header + Chr(10) + date$ + Chr(10) + author$ + Chr(10) + fs + Chr(10) + subj$)
|
||
; On stocke un pointeur sur la ligne pour un usage futur (ex: bouton Restore)
|
||
SetGadgetItemData(#GdtListHistory, idx, @main\GitHistory())
|
||
|
||
If #EnableDebug
|
||
Debug "[LOG] " + short$ + " | " + date$ + " | " + author + " | " + fs + " | " + subj$
|
||
EndIf
|
||
Wend
|
||
|
||
DisableGadget(#GdtListHistory, #False)
|
||
ProcedureReturn 1
|
||
EndProcedure
|
||
|
||
; =============================================================================
|
||
;-.gitignore
|
||
; =============================================================================
|
||
|
||
Procedure.s NormalizeGitIgnoreEntry(entry.s)
|
||
Protected e$ = Trim(entry)
|
||
e$ = ReplaceString(e$, "\", "/") ; Git préfère les /
|
||
If Left(e$, 2) = "./" : e$ = Mid(e$, 3) : EndIf
|
||
ProcedureReturn e$
|
||
EndProcedure
|
||
|
||
Procedure.b GitIgnoreHasEntry(filePath.s, entry.s)
|
||
Protected f, line$
|
||
entry = NormalizeGitIgnoreEntry(entry)
|
||
f = ReadFile(#PB_Any, filePath)
|
||
If f
|
||
While Eof(f) = 0
|
||
line$ = Trim(ReadString(f))
|
||
If line$ <> "" And Left(line$, 1) <> "#"
|
||
If NormalizeGitIgnoreEntry(line$) = entry
|
||
CloseFile(f)
|
||
ProcedureReturn #True
|
||
EndIf
|
||
EndIf
|
||
Wend
|
||
CloseFile(f)
|
||
EndIf
|
||
ProcedureReturn #False
|
||
EndProcedure
|
||
|
||
Procedure.i GitIgnoreAddEntry(filePath.s, entry.s)
|
||
Protected f
|
||
entry = NormalizeGitIgnoreEntry(entry)
|
||
f = OpenFile(#PB_Any, filePath, #PB_File_Append)
|
||
If f
|
||
; S’assure que le fichier finit bien par un saut de ligne
|
||
If Lof(f) > 0 : WriteStringN(f, "") : EndIf
|
||
WriteStringN(f, entry)
|
||
CloseFile(f)
|
||
ProcedureReturn #True
|
||
EndIf
|
||
ProcedureReturn #False
|
||
EndProcedure
|
||
|
||
Procedure.i GitIgnoreRemoveEntry(filePath.s, entry.s)
|
||
Protected fin, fout, line$, tmp$
|
||
entry = NormalizeGitIgnoreEntry(entry)
|
||
tmp$ = filePath + ".tmp"
|
||
|
||
fin = ReadFile(#PB_Any, filePath)
|
||
If fin = 0 : ProcedureReturn #False : EndIf
|
||
|
||
fout = CreateFile(#PB_Any, tmp$)
|
||
If fout = 0
|
||
CloseFile(fin)
|
||
ProcedureReturn #False
|
||
EndIf
|
||
|
||
While Eof(fin) = 0
|
||
line$ = ReadString(fin)
|
||
If NormalizeGitIgnoreEntry(Trim(line$)) <> entry
|
||
WriteStringN(fout, line$)
|
||
EndIf
|
||
Wend
|
||
|
||
CloseFile(fin) : CloseFile(fout)
|
||
DeleteFile(filePath)
|
||
If RenameFile(tmp$, filePath) = 0
|
||
; rollback si nécessaire
|
||
RenameFile(tmp$, filePath)
|
||
ProcedureReturn #False
|
||
EndIf
|
||
|
||
ProcedureReturn #True
|
||
EndProcedure
|
||
|
||
; =============================================================================
|
||
; Action principale : toggle pour l’élément sélectionné (ou les éléments cochés)
|
||
; =============================================================================
|
||
Procedure.i ToggleGitIgnoreForSelection()
|
||
Protected gitignore$, i.i, count.i, path$, added.i, removed.i, create.i
|
||
Protected nbSelected.i = 0, idx.i
|
||
|
||
gitignore$ = main\GitCall\workdir + ".gitignore"
|
||
count = CountGadgetItems(#GdtListStatus)
|
||
|
||
; -- Récupère toutes les lignes sélectionnées (multi ou simple)
|
||
Dim selected.i(Max(count-1, 0))
|
||
For i = 0 To count - 1
|
||
If GetGadgetItemState(#GdtListStatus, i) & #PB_ListIcon_Selected
|
||
selected(nbSelected) = i
|
||
nbSelected + 1
|
||
EndIf
|
||
Next
|
||
|
||
; fallback si le gadget n’est pas en multi-sélection
|
||
If nbSelected = 0
|
||
idx = GetGadgetState(#GdtListStatus)
|
||
If idx >= 0
|
||
ReDim selected.i(0)
|
||
selected(0) = idx
|
||
nbSelected = 1
|
||
EndIf
|
||
EndIf
|
||
|
||
If nbSelected = 0
|
||
MessageRequester("Git .gitignore", "Sélectionne au moins un fichier dans la liste.", #PB_MessageRequester_Warning)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; -- S’assurer que .gitignore existe
|
||
If FileSize(gitignore$) = -1
|
||
create = MessageRequester("Git .gitignore", "Le fichier .gitignore n’existe pas."+#LF$+"Le créer maintenant ?", #PB_MessageRequester_YesNo)
|
||
If create = #PB_MessageRequester_Yes
|
||
Protected hf.i = CreateFile(#PB_Any, gitignore$)
|
||
If hf : CloseFile(hf) : Else
|
||
MessageRequester("Git .gitignore", "Impossible de créer .gitignore.", #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
Else
|
||
ProcedureReturn 0
|
||
EndIf
|
||
EndIf
|
||
|
||
; -- Traite chaque fichier sélectionné
|
||
For i = 0 To nbSelected - 1
|
||
path$ = StringField(GetGadgetItemText(#GdtListStatus, selected(i)), 1, Chr(10))
|
||
path$ = NormalizeGitIgnoreEntry(path$)
|
||
|
||
If GitIgnoreHasEntry(gitignore$, path$)
|
||
If MessageRequester("Git .gitignore", "“"+path$+"” est déjà ignoré."+#LF$+"Le retirer de .gitignore ?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
|
||
If GitIgnoreRemoveEntry(gitignore$, path$) : removed + 1 : EndIf
|
||
EndIf
|
||
Else
|
||
If MessageRequester("Git .gitignore", "“"+path$+"” n’est pas ignoré."+#LF$+"L’ajouter à .gitignore ?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
|
||
If GitIgnoreAddEntry(gitignore$, path$) : added + 1 : EndIf
|
||
EndIf
|
||
EndIf
|
||
Next
|
||
|
||
If added Or removed
|
||
MessageRequester("Git .gitignore", "Ajoutées : "+Str(added)+#LF$+"Retirées : "+Str(removed), #PB_MessageRequester_Info)
|
||
CreateThread(@RefreshFileList(), 0)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
Procedure ReadGitIgnorefile()
|
||
If FileSize(".gitignore")<>-1
|
||
DisableGadget(#GdtTxtGitIgnore,#False)
|
||
Protected hf.i=OpenFile(#PB_Any,".gitignore")
|
||
Protected gitignore.s=""
|
||
If hf
|
||
While Eof(hf) = 0 ; Boucle tant que la fin du fichier n'est pas atteinte. (Eof = 'End Of File')
|
||
gitignore=gitignore+ReadString(hf)+#LF$ ; Affiche du fichier
|
||
Wend
|
||
|
||
CloseFile(hf)
|
||
SetGadgetText(#GdtTxtGitIgnore,gitignore)
|
||
EndIf
|
||
Else
|
||
DisableGadget(#GdtTxtGitIgnore,#True)
|
||
EndIf
|
||
EndProcedure
|
||
|
||
|
||
;-Suite
|
||
|
||
Procedure AddRemoteRepo(Url.s,name.s="origin")
|
||
;Check if this remote already exists
|
||
main\Gitcall\args = "remote get-url "+name
|
||
If RunExe(@main\Gitcall) = 0
|
||
;if yes we remove it
|
||
main\Gitcall\args = "remote remove "+name
|
||
RunExe(@main\Gitcall)
|
||
EndIf
|
||
; We add a new remote
|
||
main\Gitcall\args = "remote add "+name+" "+Url
|
||
If RunExe(@main\Gitcall) = 0
|
||
MessageRequester("Git Remote", "OK:" + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info)
|
||
ProcedureReturn #True
|
||
Else
|
||
MessageRequester("Git config", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error)
|
||
ProcedureReturn #False
|
||
EndIf
|
||
EndProcedure
|
||
|
||
Procedure GetRemoteUrl(name.s="origin")
|
||
main\Gitcall\args = "remote get-url "+name
|
||
If RunExe(@main\Gitcall) = 0
|
||
SetGadgetText(#GdtFieldRemote,main\GitCall\output)
|
||
Else
|
||
SetGadgetText(#GdtFieldRemote,"")
|
||
EndIf
|
||
EndProcedure
|
||
|
||
Procedure DoPush()
|
||
If Trim(GetGadgetText(#GdtFieldRemote))=""
|
||
MessageRequester("Git Push", "Échec: " + #LF$ + "You Must tu have a remote", #PB_MessageRequester_Error)
|
||
EndIf
|
||
AddRemoteRepo(Trim(GetGadgetText(#GdtFieldRemote)))
|
||
main\Gitcall\args = "push -u origin "+GetGadgetText(#GdtSlctBranch)
|
||
If RunExe(@main\Gitcall) = 0
|
||
MessageRequester("Git Push", "OK:" + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info)
|
||
ProcedureReturn #True
|
||
Else
|
||
MessageRequester("Git Push", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error)
|
||
ProcedureReturn #False
|
||
EndIf
|
||
EndProcedure
|
||
|
||
Procedure DoStatus()
|
||
Protected *status.GitStatus=@main\GitStatus
|
||
|
||
main\Gitcall\args = "status --porcelain=v2 --ignored"
|
||
|
||
If RunExe(@main\Gitcall) <> 0
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; Parser ligne par ligne
|
||
Protected n.l = CountString(main\Gitcall\output, #LF$) + 1
|
||
Protected i.l, line.s
|
||
|
||
For i = 1 To n
|
||
line = StringField(main\Gitcall\output, i, #LF$)
|
||
line = Trim(line)
|
||
|
||
If Len(line) = 0
|
||
Continue ; Ignorer les lignes vides
|
||
EndIf
|
||
|
||
; Parser selon le type de ligne
|
||
If Left(line, 1) = "#"
|
||
; Header de branche
|
||
ParseBranchHeader(line, *status)
|
||
Else
|
||
; Entrée de fichier
|
||
ParseFileEntry(line, *status)
|
||
EndIf
|
||
Next
|
||
|
||
; Exemple d'utilisation des données parsées
|
||
Debug "=== Informations de branche ==="
|
||
Debug "Branche: " + *status\branchInfo\head
|
||
Debug "Commit: " + *status\branchInfo\oid
|
||
Debug "Upstream: " + *status\branchInfo\upstream
|
||
Debug "En avance: " + Str(*status\branchInfo\ahead)
|
||
Debug "En retard: " + Str(*status\branchInfo\behind)
|
||
|
||
Debug "=== Fichiers ==="
|
||
ForEach *status\files()
|
||
Protected find.b=#False
|
||
ForEach main\listFilesGit()
|
||
If main\listFilesGit()\name=*status\files()\path:
|
||
find=#True
|
||
Debug "> "+GetFilePart(main\listFilesGit()\name)+" "+main\listFilesGit()\status+"<>"+*status\files()\statusIndex+Chr(*status\files()\statusWorktree)
|
||
|
||
main\listFilesGit()\status=Chr(*status\files()\statusIndex)+Chr(*status\files()\statusWorktree)
|
||
If Len(main\listFilesGit()\status)=1
|
||
main\listFilesGit()\status="?"+main\listFilesGit()\status
|
||
EndIf
|
||
Break
|
||
EndIf
|
||
Next
|
||
|
||
|
||
|
||
Debug "Type: >" + Chr(*status\files()\type)+"<"
|
||
Debug "Statut Index/Worktree: " + Chr(*status\files()\statusIndex) + "/" + Chr(*status\files()\statusWorktree)
|
||
Debug "Chemin: " + *status\files()\path
|
||
If *status\files()\originalPath <> ""
|
||
Debug "Chemin original: " + *status\files()\originalPath
|
||
EndIf
|
||
If *status\files()\similarity <> ""
|
||
Debug "Similarité: " + *status\files()\similarity
|
||
EndIf
|
||
Debug "---"
|
||
Next
|
||
|
||
ProcedureReturn 1
|
||
EndProcedure
|
||
|
||
Procedure DoInit()
|
||
main\Gitcall\args="init --initial-branch="+main\Currentbranch
|
||
If RunExe(@main\Gitcall)=0
|
||
MessageRequester("Git config", "OK:" + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info)
|
||
ProcedureReturn #True
|
||
Else
|
||
MessageRequester("Git config", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error)
|
||
ProcedureReturn #False
|
||
EndIf
|
||
EndProcedure
|
||
|
||
Procedure DoLsFilesByType( includeType.s = "CDOI")
|
||
; Récupère les fichiers par type spécifique
|
||
; includeType: "C"=cached, "D"=deleted, "O"=others, "I"=ignored
|
||
|
||
Protected args.s = "ls-files"
|
||
Protected status.s=""
|
||
; Construire les arguments selon les types demandés
|
||
If FindString(includeType, "C") > 0
|
||
args + " --cached --stage"
|
||
status =".."
|
||
EndIf
|
||
If FindString(includeType, "D") > 0
|
||
args + " --deleted"
|
||
status=".D"
|
||
EndIf
|
||
If FindString(includeType, "O") > 0
|
||
args + " --others"
|
||
status="??"
|
||
EndIf
|
||
If FindString(includeType, "I") > 0
|
||
args + " --ignored --exclude-standard"
|
||
status="!!"
|
||
EndIf
|
||
Debug args
|
||
main\Gitcall\args = args
|
||
If RunExe(@main\Gitcall) <> 0
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; Parser les résultats
|
||
Protected n.l = CountString(main\Gitcall\output, #LF$) + 1
|
||
Protected i.l, line.s
|
||
|
||
For i = 1 To n
|
||
line = StringField(main\Gitcall\output, i, #LF$)
|
||
line = Trim(line)
|
||
|
||
If Len(line) = 0
|
||
Continue
|
||
EndIf
|
||
|
||
|
||
AddElement(main\listFilesGit())
|
||
; Format avec métadonnées (fichiers cachés avec --stage)
|
||
If CountString(line, " ") >= 2 And FindString(line, #TAB$) > 0
|
||
;Debug "mode :"+StringField(line, 1, " ")
|
||
;Debug "hash :"+StringField(line, 2, " ")
|
||
;Debug "stageAndPath :" + StringField(line, 3, " ")
|
||
Protected tabPos.l = FindString(StringField(line, 3, " "), #TAB$)
|
||
|
||
If tabPos > 0
|
||
;Debug "-stage :"+ Left(StringField(line, 3, " "), tabPos - 1)
|
||
main\listFilesGit()\name=Mid(StringField(line, 3, " "), tabPos + 1)
|
||
|
||
EndIf
|
||
|
||
Else
|
||
; Format simple: juste le chemin
|
||
main\listFilesGit()\name=line
|
||
|
||
EndIf
|
||
main\listFilesGit()\status=status
|
||
|
||
Next
|
||
EndProcedure
|
||
|
||
Procedure.i DoCommit()
|
||
Protected code.i,nb.l=0
|
||
main\GitCall\args = "add"
|
||
For i = 0 To CountGadgetItems(#GdtListStatus) - 1
|
||
If GetGadgetItemState(#GdtListStatus, i) & #PB_ListIcon_Checked
|
||
nb+1
|
||
main\GitCall\args+" "+Chr(34)+StringField(GetGadgetItemText(#GdtListStatus, i),1,Chr(10))+Chr(34)
|
||
EndIf
|
||
Next i
|
||
If nb=0
|
||
MessageRequester("Git add", "Échec: Vous devez selectionnez des fichiers à ajouter au commit", #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
If Trim(GetGadgetText(#GdtFieldMessage))=""
|
||
MessageRequester("Git add", "Échec: Vous devez mettre un message", #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
code = RunExe(@main\GitCall)
|
||
If code <> 0
|
||
MessageRequester("Git add", "Échec: " + #LF$ + main\GitCall\errors, #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; Commit with message / Valider avec un message
|
||
main\GitCall\args = "commit -m " + Chr(34) + GetGadgetText(#GdtFieldMessage) + Chr(34)
|
||
code = RunExe(@main\GitCall)
|
||
If code <> 0
|
||
MessageRequester("Git commit", "Échec ou rien à valider: " + #LF$ + main\GitCall\errors + #LF$ + main\GitCall\output, #PB_MessageRequester_Warning)
|
||
ProcedureReturn 0
|
||
Else
|
||
MessageRequester("Git commit", "OK:" + #LF$ + main\GitCall\output, #PB_MessageRequester_Info)
|
||
EndIf
|
||
|
||
ProcedureReturn 1
|
||
EndProcedure
|
||
|
||
Procedure.s GetBranchesList()
|
||
ClearGadgetItems(#GdtSlctBranch)
|
||
main\Gitcall\args = "branch"
|
||
If RunExe(@main\Gitcall) <> 0
|
||
ProcedureReturn ""
|
||
EndIf
|
||
|
||
; Parser ligne par ligne
|
||
Protected n.l = CountString(main\Gitcall\output, #LF$) + 1
|
||
Protected i.l, line.s
|
||
|
||
For i = 1 To n
|
||
line = StringField(main\Gitcall\output, i, #LF$)
|
||
line = Trim(line)
|
||
|
||
If Len(line) = 0
|
||
Continue ; Ignorer les lignes vides
|
||
EndIf
|
||
selectbranch.b=#False
|
||
If Left(line,1)="*"
|
||
selectbranch=#True
|
||
line=Trim(StringField(line,2," "))
|
||
EndIf
|
||
AddGadgetItem(#GdtSlctBranch,i-1,line)
|
||
If selectbranch=#True
|
||
SetGadgetState(#GdtSlctBranch,i-1)
|
||
EndIf
|
||
Next
|
||
|
||
EndProcedure
|
||
|
||
; =============================================================================
|
||
;-Identity
|
||
; =============================================================================
|
||
|
||
|
||
Procedure SetGitIdentity()
|
||
If main\info\isGit=#True ; check Git is detected
|
||
Protected userName.s = Trim(GetGadgetText(#GdtFieldUserName))
|
||
Protected userEmail.s = Trim(GetGadgetText(#GdtFieldUserEmail))
|
||
Protected scope.s = LCase(GetGadgetText(#GdtSlctScope))
|
||
|
||
Protected result.s=""
|
||
Protected errors.s=""
|
||
; Définir le nom d'utilisateur
|
||
If userName <> ""
|
||
main\Gitcall\args = "config --" + scope + " user.name " + Chr(34) + userName + Chr(34)
|
||
Debug "Setting username: " + main\Gitcall\args
|
||
If RunExe(@main\Gitcall) <> 0
|
||
errors=errors+ main\Gitcall\errors+ #LF$
|
||
Else
|
||
result=result+ main\Gitcall\output + #LF$
|
||
EndIf
|
||
Else
|
||
Debug "Nom d'utilisateur vide, ignoré"
|
||
EndIf
|
||
|
||
; Définir l'email
|
||
If userEmail <> ""
|
||
main\Gitcall\args = "config --" + scope + " user.email " + Chr(34) + userEmail + Chr(34)
|
||
Debug "Setting email: " + main\Gitcall\args
|
||
If RunExe(@main\Gitcall) <> 0
|
||
errors=errors+ main\Gitcall\errors
|
||
Else
|
||
result=result+ main\Gitcall\output
|
||
EndIf
|
||
Else
|
||
Debug "Email vide, ignoré"
|
||
EndIf
|
||
If errors="" And Not result=""
|
||
MessageRequester("Git config", "OK:" + #LF$ + result, #PB_MessageRequester_Info)
|
||
ProcedureReturn #False
|
||
Else
|
||
MessageRequester("Git config", "Échec: " + #LF$ + errors, #PB_MessageRequester_Error)
|
||
ProcedureReturn #True
|
||
EndIf
|
||
Else
|
||
Debug "Git non détecté"
|
||
ProcedureReturn #False
|
||
EndIf
|
||
EndProcedure
|
||
|
||
Procedure GetGitIdentity()
|
||
If main\info\isGit=#True ; check Git is detected
|
||
main\Gitcall\args = "config --get --"+LCase(GetGadgetText(#GdtSlctScope))+" user.name"
|
||
Debug main\Gitcall\args
|
||
If RunExe(@main\Gitcall) = 0
|
||
SetGadgetText(#GdtFieldUserName,Trim(main\GitCall\output))
|
||
Else
|
||
SetGadgetText(#GdtFieldUserName,"")
|
||
EndIf
|
||
|
||
main\Gitcall\args = "config --get --"+LCase(GetGadgetText(#GdtSlctScope))+" user.email"
|
||
If RunExe(@main\Gitcall) = 0
|
||
SetGadgetText(#GdtFieldUserEmail,Trim(main\GitCall\output))
|
||
Else
|
||
SetGadgetText(#GdtFieldUserEmail,"")
|
||
EndIf
|
||
EndIf
|
||
EndProcedure
|
||
|
||
; =============================================================================
|
||
;-History Commit
|
||
; =============================================================================
|
||
|
||
|
||
; Retrouver le full hash à partir du short hash
|
||
Procedure.s FindFullHashByShort(short$)
|
||
ForEach main\GitHistory()
|
||
If LCase(main\GitHistory()\shortHash) = LCase(short$)
|
||
ProcedureReturn main\GitHistory()\fullHash
|
||
EndIf
|
||
Next
|
||
ProcedureReturn ""
|
||
EndProcedure
|
||
|
||
|
||
; Affiche les infos (en-tête + fichiers + shortstat) dans l'éditeur
|
||
Procedure.i ShowCommitInfo(hash$)
|
||
Protected code.i, args.s, out$
|
||
|
||
If hash$ = ""
|
||
SetGadgetText(#GdtTxtCommitInfo, "")
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; Sortie compacte et lisible (sans patch)
|
||
; - --decorate=short pour refs
|
||
; - --name-status pour statut par fichier
|
||
; - --stat pour le récap
|
||
; - --date=format-local pour respecter l’OS
|
||
args = "show --no-color --decorate=short " +
|
||
"--date=format-local:" + Chr(34) + "%Y-%m-%d %H:%M" + Chr(34) + " " +
|
||
"--pretty=format:" + Chr(34) +
|
||
"commit %H (%h)%nAuthor: %an <%ae>%nDate: %ad%nRefs: %D%n" +
|
||
"%n %s%n%n%b" + Chr(34) + " " +
|
||
"--name-status --stat " + hash$
|
||
|
||
main\GitCall\args = args
|
||
code = RunExe(@main\GitCall)
|
||
If code <> 0
|
||
SetGadgetText(#GdtTxtCommitInfo, "Erreur git show:" + #LF$ + main\GitCall\errors)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
out$ = main\GitCall\output
|
||
If out$ = "" : out$ = "(Aucune information renvoyée par git pour " + hash$ + ")" : EndIf
|
||
|
||
SetGadgetText(#GdtTxtCommitInfo, out$)
|
||
|
||
If #EnableDebug
|
||
Debug "[ShowCommitInfo] " + hash$
|
||
EndIf
|
||
|
||
ProcedureReturn 1
|
||
EndProcedure
|
||
|
||
|
||
; À appeler quand la sélection de #GdtListHistory change
|
||
Procedure.i ShowSelectedCommitInfo()
|
||
Protected row.i = GetGadgetState(#GdtListHistory)
|
||
Protected *rowPtr.GitCommitRow, header$, short$, full$
|
||
|
||
If row < 0
|
||
SetGadgetText(#GdtTxtCommitInfo, "")
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; 1) On tente via le pointeur stocké dans l’item data
|
||
*rowPtr = GetGadgetItemData(#GdtListHistory, row)
|
||
If *rowPtr
|
||
full$ = *rowPtr\fullHash
|
||
EndIf
|
||
|
||
; 2) Fallback : on re-déduit via le texte de colonne 0 (short hash au début)
|
||
If full$ = ""
|
||
header$ = GetGadgetItemText(#GdtListHistory, row, 0) ; ex: "abc1234 (HEAD -> main)"
|
||
short$ = StringField(header$, 1, " ") ; -> "abc1234"
|
||
full$ = FindFullHashByShort(short$)
|
||
EndIf
|
||
|
||
ProcedureReturn ShowCommitInfo(full$)
|
||
EndProcedure
|
||
|
||
|
||
Procedure RefreshFileList(null.i)
|
||
DisableGadget(#GdtListStatus,#True)
|
||
DisableGadget(#GdtBtnRefresh,#True)
|
||
ClearGadgetItems(#GdtListStatus)
|
||
ClearList(main\listFilesGit())
|
||
;-Init File List
|
||
DoLsFilesByType("C")
|
||
;DoLsFilesByType("D")
|
||
;DoLsFilesByType("O")
|
||
;DoLsFilesByType("I")
|
||
;DoStatus()
|
||
|
||
GetGitStatus()
|
||
|
||
Protected n.l=n-1
|
||
ForEach main\listFilesGit()
|
||
n=n+1
|
||
AddGadgetItem(#GdtListStatus,n,main\listFilesGit()\name+Chr(10)+main\listFilesGit()\status+Chr(10)+GetStatusDescription(main\listFilesGit()\status))
|
||
If Right(main\listFilesGit()\status,1)="M"
|
||
SetGadgetItemState(#GdtListStatus,n,#PB_ListIcon_Checked)
|
||
EndIf
|
||
Next
|
||
DisableGadget(#GdtListStatus,#False)
|
||
DisableGadget(#GdtBtnRefresh,#False)
|
||
EndProcedure
|
||
|
||
Procedure UpdateHelp(txt.s)
|
||
SetGadgetItemText(#GdtHelp,#PB_Web_HtmlCode,txt.s)
|
||
EndProcedure
|
||
|
||
Procedure OpenGUI()
|
||
Protected argCount.l=CountProgramParameters(), w.l,a$
|
||
If CountProgramParameters() <> 0
|
||
; Parse command line arguments / Analyser les arguments de ligne de commande
|
||
For w = 0 To argCount - 1
|
||
a$ = ProgramParameter(w)
|
||
Select LCase(a$)
|
||
Case "--project" : If w + 1 < argCount : main\GitCall\workdir = ProgramParameter(w + 1) : EndIf
|
||
EndSelect
|
||
Next w
|
||
EndIf
|
||
|
||
;Init Defaut Workdir if no project
|
||
If main\GitCall\workdir=""
|
||
;main\GitCall\workdir="C:\Users\413\Documents\Amstrad CPC\PBIDE-GitTool\"
|
||
main\GitCall\workdir=GetCurrentDirectory()
|
||
EndIf
|
||
|
||
If EnsureGitAvailable()=0
|
||
Debug T("?GITINIT","WOULD YOU INITIALISE THIS FOLDER")
|
||
End
|
||
EndIf
|
||
; --- Dimensions générales ---
|
||
#WinW = 950
|
||
#WinH = 930 ; plus haut pour la zone d’aide
|
||
#PanelX = 15
|
||
#PanelY = 15
|
||
#PanelW = 920
|
||
#PanelH = 700
|
||
#InPad = 15 ; marge interne au Panel
|
||
#FrmGap = 10 ; espace vertical entre frames
|
||
#RowH = 26
|
||
#BtnH = 30
|
||
|
||
If OpenWindow(#WinMain, 0, 0, #WinW, #WinH, "Git helpmate", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
|
||
|
||
PanelGadget(#gdtPnl, #PanelX, #PanelY, #PanelW, #PanelH)
|
||
|
||
; ============================================================
|
||
; Onglet 1 : Dépôt
|
||
; ============================================================
|
||
AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-Repo","Dépôt"))
|
||
|
||
; ---- Cadre "Dépôt local" ----
|
||
FrameGadget(#GdtFrmLocal, #InPad, #InPad, #PanelW - 2*#InPad, 100, T("GdtFrmLocal","Dépôt local"))
|
||
TextGadget(#GdtLblRepo, #InPad + 10, #InPad + 30, 60, 22, T("GdtLblRepo","Dépôt :"))
|
||
StringGadget(#GgtFieldRepo, #InPad + 75, #InPad + 28, #PanelW - 2*#InPad - 75 - 105, #RowH, repoDir$)
|
||
ButtonGadget(#GdtBtnBrowseRepo, #PanelW - #InPad - 95, #InPad + 28, 95, #RowH, T("GdtBtnBrowseRepo","Parcourir…"))
|
||
ButtonGadget(#GdtBtnInit, #InPad + 10, #InPad + 60, 105, #BtnH, T("GdtBtnInit","Init Dépôt"))
|
||
ButtonGadget(#GdtBtnRefresh, #InPad + 120, #InPad + 60, 105, #BtnH, T("GdtBtnRefresh","Rafraîchir"))
|
||
|
||
; ---- Cadre "Distant" ----
|
||
Define yRemote = #InPad + 100 + #FrmGap
|
||
FrameGadget(#GdtFrmRemote, #InPad, yRemote, #PanelW - 2*#InPad, 100, T("GdtFrmRemote","Distant (remote / branche)"))
|
||
TextGadget(#GdtLblRemote, #InPad + 10, yRemote + 30, 60, 22, T("GdtLblRemote","Remote :"))
|
||
StringGadget(#GdtFieldRemote, #InPad + 75, yRemote + 28, 220, #RowH, "")
|
||
TextGadget(#GdtLblBranch, #InPad + 305, yRemote + 30, 70, 22, T("GdtLblBranch","Branche :"))
|
||
ComboBoxGadget(#GdtSlctBranch, #InPad + 375, yRemote + 28, 220, #RowH, #PB_ComboBox_Editable)
|
||
ButtonGadget(#GdtBtnNewBranch, #PanelW - #InPad - 130, yRemote + 26, 130, #BtnH, T("GdtBtnNewBranch","New branch"))
|
||
ButtonGadget(#GdtBtnClone, #InPad + 10, yRemote + 60, 100, #BtnH, T("GdtBtnClone","Clone"))
|
||
ButtonGadget(#GdtBtnPull, #InPad + 120, yRemote + 60, 100, #BtnH, T("GdtBtnPull","Pull"))
|
||
ButtonGadget(#GdtBtnPush, #InPad + 230, yRemote + 60, 100, #BtnH, T("GdtBtnPush","Push"))
|
||
|
||
; ---- Cadre "Fichiers & modifications" ----
|
||
Define yFiles = yRemote + 100 + #FrmGap
|
||
Define hFiles = #PanelH - yFiles - #InPad
|
||
FrameGadget(#GdtFrmFiles, #InPad, yFiles, #PanelW - 2*#InPad, hFiles, T("GdtFrmFiles","Fichiers & modifications"))
|
||
|
||
; Liste des fichiers — hauteur auto (pas de nouvelle variable)
|
||
Define listTop = yFiles + 25
|
||
Define listH = hFiles - (25 + 10 + 2*#BtnH + 20)
|
||
If listH < 120 : listH = 120 : EndIf
|
||
ListIconGadget(#GdtListStatus, #InPad + 10, listTop, #PanelW - 2*#InPad - 20, listH, T("GdtListStatus-Path","Path"), 300, #PB_ListIcon_CheckBoxes | #PB_ListIcon_MultiSelect | #PB_ListIcon_FullRowSelect |#PB_ListIcon_AlwaysShowSelection)
|
||
AddGadgetColumn(#GdtListStatus, 1, T("GdtListStatus-Status","Status"), 50)
|
||
AddGadgetColumn(#GdtListStatus, 2, T("GdtListStatus-Desc","Description"), 300)
|
||
|
||
; Actions locales
|
||
Define yLocalActions = listTop + listH + 10
|
||
ButtonGadget(#GdtBtnRestore, #InPad + 10, yLocalActions, 110, #BtnH, T("GdtBtnRestore","Restaurer"))
|
||
ButtonGadget(#GdtBtnRename, #InPad + 130, yLocalActions, 110, #BtnH, T("GdtBtnRename","Renommer"))
|
||
ButtonGadget(#GdtBtnDelete, #InPad + 250, yLocalActions, 110, #BtnH, T("GdtBtnDelete","Supprimer"))
|
||
ButtonGadget(#GdtBtnIgnore, #InPad + 370, yLocalActions, 110, #BtnH, T("GdtBtnIgnore","Ignorer"))
|
||
|
||
; Message de commit
|
||
Define yMsg = yLocalActions + #BtnH + 10
|
||
TextGadget(#GdtLblMessage, #InPad + 10, yMsg + 4, 80, 22, T("GdtLblMessage","Message :"))
|
||
StringGadget(#GdtFieldMessage, #InPad + 95, yMsg, #PanelW - 2*#InPad - 95 - 110, #RowH, "")
|
||
ButtonGadget(#GdtBtnCommit, #PanelW - #InPad - 100, yMsg - 2, 100, #BtnH, T("GdtBtnCommit","Commit"))
|
||
|
||
; ============================================================
|
||
; Onglet 2 : History
|
||
; ============================================================
|
||
AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-History","History"))
|
||
|
||
; ListIconGadget (colonnes: Header, Date, Auteur, Fichiers, Message)
|
||
ListIconGadget(#GdtListHistory,
|
||
#InPad, #InPad,
|
||
#PanelW - 2*#InPad,
|
||
#PanelH - 2*#InPad - #BtnH - 10 - 150 - 10,
|
||
T("GdtListHistory-Header","Header"), 220, #PB_ListIcon_FullRowSelect)
|
||
AddGadgetColumn(#GdtListHistory, 1, T("GdtListHistory-Date","Date"), 150)
|
||
AddGadgetColumn(#GdtListHistory, 2, T("GdtListHistory-Author","Auteur"), 180)
|
||
AddGadgetColumn(#GdtListHistory, 3, T("GdtListHistory-Files","Fichiers"), 120)
|
||
AddGadgetColumn(#GdtListHistory, 4, T("GdtListHistory-Message","Message"),
|
||
(#PanelW - 2*#InPad) - (220 + 150 + 180 + 120) - 20)
|
||
|
||
; Bouton Restore This Commit (sous la liste)
|
||
ButtonGadget(#GdtBtnRestoreCommit,
|
||
#InPad,
|
||
#PanelH - #InPad - #BtnH - 150 - 10,
|
||
180, #BtnH,
|
||
T("GdtBtnRestoreCommit","Restore This Commit"))
|
||
|
||
; Zone d’info du commit (Editor, lecture seule)
|
||
EditorGadget(#GdtTxtCommitInfo,
|
||
#InPad,
|
||
#PanelH - #InPad - 150,
|
||
#PanelW - 2*#InPad,
|
||
150,
|
||
#PB_Editor_ReadOnly)
|
||
|
||
; ============================================================
|
||
; Onglet 3 : .gitignore file
|
||
; ============================================================
|
||
AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-gitignore",".gitignore file"))
|
||
EditorGadget(#GdtTxtGitIgnore,#InPad, #InPad,#PanelW - 2*#InPad,#PanelH - 4*#InPad-#BtnH)
|
||
ButtonGadget(#GdtBtnSaveGitIgnore,#InPad,GadgetY(#GdtTxtGitIgnore)+GadgetHeight(#GdtTxtGitIgnore)+#InPad,100,#BtnH,T("GdtBtnSaveGitIgnore","Save File"))
|
||
; ============================================================
|
||
; Onglet 4 : Config
|
||
; ============================================================
|
||
AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-Config","Config"))
|
||
|
||
FrameGadget(#GdtFrmConfig, #InPad, #InPad, #PanelW - 2*#InPad, 170, T("GdtFrmConfig","Configuration Git"))
|
||
TextGadget(#GdtLblUserName, #InPad + 10, #InPad + 35, 90, 22, "user.name")
|
||
StringGadget(#GdtFieldUserName, #InPad + 110, #InPad + 33, #PanelW - 2*#InPad - 120, #RowH, "")
|
||
TextGadget(#GdtLblUserEmail, #InPad + 10, #InPad + 70, 90, 22, "user.email")
|
||
StringGadget(#GdtFieldUserEmail, #InPad + 110, #InPad + 68, #PanelW - 2*#InPad - 120, #RowH, "")
|
||
TextGadget(#GdtLblScope, #InPad + 10, #InPad + 105, 90, 22, T("GdtLblScope","Portée"))
|
||
ComboBoxGadget(#GdtSlctScope, #InPad + 110, #InPad + 103, 180, #RowH)
|
||
AddGadgetItem(#GdtSlctScope, -1, "Local")
|
||
AddGadgetItem(#GdtSlctScope, -1, "System")
|
||
AddGadgetItem(#GdtSlctScope, -1, "Global")
|
||
SetGadgetState(#GdtSlctScope, 0)
|
||
GetGitIdentity()
|
||
ButtonGadget(#GdtBtnSaveCfg, #PanelW - #InPad - 110, #InPad + 100, 110, #BtnH, T("GdtBtnSaveCfg","Enregistrer"))
|
||
|
||
; --- Fin des onglets ---
|
||
CloseGadgetList()
|
||
|
||
; ============================================================
|
||
; Zone d’aide (plus haute grâce à #PanelH réduit)
|
||
; ============================================================
|
||
Define helpY = #PanelY + #PanelH + 10
|
||
;FrameGadget(#GdtFrmHelp, #PanelX, helpY, #PanelW, #WinH - helpY - #PanelX, T("GdtFrmHelp","Aide"))
|
||
WebViewGadget(#GdtHelp, #PanelX + 10, helpY + 25, #PanelW - 20, #WinH - helpY - #PanelX - 35)
|
||
|
||
|
||
|
||
SetGadgetText(#GgtFieldRepo,main\GitCall\workdir)
|
||
CreateThread(@RefreshFileList(),0)
|
||
If main\info\isInit=#True
|
||
GetRemoteUrl("origin")
|
||
GetCommitHistory()
|
||
EndIf
|
||
|
||
GetBranchesList()
|
||
GetCommitHistory()
|
||
; =================== EVENT LOOP / BOUCLE ÉVÉNEMENTS ===================
|
||
Quit=#False
|
||
Repeat
|
||
Protected ev.i = WaitWindowEvent()
|
||
Select ev
|
||
Case #PB_Event_CloseWindow
|
||
Quit=#True
|
||
Case #PB_Event_Gadget
|
||
Select EventGadget()
|
||
Case #gdtPnl
|
||
If EventType()=#PB_EventType_Change
|
||
ReadGitIgnorefile()
|
||
Select GetGadgetState(#GdtPnl)
|
||
EndSelect
|
||
UpdateHelp(GitIgnoreHelpHTML)
|
||
;SetGadgetText(#GdtHelp, "file://" + #PB_Compiler_Home + "examples/sources/Data/WebView/webview.html")
|
||
|
||
EndIf
|
||
Case #GdtBtnInit
|
||
DoInit()
|
||
CreateThread(@RefreshFileList(),0)
|
||
Case #GdtBtnRefresh
|
||
CreateThread(@RefreshFileList(),0)
|
||
Case #GgtFieldRepo
|
||
If EventType()=#PB_EventType_Change
|
||
main\GitCall\workdir=GetGadgetText(#GgtFieldRepo)
|
||
EndIf
|
||
|
||
Case #GdtBtnBrowseRepo
|
||
Protected path.s=PathRequester("Select Folder",main\GitCall\workdir,WindowID(#WinMain))
|
||
If path<>"" And FileSize(path)=-2
|
||
main\GitCall\workdir=path
|
||
EndIf
|
||
Case #GdtFieldRemote
|
||
;If EventType()=#PB_EventType_LostFocus
|
||
; AddRemoteRepo(GetGadgetText(#GdtFieldRemote),"origin")
|
||
; EndIf
|
||
Case #GdtBtnPush
|
||
DoPush()
|
||
Case #GdtListStatus
|
||
Case #GdtBtnIgnore
|
||
ToggleGitIgnoreForSelection()
|
||
Case #GdtBtnSaveGitIgnore
|
||
Protected hf.i=CreateFile(#PB_Any,".gitignore")
|
||
If hf
|
||
WriteString(hf,GetGadgetText(#GdtTxtGitIgnore))
|
||
CloseFile(hf)
|
||
EndIf
|
||
|
||
Case #GdtBtnCommit
|
||
DoCommit()
|
||
Case #GdtFieldRemote
|
||
|
||
Case #GdtListHistory
|
||
If EventType() = #PB_EventType_Change
|
||
ShowSelectedCommitInfo()
|
||
EndIf
|
||
|
||
Case #GdtSlctBranch
|
||
Case #GdtSlctScope
|
||
GetGitIdentity()
|
||
Case #GdtBtnSaveCfg
|
||
SetGitIdentity()
|
||
|
||
|
||
EndSelect
|
||
EndSelect
|
||
Until Quit=#True
|
||
End
|
||
EndIf
|
||
EndProcedure
|
||
OpenGUI()
|
||
|
||
|
||
|
||
|
||
; IDE Options = PureBasic 6.21 (Windows - x64)
|
||
; CursorPosition = 1765
|
||
; FirstLine = 1728
|
||
; Folding = -------
|
||
; Optimizer
|
||
; EnableThread
|
||
; EnableXP
|
||
; DPIAware
|
||
; CompileSourceDirectory
|
||
; EnablePurifier |