v0.7.5 - Miroir depot git et detection fichiers non suivis

Changements :
- Detection des fichiers non suivis (untracked) dans chaque depot
- Affichage "X fichier(s) en trop" dans le statut
- Popup de confirmation listant les fichiers avant suppression (git clean -fd)
- Suppression auto des fichiers en trop via "Tout mettre a jour"
- Verification du depot distant via git ls-remote avant de proposer le clone
- Affichage "Depot introuvable" si l'URL pointe vers un repo inexistant

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 10:34:40 +01:00
parent db57cfacaf
commit 55663e3a19
5 changed files with 94 additions and 16 deletions

Binary file not shown.

41
git.go
View File

@@ -16,16 +16,18 @@ import (
)
type RepoResult struct {
Name string
Path string
URL string
Pending bool
UpToDate bool
Offline bool
NeedsClone bool
Error string
NewCommits int
LocalChanges int
Name string
Path string
URL string
Pending bool
UpToDate bool
Offline bool
NeedsClone bool
Error string
NewCommits int
LocalChanges int
UntrackedFiles int
UntrackedList []string // liste des fichiers non suivis
}
func runGit(args []string, cwd string, timeout time.Duration) (code int, stdout string, stderr string) {
@@ -115,10 +117,17 @@ func checkRepo(cfg RepoConfig) RepoResult {
_, status, _ := runGit([]string{"status", "--porcelain"}, local, 5*time.Second)
if status != "" {
res.LocalChanges = len(strings.Split(strings.TrimSpace(status), "\n"))
for _, line := range strings.Split(strings.TrimSpace(status), "\n") {
if strings.HasPrefix(line, "?? ") {
res.UntrackedFiles++
res.UntrackedList = append(res.UntrackedList, strings.TrimPrefix(line, "?? "))
} else {
res.LocalChanges++
}
}
}
res.UpToDate = res.NewCommits == 0 && res.LocalChanges == 0
res.UpToDate = res.NewCommits == 0 && res.LocalChanges == 0 && res.UntrackedFiles == 0
return res
}
@@ -154,6 +163,14 @@ func doCheckout(res RepoResult) error {
return nil
}
func doClean(res RepoResult) error {
code, _, stderr := runGit([]string{"clean", "-fd"}, res.Path, 60*time.Second)
if code != 0 {
return fmt.Errorf("%s", stderr)
}
return nil
}
// ── Progression Git ───────────────────────────────────────────────────────────
// ProgressInfo contient l'état de progression d'une opération git.

65
gui.go
View File

@@ -46,6 +46,12 @@ func (it *RepoItem) statusText() string {
}
msg += fmt.Sprintf("%d modif. locale(s)", r.LocalChanges)
}
if r.UntrackedFiles > 0 {
if msg != "" {
msg += ", "
}
msg += fmt.Sprintf("%d fichier(s) en trop", r.UntrackedFiles)
}
if msg == "" {
return "À jour"
}
@@ -195,7 +201,7 @@ func (m *RepoModel) hasUpdates() bool {
defer m.mu.RUnlock()
for _, it := range m.items {
r := it.result
if !r.Pending && r.Error == "" && !r.Offline && (r.NewCommits > 0 || r.LocalChanges > 0 || r.NeedsClone) {
if !r.Pending && r.Error == "" && !r.Offline && (r.NewCommits > 0 || r.LocalChanges > 0 || r.UntrackedFiles > 0 || r.NeedsClone) {
return true
}
}
@@ -430,6 +436,12 @@ func logLineForResult(r RepoResult) string {
}
msg += fmt.Sprintf("%d modif. locale(s)", r.LocalChanges)
}
if r.UntrackedFiles > 0 {
if msg != "" {
msg += ", "
}
msg += fmt.Sprintf("%d fichier(s) en trop", r.UntrackedFiles)
}
return msg
}
@@ -450,6 +462,41 @@ func (a *App) recheckOne(idx int) {
}()
}
// proposeClean affiche un popup listant les fichiers non suivis et propose de les supprimer.
func (a *App) proposeClean(idx int, res RepoResult) {
// Construire la liste des fichiers (max 30 affichés)
list := ""
for i, f := range res.UntrackedList {
if i >= 30 {
list += fmt.Sprintf("\n... et %d autre(s)", len(res.UntrackedList)-30)
break
}
list += "\n - " + f
}
msg := fmt.Sprintf("[%s] %d fichier(s) non suivi(s) détecté(s) :%s\n\nSupprimer ces fichiers ?",
res.Name, res.UntrackedFiles, list)
ans := walk.MsgBox(a.mw, "Fichiers en trop", msg, walk.MsgBoxYesNo|walk.MsgBoxIconQuestion)
if ans == walk.DlgCmdYes {
a.appendLog(fmt.Sprintf("[%s] Nettoyage de %d fichier(s)...", res.Name, res.UntrackedFiles))
go func() {
err := doClean(res)
a.mw.Synchronize(func() {
if err != nil {
a.appendLog(fmt.Sprintf("[%s] Erreur nettoyage: %v", res.Name, err))
logError(fmt.Sprintf("[%s] clean: %v", res.Name, err))
} else {
a.appendLog(fmt.Sprintf("[%s] %d fichier(s) supprimé(s)", res.Name, res.UntrackedFiles))
logInfo(fmt.Sprintf("[%s] %d fichier(s) supprimé(s)", res.Name, res.UntrackedFiles))
}
a.recheckOne(idx)
})
}()
} else {
a.recheckOne(idx)
}
}
// ── Progression ───────────────────────────────────────────────────────────────
// makeProgressCB crée un callback de progression pour la ligne row du tableau.
@@ -487,7 +534,7 @@ func (a *App) onSelectionChanged() {
if res.NeedsClone {
a.btnAction.SetText("Cloner")
a.btnAction.SetEnabled(true)
} else if res.NewCommits > 0 || res.LocalChanges > 0 {
} else if res.NewCommits > 0 || res.LocalChanges > 0 || res.UntrackedFiles > 0 {
a.btnAction.SetText("Mettre à jour")
a.btnAction.SetEnabled(true)
} else {
@@ -503,6 +550,12 @@ func (a *App) doAction() {
}
cfg := a.reposConfig[idx]
// Si uniquement des fichiers en trop, proposer directement le nettoyage
if res.UntrackedFiles > 0 && res.NewCommits == 0 && res.LocalChanges == 0 && !res.NeedsClone {
a.proposeClean(idx, res)
return
}
a.btnAction.SetEnabled(false)
a.appendLog(fmt.Sprintf("[%s] Mise à jour en cours...", res.Name))
@@ -529,6 +582,11 @@ func (a *App) doAction() {
a.appendLog(fmt.Sprintf("[%s] Mise à jour OK", res.Name))
logInfo(fmt.Sprintf("[%s] Mise à jour OK", res.Name))
}
// Si des fichiers en trop après la mise à jour, proposer le nettoyage
if res.UntrackedFiles > 0 {
a.proposeClean(idx, res)
return
}
// Re-vérifier uniquement ce dépôt, pas tous
a.recheckOne(idx)
})
@@ -559,6 +617,9 @@ func (a *App) updateAll() {
if err == nil && res.NewCommits > 0 {
err = doPullWithProgress(res, cb)
}
if err == nil && res.UntrackedFiles > 0 {
err = doClean(res)
}
}
a.mw.Synchronize(func() {
if err != nil {

View File

@@ -9,7 +9,7 @@ import (
"github.com/lxn/walk"
)
const VERSION = "0.7.4"
const VERSION = "0.7.5"
func exeDir() string {
exe, err := os.Executable()

View File

@@ -1 +1 @@
0.7.4
0.7.5