2412 lines
88 KiB
Plaintext
2412 lines
88 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
|
||
importance.i
|
||
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)
|
||
Declare.i StatusImportance(status.s)
|
||
|
||
|
||
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
|
||
|
||
; ---- Helpers ---------------------------------------------------------------
|
||
|
||
; --- Importance par statut Gitpour le trie de
|
||
Procedure.i StatusImportance(status.s)
|
||
Protected x.s = Left(status, 1)
|
||
Protected y.s = Mid(status, 2, 1)
|
||
Protected score.i = 0
|
||
|
||
; Conflits d'abord
|
||
Select status
|
||
Case "DD","AU","UD","UA","DU","AA","UU"
|
||
ProcedureReturn 1000
|
||
EndSelect
|
||
If x = "U" Or y = "U" : ProcedureReturn 1000 : EndIf
|
||
|
||
; Cas simples
|
||
If status = "??" : ProcedureReturn 300 : EndIf ; Non suivis
|
||
If status = "!!" : ProcedureReturn 50 : EndIf ; Ignorés
|
||
If status = " " : ProcedureReturn 0 : EndIf ; Propres
|
||
|
||
; Index > Worktree
|
||
If x <> " " : score + 700 : EndIf
|
||
If y <> " " And y <> "?" And y <> "!" : score + 600 : EndIf
|
||
|
||
; Raffinement (X puis Y)
|
||
Select x
|
||
Case "D" : score + 80
|
||
Case "R" : score + 70
|
||
Case "A" : score + 60
|
||
Case "M" : score + 50
|
||
Case "C" : score + 40
|
||
Case "T" : score + 30
|
||
EndSelect
|
||
Select y
|
||
Case "D" : score + 40
|
||
Case "R" : score + 35
|
||
Case "A" : score + 30
|
||
Case "M" : score + 25
|
||
Case "C" : score + 20
|
||
Case "T" : score + 15
|
||
EndSelect
|
||
|
||
ProcedureReturn score
|
||
EndProcedure
|
||
|
||
Procedure.s _SupTrim(text.s)
|
||
If text = "" : ProcedureReturn "" : EndIf
|
||
|
||
Protected i.i = 1
|
||
Protected j.i = Len(text)
|
||
Protected c.l
|
||
|
||
; Trim gauche : avancer tant que <= 32
|
||
While i <= j
|
||
c = Asc(Mid(text, i, 1))
|
||
If c <= 32
|
||
i + 1
|
||
Else
|
||
Break
|
||
EndIf
|
||
Wend
|
||
|
||
; Trim droite : reculer tant que <= 32
|
||
While j >= i
|
||
c = Asc(Mid(text, j, 1))
|
||
If c <= 32
|
||
j - 1
|
||
Else
|
||
Break
|
||
EndIf
|
||
Wend
|
||
|
||
If j < i
|
||
ProcedureReturn ""
|
||
Else
|
||
ProcedureReturn Mid(text, i, j - i + 1)
|
||
EndIf
|
||
EndProcedure
|
||
|
||
Procedure.s _JoinPath(base$, sub$)
|
||
Protected out$ = Trim(base$)
|
||
If out$ = "" : ProcedureReturn sub$ : EndIf
|
||
If Right(out$, 1) <> "\" And Right(out$, 1) <> "/" : out$ + "/" : EndIf
|
||
ProcedureReturn out$ + sub$
|
||
EndProcedure
|
||
|
||
Procedure.b _RepoIsInitialized(workdir$)
|
||
ProcedureReturn Bool(FileSize(_JoinPath(workdir$, ".git")) = -2)
|
||
EndProcedure
|
||
|
||
Procedure.i _CountCheckedItems(listID)
|
||
Protected i, n = CountGadgetItems(listID), c
|
||
For i = 0 To n - 1
|
||
If GetGadgetItemState(listID, i) & #PB_ListIcon_Checked
|
||
c + 1
|
||
EndIf
|
||
Next
|
||
ProcedureReturn c
|
||
EndProcedure
|
||
|
||
Procedure.i _CountSelectedItems(listID)
|
||
Protected i, n = CountGadgetItems(listID), c
|
||
For i = 0 To n - 1
|
||
If GetGadgetItemState(listID, i) & #PB_ListIcon_Selected
|
||
c + 1
|
||
EndIf
|
||
Next
|
||
ProcedureReturn c
|
||
EndProcedure
|
||
|
||
Procedure.b _HasRemote()
|
||
ProcedureReturn Bool(Trim(GetGadgetText(#GdtFieldRemote)) <> "")
|
||
EndProcedure
|
||
|
||
Procedure Max(nb1, nb2)
|
||
If nb1 > nb2
|
||
Result = nb1
|
||
Else
|
||
Result = nb2
|
||
EndIf
|
||
|
||
ProcedureReturn Result
|
||
EndProcedure
|
||
|
||
; ------------------------------------------------------------------
|
||
; Récupération récursive de TOUS les fichiers du workdir
|
||
; Remplit main\listFilesGit() avec un statut " " (clean/unmodified)
|
||
; ------------------------------------------------------------------
|
||
|
||
; --- Helper interne : scanne un dossier et alimente la liste
|
||
Procedure _ScanAndFillAllFiles(dir$, root$)
|
||
Protected did.i = ExamineDirectory(#PB_Any, dir$, "*")
|
||
If did = 0 : ProcedureReturn : EndIf
|
||
|
||
While NextDirectoryEntry(did)
|
||
Protected name$ = DirectoryEntryName(did)
|
||
If name$ = "." Or name$ = ".." : Continue : EndIf
|
||
|
||
Protected full$ = dir$ + name$
|
||
Debug full$
|
||
If DirectoryEntryType(did) = #PB_DirectoryEntry_Directory
|
||
; Ignorer le dépôt interne
|
||
If LCase(name$) <> ".git"
|
||
If Right(full$, 1) <> #PS$ : full$ + #PS$ : EndIf
|
||
_ScanAndFillAllFiles(full$, root$)
|
||
EndIf
|
||
Else
|
||
; Fichier : on ajoute une ligne dans main\listFilesGit()
|
||
Protected rel$ = Mid(full$, Len(root$) + 1)
|
||
rel$ = ReplaceString(rel$, "\", "/") ; chemins normalisés
|
||
|
||
AddElement(main\listFilesGit())
|
||
main\listFilesGit()\name = rel$
|
||
main\listFilesGit()\status = " " ; 2 espaces = clean
|
||
main\listFilesGit()\indexStatus = " "
|
||
main\listFilesGit()\workingTreeStatus = " "
|
||
main\listFilesGit()\statusDescription = "Unmodified"
|
||
EndIf
|
||
Wend
|
||
|
||
FinishDirectory(did)
|
||
EndProcedure
|
||
|
||
; --- API publique : appelle ceci pour remplir la liste
|
||
Procedure.i FillAllFilesRecursively()
|
||
|
||
root$ = GetCurrentDirectory()
|
||
|
||
|
||
; Normalise avec un séparateur de fin
|
||
If Right(root$, 1) <> "\" And Right(root$, 1) <> "/" : root$ + "/" : EndIf
|
||
|
||
; On n'efface pas ici pour laisser le choix à l'appelant
|
||
_ScanAndFillAllFiles(root$, root$)
|
||
|
||
ProcedureReturn ListSize(main\listFilesGit())
|
||
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 " "
|
||
ProcedureReturn "Unmodified" ;Non modifié
|
||
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()
|
||
; Ne PAS vider la liste ici : on veut pouvoir mettre à jour des entrées existantes
|
||
; ClearList(main\listFilesGit()) ; <-- laissé intentionnellement commenté
|
||
|
||
; 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
|
||
status$ = Left(line$, 2)
|
||
index$ = Left(line$, 1)
|
||
worktree$ = Mid(line$, 2, 1)
|
||
name$ = Mid(line$, 4) ; chemin tel que renvoyé par Git
|
||
name$ = ReplaceString(name$, "\", "/") ; normalisation (par sécurité)
|
||
|
||
; ----- MODIF: chercher si l'entrée existe déjà -----
|
||
found.b = #False
|
||
ForEach main\listFilesGit()
|
||
If main\listFilesGit()\name = name$
|
||
found = #True
|
||
; Mise à jour uniquement
|
||
main\listFilesGit()\status = status$
|
||
main\listFilesGit()\indexStatus = index$
|
||
main\listFilesGit()\workingTreeStatus = worktree$
|
||
main\listFilesGit()\statusDescription = GetStatusDescription(status$)
|
||
Break
|
||
EndIf
|
||
Next
|
||
|
||
; Si non trouvée, on l'ajoute
|
||
If Not found
|
||
AddElement(main\listFilesGit())
|
||
main\listFilesGit()\name = name$
|
||
main\listFilesGit()\status = status$
|
||
main\listFilesGit()\indexStatus = index$
|
||
main\listFilesGit()\workingTreeStatus = worktree$
|
||
main\listFilesGit()\statusDescription = GetStatusDescription(status$)
|
||
EndIf
|
||
; ----- FIN MODIF -----
|
||
EndIf
|
||
EndIf
|
||
Next
|
||
|
||
Debug "Récupération des status Git réussie. " + Str(ListSize(main\listFilesGit())) + " fichiers (maj/ajout)."
|
||
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")
|
||
Url=_SupTrim(Url)
|
||
name=_SupTrim(name)
|
||
Protected add.b=#False
|
||
;Check if this remote already exists
|
||
main\Gitcall\args = "remote get-url "+name
|
||
If RunExe(@main\Gitcall) = 0
|
||
;if yes we remove it
|
||
If Url<>_SupTrim(main\Gitcall\output)
|
||
main\Gitcall\args = "remote remove "+name
|
||
RunExe(@main\Gitcall)
|
||
add=#True
|
||
EndIf
|
||
Else
|
||
add=#True
|
||
EndIf
|
||
; We add a new remote
|
||
If add=#True
|
||
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
|
||
ProcedureReturn #True
|
||
EndIf
|
||
EndProcedure
|
||
|
||
Procedure GetRemoteUrl(name.s="origin")
|
||
main\Gitcall\args = "remote get-url "+name
|
||
If RunExe(@main\Gitcall) = 0
|
||
SetGadgetText(#GdtFieldRemote,_SupTrim(main\GitCall\output))
|
||
Else
|
||
SetGadgetText(#GdtFieldRemote,"")
|
||
EndIf
|
||
EndProcedure
|
||
|
||
; =============================================================================
|
||
; Gestionnaire intelligent de pull avec résolution d'erreurs automatisée
|
||
; =============================================================================
|
||
|
||
Procedure.i SmartPull(remoteName.s = "origin", branchName.s = "")
|
||
; Pull avec 3 choix simples en cas de problème
|
||
; Retourne : 1=succès, 0=échec/annulé
|
||
|
||
; Branche par défaut = branche courante
|
||
If branchName = ""
|
||
main\GitCall\args = "branch --show-current"
|
||
If RunExe(@main\GitCall) = 0
|
||
branchName = _SupTrim(main\GitCall\output)
|
||
Else
|
||
branchName = "main"
|
||
EndIf
|
||
EndIf
|
||
|
||
; Tentative de pull standard
|
||
main\GitCall\args = "pull " + remoteName + " " + branchName
|
||
If RunExe(@main\GitCall) = 0
|
||
MessageRequester("Git Pull", "Pull réussi :" + #LF$ + main\GitCall\output, #PB_MessageRequester_Info)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
|
||
; En cas d'échec, proposer les 3 options
|
||
Protected choice.i = MessageRequester("Problème lors du pull",
|
||
"Le pull a échoué :" + #LF$ +
|
||
main\GitCall\errors + #LF$ + #LF$ +
|
||
"Que voulez-vous faire ?" + #LF$ + #LF$ +
|
||
"OUI = ÉCRASER tout en local (ATTENTION: perte définitive)" + #LF$ +
|
||
"NON = SAUVEGARDER vos modifications puis pull" + #LF$ +
|
||
"ANNULER = Annuler l'opération",
|
||
#PB_MessageRequester_YesNoCancel | #PB_MessageRequester_Warning)
|
||
|
||
Select choice
|
||
Case #PB_MessageRequester_Yes ; Option 1: ÉCRASER
|
||
; Reset hard = synchronisation forcée avec le remote
|
||
main\GitCall\args = "reset --hard " + remoteName + "/" + branchName
|
||
If RunExe(@main\GitCall) = 0
|
||
MessageRequester("Écrasement terminé",
|
||
"Dépôt synchronisé avec le remote." + #LF$ +
|
||
"Toutes vos modifications locales ont été supprimées.",
|
||
#PB_MessageRequester_Info)
|
||
ProcedureReturn 1
|
||
Else
|
||
MessageRequester("Erreur", "Impossible d'écraser :" + #LF$ + main\GitCall\errors, #PB_MessageRequester_Error)
|
||
EndIf
|
||
|
||
Case #PB_MessageRequester_No ; Option 2: STASH + PULL + POP
|
||
; Sauvegarder
|
||
main\GitCall\args = "stash push -m " + Chr(34) + "Sauvegarde avant pull " + FormatDate("%yyyy-%mm-%dd %hh:%ii", Date()) + Chr(34)
|
||
If RunExe(@main\GitCall) <> 0
|
||
MessageRequester("Erreur", "Impossible de sauvegarder :" + #LF$ + main\GitCall\errors, #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; Pull
|
||
main\GitCall\args = "pull " + remoteName + " " + branchName
|
||
If RunExe(@main\GitCall) <> 0
|
||
MessageRequester("Erreur", "Pull échoué :" + #LF$ + main\GitCall\errors, #PB_MessageRequester_Error)
|
||
; Restaurer le stash en cas d'échec
|
||
main\GitCall\args = "stash pop"
|
||
RunExe(@main\GitCall)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; Restaurer
|
||
main\GitCall\args = "stash pop"
|
||
If RunExe(@main\GitCall) = 0
|
||
MessageRequester("Pull terminé", "Pull réussi, vos modifications ont été restaurées.", #PB_MessageRequester_Info)
|
||
Else
|
||
If FindString(main\GitCall\errors, "CONFLICT") > 0
|
||
MessageRequester("Conflits à résoudre",
|
||
"Pull réussi mais conflits détectés." + #LF$ +
|
||
"Résolvez les conflits puis commitez.",
|
||
#PB_MessageRequester_Warning)
|
||
Else
|
||
MessageRequester("Attention",
|
||
"Pull réussi mais problème de restauration." + #LF$ +
|
||
"Vos modifications sont sauvegardées dans le stash.",
|
||
#PB_MessageRequester_Warning)
|
||
EndIf
|
||
EndIf
|
||
|
||
ProcedureReturn 1
|
||
|
||
Case #PB_MessageRequester_Cancel ; Option 3: ANNULER
|
||
MessageRequester("Pull annulé", "Aucune modification effectuée.", #PB_MessageRequester_Info)
|
||
|
||
EndSelect
|
||
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
; TEST TITAN
|
||
|
||
; Fonction pour remplacer votre DoPull() actuelle
|
||
Procedure DoSmartPull()
|
||
If Trim(GetGadgetText(#GdtFieldRemote)) = ""
|
||
MessageRequester("Git Pull", "Vous devez configurer un remote.", #PB_MessageRequester_Error)
|
||
ProcedureReturn #False
|
||
EndIf
|
||
|
||
AddRemoteRepo(Trim(GetGadgetText(#GdtFieldRemote)))
|
||
|
||
Protected result.i = SmartPull("origin", GetGadgetText(#GdtSlctBranch))
|
||
|
||
If result > 0
|
||
CreateThread(@RefreshFileList(), 0) ; Refresh interface
|
||
EndIf
|
||
|
||
ProcedureReturn Bool(result > 0)
|
||
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 DoPull()
|
||
If Trim(GetGadgetText(#GdtFieldRemote))=""
|
||
MessageRequester("Git Pull", "Échec: " + #LF$ + "You Must tu have a remote", #PB_MessageRequester_Error)
|
||
EndIf
|
||
AddRemoteRepo(Trim(GetGadgetText(#GdtFieldRemote)))
|
||
main\Gitcall\args = "pull origin "+GetGadgetText(#GdtSlctBranch)+" --allow-unrelated-histories"
|
||
If RunExe(@main\Gitcall) = 0
|
||
MessageRequester("Git Pull", "OK:" + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info)
|
||
ProcedureReturn #True
|
||
Else
|
||
MessageRequester("Git Pull", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error)
|
||
ProcedureReturn #False
|
||
EndIf
|
||
EndProcedure
|
||
|
||
Procedure.s ExtractRepoNameFromUrl(url.s)
|
||
; helper for Doclone
|
||
; Extraire le nom du dépôt depuis l'URL
|
||
Protected repoName.s, parts.i, i.i
|
||
|
||
; Supprimer .git à la fin si présent
|
||
If Right(url, 4) = ".git"
|
||
url = Left(url, Len(url) - 4)
|
||
EndIf
|
||
|
||
; Remplacer les \ par des / pour uniformiser
|
||
url = ReplaceString(url, "\", "/")
|
||
|
||
; Compter le nombre de parties séparées par /
|
||
parts = CountString(url, "/")
|
||
|
||
; Extraire la dernière partie
|
||
If parts > 0
|
||
repoName = StringField(url, parts + 1, "/")
|
||
Else
|
||
repoName = url
|
||
EndIf
|
||
|
||
ProcedureReturn repoName
|
||
EndProcedure
|
||
|
||
Procedure.s GetParentPath(path.s)
|
||
Protected parentPath.s
|
||
|
||
; Supprimer le séparateur final si présent
|
||
If Right(Path, 1) = "\" Or Right(Path, 1) = "/"
|
||
Path = Left(Path, Len(Path) - 1)
|
||
EndIf
|
||
|
||
; Utiliser GetPathPart() qui retourne le chemin sans le fichier/dossier final
|
||
parentPath = GetPathPart(path)
|
||
|
||
ProcedureReturn parentPath
|
||
EndProcedure
|
||
|
||
Procedure DoClone()
|
||
Protected remoteUrl.s, repoName.s, targetFolder.s, currentDir.s, choice.i
|
||
|
||
remoteUrl = Trim(GetGadgetText(#GdtFieldRemote))
|
||
If remoteUrl = ""
|
||
MessageRequester("Git Clone", "Échec: " + #LF$ + "You Must have a remote URL", #PB_MessageRequester_Error)
|
||
ProcedureReturn #False
|
||
EndIf
|
||
|
||
; Extraire le nom du dépôt depuis l'URL
|
||
Debug "remoteUrl="+remoteUrl
|
||
repoName = ExtractRepoNameFromUrl(remoteUrl)
|
||
Debug "repoName="+repoName
|
||
; Obtenir le chemin complet du répertoire courant
|
||
currentDir = GetCurrentDirectory()
|
||
|
||
; Demander à l'utilisateur où cloner
|
||
choice = MessageRequester("Git Clone - Destination",
|
||
"Où voulez-vous cloner le dépôt ?" + #LF$ + #LF$ +
|
||
"OUI: Créer un répertoire '" + repoName + "' dans le répertoire courant" + #LF$ +
|
||
" → " + currentDir + repoName + "/" + #LF$ + #LF$ +
|
||
"NON: Cloner directement dans le répertoire courant" + #LF$ +
|
||
" → " + currentDir + #LF$ + #LF$ +
|
||
"ANNULER: Annuler l'opération",
|
||
#PB_MessageRequester_YesNoCancel)
|
||
|
||
Select choice
|
||
Case #PB_MessageRequester_Yes
|
||
; Créer un nouveau répertoire avec le nom du dépôt
|
||
targetFolder = repoName
|
||
main\Gitcall\args = "clone " + remoteUrl + " " + targetFolder
|
||
|
||
Case #PB_MessageRequester_No
|
||
; Cloner directement dans le répertoire courant (doit être vide)
|
||
SetCurrentDirectory(GetParentPath(GetGadgetText(#GgtFieldRepo)))
|
||
targetFolder = "."
|
||
main\Gitcall\args = "clone " + remoteUrl + " " + targetFolder
|
||
|
||
Case #PB_MessageRequester_Cancel
|
||
; Annuler l'opération
|
||
ProcedureReturn #False
|
||
|
||
EndSelect
|
||
|
||
; Exécuter la commande git clone
|
||
If RunExe(@main\Gitcall) = 0
|
||
If targetFolder = "."
|
||
MessageRequester("Git Clone", "Succès:" + #LF$ +
|
||
"Dépôt cloné dans le répertoire courant" + #LF$ + #LF$ +
|
||
main\Gitcall\output, #PB_MessageRequester_Info)
|
||
Else
|
||
MessageRequester("Git Clone", "Succès:" + #LF$ +
|
||
"Dépôt cloné dans le répertoire: " + targetFolder + "/" + #LF$ + #LF$ +
|
||
main\Gitcall\output, #PB_MessageRequester_Info)
|
||
EndIf
|
||
AddRemoteRepo(remoteUrl)
|
||
SetCurrentDirectory(GetGadgetText(#GgtFieldRepo))
|
||
CreateThread(@RefreshFileList(),0)
|
||
ProcedureReturn #True
|
||
Else
|
||
MessageRequester("Git Clone", "É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 + " -s --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 -a"
|
||
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
|
||
FillAllFilesRecursively()
|
||
;DoLsFilesByType("D")
|
||
;DoLsFilesByType("O")
|
||
;DoLsFilesByType("I")
|
||
;DoStatus()
|
||
|
||
GetGitStatus()
|
||
|
||
ForEach main\listFilesGit()
|
||
main\listFilesGit()\importance = StatusImportance(main\listFilesGit()\status)
|
||
Next
|
||
|
||
; --- Tri par importance (du plus important au moins important)
|
||
SortStructuredList(main\listFilesGit(), #PB_Sort_Descending, OffsetOf(listFilesGit\importance), #PB_Integer)
|
||
|
||
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)
|
||
GetBranchesList()
|
||
GetRemoteUrl()
|
||
GetCommitHistory()
|
||
EndProcedure
|
||
|
||
Procedure UpdateHelp(txt.s)
|
||
SetGadgetItemText(#GdtHelp,#PB_Web_HtmlCode,txt.s)
|
||
EndProcedure
|
||
|
||
Procedure _SetTooltips()
|
||
; Dépôt
|
||
GadgetToolTip(#GdtBtnInit, T("tip.init", "Initialiser un dépôt Git ici"))
|
||
GadgetToolTip(#GdtBtnRefresh, T("tip.refresh", "Rafraîchir la liste des fichiers et l’état Git"))
|
||
GadgetToolTip(#GgtFieldRepo, T("tip.repo.path", "Chemin du dossier projet (workdir)"))
|
||
|
||
; Remote / Branch
|
||
GadgetToolTip(#GdtFieldRemote, T("tip.remote", "URL du remote (ex.: https://... ou git@host:org/repo.git)"))
|
||
GadgetToolTip(#GdtSlctBranch, T("tip.branch.select","Choisir la branche active"))
|
||
GadgetToolTip(#GdtBtnNewBranch,T("tip.branch.new", "Créer une nouvelle branche"))
|
||
GadgetToolTip(#GdtBtnClone, T("tip.clone", "Cloner depuis l’URL remote"))
|
||
GadgetToolTip(#GdtBtnPull, T("tip.pull", "Récupérer et fusionner depuis le remote"))
|
||
GadgetToolTip(#GdtBtnPush, T("tip.push", "Envoyer vos commits sur le remote"))
|
||
|
||
; Fichiers & actions locales
|
||
GadgetToolTip(#GdtListStatus, T("tip.files.list", "Fichiers du dépôt : cochez pour préparer un commit, sélectionnez pour agir"))
|
||
GadgetToolTip(#GdtBtnRestore, T("tip.restore", "Restaurer les fichiers sélectionnés"))
|
||
GadgetToolTip(#GdtBtnRename, T("tip.rename", "Renommer les fichiers sélectionnés"))
|
||
GadgetToolTip(#GdtBtnDelete, T("tip.delete", "Supprimer les fichiers sélectionnés"))
|
||
GadgetToolTip(#GdtBtnIgnore, T("tip.ignore", "Ajouter/retirer les fichiers sélectionnés dans .gitignore"))
|
||
|
||
; Commit
|
||
GadgetToolTip(#GdtFieldMessage,T("tip.message", "Message du commit"))
|
||
GadgetToolTip(#GdtBtnCommit, T("tip.commit", "Committer tous les fichiers cochés avec le message"))
|
||
|
||
; History
|
||
GadgetToolTip(#GdtListHistory, T("tip.history", "Historique des commits"))
|
||
GadgetToolTip(#GdtBtnRestoreCommit,T("tip.history.restore","Restaurer/checkout le commit sélectionné"))
|
||
|
||
; .gitignore
|
||
GadgetToolTip(#GdtTxtGitIgnore, T("tip.gitignore.edit","Éditeur du .gitignore"))
|
||
GadgetToolTip(#GdtBtnSaveGitIgnore,T("tip.gitignore.save","Sauvegarder le .gitignore"))
|
||
|
||
; Config
|
||
GadgetToolTip(#GdtFieldUserName, T("tip.cfg.username","Nom d’utilisateur Git (user.name)"))
|
||
GadgetToolTip(#GdtFieldUserEmail,T("tip.cfg.useremail","Email Git (user.email)"))
|
||
GadgetToolTip(#GdtSlctScope, T("tip.cfg.scope", "Portée de la configuration (Local/Global/System)"))
|
||
GadgetToolTip(#GdtBtnSaveCfg, T("tip.cfg.save", "Enregistrer la configuration Git"))
|
||
EndProcedure
|
||
|
||
; ---- Procédure principale ---------------------------------------------------
|
||
|
||
Procedure InitGadget()
|
||
Protected hasGit.b = main\info\isGit
|
||
Protected workdir$ = Trim(GetGadgetText(#GgtFieldRepo))
|
||
Protected repoDirOK.b = Bool(workdir$ <> "" And FileSize(workdir$) = -2)
|
||
Protected isInit.b = Bool(repoDirOK And _RepoIsInitialized(workdir$))
|
||
Protected hasRemote.b = _HasRemote()
|
||
Protected selCount.i = _CountSelectedItems(#GdtListStatus)
|
||
Protected checkedCount.i = _CountCheckedItems(#GdtListStatus)
|
||
Protected haveHistorySel.b = Bool(GetGadgetState(#GdtListHistory) >= 0)
|
||
Protected userName$ = Trim(GetGadgetText(#GdtFieldUserName))
|
||
Protected userEmail$ = Trim(GetGadgetText(#GdtFieldUserEmail))
|
||
|
||
; Mémoriser l’état init global
|
||
main\info\isInit = isInit
|
||
|
||
; ---- Zone Dépôt ----------------------------------------------------------
|
||
DisableGadget(#GdtBtnInit, Bool(Not (hasGit And repoDirOK And Not isInit)))
|
||
DisableGadget(#GdtBtnRefresh, Bool(Not (hasGit And repoDirOK)))
|
||
DisableGadget(#GgtFieldRepo, Bool(Not hasGit))
|
||
|
||
; ---- Remote / Branch -----------------------------------------------------
|
||
DisableGadget(#GdtFieldRemote, Bool(Not (hasGit And repoDirOK)))
|
||
DisableGadget(#GdtSlctBranch, Bool(Not (hasGit And isInit)))
|
||
DisableGadget(#GdtBtnNewBranch,Bool(Not (hasGit And isInit)))
|
||
|
||
; Règle demandée : Clone/Pull/Push uniquement si une remote est définie
|
||
DisableGadget(#GdtBtnClone, Bool(Not (hasGit And repoDirOK And hasRemote)))
|
||
DisableGadget(#GdtBtnPull, Bool(Not (hasGit And isInit And hasRemote)))
|
||
DisableGadget(#GdtBtnPush, Bool(Not (hasGit And isInit And hasRemote)))
|
||
|
||
; ---- Fichiers & actions locales -----------------------------------------
|
||
; Restaurer / Renommer / Supprimer / Ignorer : uniquement si des fichiers sont sélectionnés
|
||
DisableGadget(#GdtBtnRestore, Bool(selCount = 0))
|
||
DisableGadget(#GdtBtnRename, Bool(selCount = 0))
|
||
DisableGadget(#GdtBtnDelete, Bool(selCount = 0))
|
||
DisableGadget(#GdtBtnIgnore, Bool(selCount = 0))
|
||
|
||
; Commit : uniquement si des fichiers sont cochés
|
||
DisableGadget(#GdtBtnCommit, Bool(Not (hasGit And isInit And checkedCount > 0)))
|
||
|
||
; ---- Onglet History ------------------------------------------------------
|
||
DisableGadget(#GdtBtnRestoreCommit, Bool(Not (hasGit And isInit And haveHistorySel)))
|
||
|
||
; ---- .gitignore ----------------------------------------------------------
|
||
; On autorise la sauvegarde si le repo est initialisé (fichier créé si besoin)
|
||
DisableGadget(#GdtBtnSaveGitIgnore, Bool(Not (hasGit And isInit)))
|
||
|
||
; ---- Config --------------------------------------------------------------
|
||
; Le bouton "Enregistrer" actif si au moins un champ non vide
|
||
DisableGadget(#GdtBtnSaveCfg, Bool(userName$ = "" And userEmail$ = ""))
|
||
|
||
|
||
EndProcedure
|
||
|
||
Macro RightGadget(GDT)
|
||
GadgetX(GDT)+GadgetWidth(GDT)
|
||
EndMacro
|
||
|
||
Macro DownGadget(GDT)
|
||
GadgetY(GDT)+GadgetHeight(GDT)
|
||
EndMacro
|
||
|
||
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 = 720 ; plus haut pour la zone d’aide
|
||
#PanelW = 920
|
||
#PanelH = 500
|
||
#InPad = 10 ; marge interne au Panel
|
||
#InFrmY=30
|
||
#InMargin = 10 ; espace vertical entre frames
|
||
;#RowH = 26
|
||
#BtnH = 30
|
||
#BtnW = 95
|
||
|
||
If OpenWindow(#WinMain, 0, 0, #WinW, #WinH, "Git helpmate", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
|
||
|
||
PanelGadget(#GdtPnl, #InPad,#InPad, #PanelW, #PanelH)
|
||
|
||
; ============================================================
|
||
; Onglet 1 : Dépôt
|
||
; ============================================================
|
||
AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-Repo","Dépôt"))
|
||
Protected PanelH.l=WindowHeight(#WinMain)- 2*#InPad
|
||
|
||
; ---- Cadre "Dépôt local" ----
|
||
Protected Width.l=#PanelW - #InPad*2
|
||
FrameGadget(#GdtFrmLocal, #InPad, #InPad, Width, #InFrmY+#BtnH*2+#InMargin*2, T("GdtFrmLocal","Dépôt local"),#PB_Frame_Container)
|
||
TextGadget(#GdtLblRepo, #InPad, #InFrmY, 60, #BtnH , T("GdtLblRepo","Dépôt :"))
|
||
width.l=GadgetWidth(#GdtFrmLocal)-#InPad*3-GadgetWidth(#GdtLblRepo)-#BtnW
|
||
StringGadget(#GgtFieldRepo, RightGadget(#GdtLblRepo), #InFrmY, width, #BtnH, repoDir$)
|
||
ButtonGadget(#GdtBtnBrowseRepo, RightGadget(#GgtFieldRepo)+#InPad, #InFrmY, #BtnW, #BtnH, T("GdtBtnBrowseRepo","Parcourir…"))
|
||
ButtonGadget(#GdtBtnInit, #InPad , DownGadget(#GdtBtnBrowseRepo)+ #InMargin,#BtnW, #BtnH, T("GdtBtnInit","Init Dépôt"))
|
||
ButtonGadget(#GdtBtnRefresh, RightGadget(#GdtBtnInit)+#InPad,GadgetY(#GdtBtnInit), #BtnW, #BtnH, T("GdtBtnRefresh","Rafraîchir"))
|
||
CloseGadgetList()
|
||
; ---- Cadre "Distant" ----
|
||
Define yRemote = DownGadget(#GdtFrmLocal)+ #InPad
|
||
FrameGadget(#GdtFrmRemote, #InPad, yRemote, #PanelW - 2*#InPad, #InFrmY+#BtnH*2+#InMargin*2, T("GdtFrmRemote","Distant (remote / branche)"),#PB_Frame_Container)
|
||
TextGadget(#GdtLblRemote, #InPad , #InFrmY, 60, #BtnH, T("GdtLblRemote","Remote :"))
|
||
StringGadget(#GdtFieldRemote, RightGadget(#GdtLblRemote)+#InMargin, #InFrmY, 420, #BtnH, "")
|
||
TextGadget(#GdtLblBranch, RightGadget(#GdtFieldRemote)+ #InMargin, #InFrmY, 70, #BtnH, T("GdtLblBranch","Branche :"))
|
||
ComboBoxGadget(#GdtSlctBranch, RightGadget(#GdtLblBranch), #InFrmY, 220, #BtnH, #PB_ComboBox_Editable)
|
||
ButtonGadget(#GdtBtnNewBranch, RightGadget(#GdtSlctBranch), #InFrmY, 130, #BtnH, T("GdtBtnNewBranch","New branch"))
|
||
ButtonGadget(#GdtBtnClone, #InPad,DownGadget(#GdtLblRemote)+#InMargin, #BtnW,#BtnH, T("GdtBtnClone","Clone"))
|
||
ButtonGadget(#GdtBtnPull, RightGadget(#GdtBtnClone)+#InPad,GadgetY(#GdtBtnClone), #BtnW, #BtnH, T("GdtBtnPull","Pull"))
|
||
ButtonGadget(#GdtBtnPush, RightGadget(#GdtBtnPull)+#InPad,GadgetY(#GdtBtnClone), #BtnW, #BtnH, T("GdtBtnPush","Push"))
|
||
CloseGadgetList()
|
||
; ---- Cadre "Fichiers & modifications" ----
|
||
Define yFiles = DownGadget(#GdtFrmRemote)+ #InPad
|
||
Define hFiles = #PanelH - yFiles - #InPad
|
||
FrameGadget(#GdtFrmFiles, #InPad, yFiles, #PanelW - 2*#InPad, hFiles, T("GdtFrmFiles","Fichiers & modifications"),#PB_Frame_Container)
|
||
ListIconGadget(#GdtListStatus, #InPad, #InFrmY, GadgetWidth(#GdtFrmFiles)-2*#InPad, 120, 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 = DownGadget(#GdtListStatus)+#InMargin
|
||
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, #BtnH, "")
|
||
ButtonGadget(#GdtBtnCommit, #PanelW - #InPad - 100, yMsg - 2, 100, #BtnH, T("GdtBtnCommit","Commit"))
|
||
CloseGadgetList()
|
||
|
||
;test
|
||
; ============================================================
|
||
; 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, #BtnH, "")
|
||
TextGadget(#GdtLblUserEmail, #InPad + 10, #InPad + 70, 90, 22, "user.email")
|
||
StringGadget(#GdtFieldUserEmail, #InPad + 110, #InPad + 68, #PanelW - 2*#InPad - 120, #BtnH, "")
|
||
TextGadget(#GdtLblScope, #InPad + 10, #InPad + 105, 90, 22, T("GdtLblScope","Portée"))
|
||
ComboBoxGadget(#GdtSlctScope, #InPad + 110, #InPad + 103, 180, #BtnH)
|
||
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()
|
||
|
||
; ============================================================
|
||
; Aide à droite du Panel (sans splitter)
|
||
; ============================================================
|
||
; On garde #PanelW / #PanelH tels quels
|
||
|
||
Define helpGap = 10 ; espace entre panel et aide
|
||
Define helpX = #InPad + #PanelW + helpGap ; à droite du panel
|
||
Define helpY = #InPad
|
||
Define helpW = WindowWidth(#WinMain)-GadgetWidth(#gdtPnl)- #InPad*4 ; reste de largeur jusqu'à la marge droite
|
||
Define helpH = #PanelH ; même hauteur que le panel
|
||
|
||
; (Optionnel) borne minimale si besoin
|
||
If helpW < 100 : helpW = 100 : EndIf
|
||
|
||
; Soit en direct :
|
||
WebViewGadget(#GdtHelp, helpX, helpY, helpW, helpH)
|
||
|
||
; — ou, si tu veux un cadre :
|
||
; FrameGadget(#GdtFrmHelp, helpX, helpY, helpW, helpH, T("GdtFrmHelp","Aide"))
|
||
; WebViewGadget(#GdtHelp, helpX + 10, helpY + 25, helpW - 20, helpH - 35)
|
||
|
||
|
||
SetGadgetText(#GgtFieldRepo,main\GitCall\workdir)
|
||
CreateThread(@RefreshFileList(),0)
|
||
InitGadget()
|
||
_SetTooltips()
|
||
If FileSize(".git")=-2:main\info\isInit=#True:EndIf
|
||
;If main\info\isInit=#True
|
||
; GetRemoteUrl("origin")
|
||
; GetCommitHistory()
|
||
;;EndIf
|
||
|
||
GetBranchesList()
|
||
|
||
; =================== 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
|
||
Debug "Click>#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
|
||
Debug "Click>#GdtBtnInit"
|
||
DoInit()
|
||
CreateThread(@RefreshFileList(),0)
|
||
Case #GdtBtnRefresh
|
||
Debug "Click>#GdtBtnRefresh"
|
||
CreateThread(@RefreshFileList(),0)
|
||
Case #GgtFieldRepo
|
||
|
||
If EventType()=#PB_EventType_LostFocus
|
||
Debug "Click>#GgtFieldRepo"
|
||
main\GitCall\workdir=GetGadgetText(#GgtFieldRepo)
|
||
CreateThread(@RefreshFileList(),0)
|
||
EndIf
|
||
|
||
Case #GdtBtnBrowseRepo
|
||
Debug "Click>#GdtBtnBrowseRepo"
|
||
Protected path.s=PathRequester("Select Folder",main\GitCall\workdir,WindowID(#WinMain))
|
||
If path<>"" And FileSize(path)=-2
|
||
main\GitCall\workdir=path
|
||
SetCurrentDirectory(main\GitCall\workdir)
|
||
SetGadgetText(#GgtFieldRepo,main\GitCall\workdir)
|
||
CreateThread(@RefreshFileList(),0)
|
||
EndIf
|
||
Case #GdtFieldRemote
|
||
;If EventType()=#PB_EventType_LostFocus
|
||
; AddRemoteRepo(GetGadgetText(#GdtFieldRemote),"origin")
|
||
; EndIf
|
||
|
||
Case #GdtBtnClone
|
||
Debug "Click>#GdtBtnClone"
|
||
DoClone()
|
||
Case #GdtBtnPush
|
||
Debug "Click>#GdtBtnPush"
|
||
DoPush()
|
||
Case #GdtBtnPull
|
||
Debug "Click>#GdtBtnPull"
|
||
DoPull()
|
||
Case #GdtListStatus
|
||
Debug "Click>#GdtListStatus"
|
||
Case #GdtBtnIgnore
|
||
Debug "Click>#GdtBtnIgnore"
|
||
ToggleGitIgnoreForSelection()
|
||
Case #GdtBtnSaveGitIgnore
|
||
Debug "Click>#GdtBtnSaveGitIgnore"
|
||
Protected hf.i=CreateFile(#PB_Any,".gitignore")
|
||
If hf
|
||
WriteString(hf,GetGadgetText(#GdtTxtGitIgnore))
|
||
CloseFile(hf)
|
||
EndIf
|
||
|
||
Case #GdtBtnCommit
|
||
Debug "Click>#GdtBtnCommit"
|
||
DoCommit()
|
||
SetGadgetText(#GdtFieldMessage,"")
|
||
CreateThread(@RefreshFileList(),0)
|
||
Case #GdtFieldRemote
|
||
Debug "Click>#GdtFieldRemote"
|
||
|
||
Case #GdtListHistory
|
||
Debug "Click>#GdtListHistory"
|
||
If EventType() = #PB_EventType_Change
|
||
ShowSelectedCommitInfo()
|
||
EndIf
|
||
|
||
Case #GdtSlctBranch
|
||
Debug "Click>#GdtSlctBranch"
|
||
Case #GdtSlctScope
|
||
Debug "Click>#GdtSlctScope"
|
||
GetGitIdentity()
|
||
Case #GdtBtnSaveCfg
|
||
Debug "Click>#GdtBtnSaveCfg"
|
||
SetGitIdentity()
|
||
|
||
|
||
EndSelect
|
||
InitGadget()
|
||
EndSelect
|
||
Until Quit=#True
|
||
End
|
||
EndIf
|
||
EndProcedure
|
||
OpenGUI()
|
||
|
||
|
||
|
||
|
||
; IDE Options = PureBasic 6.21 (Windows - x64)
|
||
; CursorPosition = 1440
|
||
; FirstLine = 1415
|
||
; Folding = ----------
|
||
; Optimizer
|
||
; EnableThread
|
||
; EnableXP
|
||
; DPIAware
|
||
; CompileSourceDirectory
|
||
; EnablePurifier |