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:
Binary file not shown.
41
git.go
41
git.go
@@ -16,16 +16,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RepoResult struct {
|
type RepoResult struct {
|
||||||
Name string
|
Name string
|
||||||
Path string
|
Path string
|
||||||
URL string
|
URL string
|
||||||
Pending bool
|
Pending bool
|
||||||
UpToDate bool
|
UpToDate bool
|
||||||
Offline bool
|
Offline bool
|
||||||
NeedsClone bool
|
NeedsClone bool
|
||||||
Error string
|
Error string
|
||||||
NewCommits int
|
NewCommits int
|
||||||
LocalChanges 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) {
|
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)
|
_, status, _ := runGit([]string{"status", "--porcelain"}, local, 5*time.Second)
|
||||||
if status != "" {
|
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
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +163,14 @@ func doCheckout(res RepoResult) error {
|
|||||||
return nil
|
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 ───────────────────────────────────────────────────────────
|
// ── Progression Git ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// ProgressInfo contient l'état de progression d'une opération git.
|
// ProgressInfo contient l'état de progression d'une opération git.
|
||||||
|
|||||||
65
gui.go
65
gui.go
@@ -46,6 +46,12 @@ func (it *RepoItem) statusText() string {
|
|||||||
}
|
}
|
||||||
msg += fmt.Sprintf("%d modif. locale(s)", r.LocalChanges)
|
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 == "" {
|
if msg == "" {
|
||||||
return "À jour"
|
return "À jour"
|
||||||
}
|
}
|
||||||
@@ -195,7 +201,7 @@ func (m *RepoModel) hasUpdates() bool {
|
|||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
for _, it := range m.items {
|
for _, it := range m.items {
|
||||||
r := it.result
|
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
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,6 +436,12 @@ func logLineForResult(r RepoResult) string {
|
|||||||
}
|
}
|
||||||
msg += fmt.Sprintf("%d modif. locale(s)", r.LocalChanges)
|
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
|
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 ───────────────────────────────────────────────────────────────
|
// ── Progression ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// makeProgressCB crée un callback de progression pour la ligne row du tableau.
|
// makeProgressCB crée un callback de progression pour la ligne row du tableau.
|
||||||
@@ -487,7 +534,7 @@ func (a *App) onSelectionChanged() {
|
|||||||
if res.NeedsClone {
|
if res.NeedsClone {
|
||||||
a.btnAction.SetText("Cloner")
|
a.btnAction.SetText("Cloner")
|
||||||
a.btnAction.SetEnabled(true)
|
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.SetText("Mettre à jour")
|
||||||
a.btnAction.SetEnabled(true)
|
a.btnAction.SetEnabled(true)
|
||||||
} else {
|
} else {
|
||||||
@@ -503,6 +550,12 @@ func (a *App) doAction() {
|
|||||||
}
|
}
|
||||||
cfg := a.reposConfig[idx]
|
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.btnAction.SetEnabled(false)
|
||||||
a.appendLog(fmt.Sprintf("[%s] Mise à jour en cours...", res.Name))
|
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))
|
a.appendLog(fmt.Sprintf("[%s] Mise à jour OK", res.Name))
|
||||||
logInfo(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
|
// Re-vérifier uniquement ce dépôt, pas tous
|
||||||
a.recheckOne(idx)
|
a.recheckOne(idx)
|
||||||
})
|
})
|
||||||
@@ -559,6 +617,9 @@ func (a *App) updateAll() {
|
|||||||
if err == nil && res.NewCommits > 0 {
|
if err == nil && res.NewCommits > 0 {
|
||||||
err = doPullWithProgress(res, cb)
|
err = doPullWithProgress(res, cb)
|
||||||
}
|
}
|
||||||
|
if err == nil && res.UntrackedFiles > 0 {
|
||||||
|
err = doClean(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a.mw.Synchronize(func() {
|
a.mw.Synchronize(func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/lxn/walk"
|
"github.com/lxn/walk"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "0.7.4"
|
const VERSION = "0.7.5"
|
||||||
|
|
||||||
func exeDir() string {
|
func exeDir() string {
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.7.4
|
0.7.5
|
||||||
|
|||||||
Reference in New Issue
Block a user