diff --git a/GitUpdateChecker.exe b/GitUpdateChecker.exe index d239b4e..644b672 100644 Binary files a/GitUpdateChecker.exe and b/GitUpdateChecker.exe differ diff --git a/git.go b/git.go index ae3cbdc..b2bef9e 100644 --- a/git.go +++ b/git.go @@ -280,8 +280,8 @@ func parseGitProgress(line string) (ProgressInfo, bool) { // runGitWithProgress exécute une commande git et capture la progression en temps réel. // Le timeout est désactivé (0) ou très long pour les gros dépôts. -func runGitWithProgress(args []string, cwd string, timeout time.Duration, cb ProgressCallback) (int, string, string) { - ctx, cancel := context.WithTimeout(context.Background(), timeout) +func runGitWithProgress(parent context.Context, args []string, cwd string, timeout time.Duration, cb ProgressCallback) (int, string, string) { + ctx, cancel := context.WithTimeout(parent, timeout) defer cancel() fullArgs := append([]string{"-c", "safe.directory=*"}, args...) @@ -339,6 +339,9 @@ func runGitWithProgress(args []string, cwd string, timeout time.Duration, cb Pro stderr := strings.TrimSpace(stderrBuf.String()) if err != nil { + if ctx.Err() == context.Canceled { + return 1, stdout, "Annulé" + } if ctx.Err() == context.DeadlineExceeded { return 1, stdout, "Timeout" } @@ -352,7 +355,7 @@ func runGitWithProgress(args []string, cwd string, timeout time.Duration, cb Pro } // doCloneWithProgress clone un dépôt avec suivi de progression. -func doCloneWithProgress(cfg RepoConfig, cb ProgressCallback) error { +func doCloneWithProgress(ctx context.Context, cfg RepoConfig, cb ProgressCallback) error { local := absRepoPath(cfg.Path) if err := os.MkdirAll(filepath.Dir(local), 0755); err != nil { return err @@ -362,7 +365,7 @@ func doCloneWithProgress(cfg RepoConfig, cb ProgressCallback) error { entries, _ := os.ReadDir(local) if len(entries) == 0 { code, _, stderr := runGitWithProgress( - []string{"clone", "--progress", cfg.URL, local}, + ctx, []string{"clone", "--progress", cfg.URL, local}, "", 2*time.Hour, cb, ) if code != 0 { @@ -383,7 +386,7 @@ func doCloneWithProgress(cfg RepoConfig, cb ProgressCallback) error { } code, _, stderr = runGitWithProgress( - []string{"fetch", "--progress", "origin"}, + ctx, []string{"fetch", "--progress", "origin"}, local, 2*time.Hour, cb, ) if code != 0 { @@ -405,13 +408,13 @@ func doCloneWithProgress(cfg RepoConfig, cb ProgressCallback) error { } // doPullWithProgress fait un pull avec suivi de progression. -func doPullWithProgress(res RepoResult, cb ProgressCallback) error { +func doPullWithProgress(ctx context.Context, res RepoResult, cb ProgressCallback) error { _, branch, _ := runGit([]string{"rev-parse", "--abbrev-ref", "HEAD"}, res.Path, 5*time.Second) if branch == "" { branch = "master" } code, _, stderr := runGitWithProgress( - []string{"pull", "--progress", "origin", branch}, + ctx, []string{"pull", "--progress", "origin", branch}, res.Path, 2*time.Hour, cb, ) if code != 0 { diff --git a/gui.go b/gui.go index 35f519d..f3d7f65 100644 --- a/gui.go +++ b/gui.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" _ "embed" "fmt" "image" @@ -222,10 +223,13 @@ type App struct { btnRefresh *walk.PushButton btnUpdateAll *walk.PushButton btnAction *walk.PushButton + btnStop *walk.PushButton reposConfig []RepoConfig suConfig SelfUpdateConfig checking atomic.Bool + cancelMu sync.Mutex + cancelFunc context.CancelFunc } func runApp() error { @@ -327,6 +331,12 @@ func (a *App) buildAndRun() error { Enabled: false, OnClicked: a.doAction, }, + PushButton{ + AssignTo: &a.btnStop, + Text: "Arrêter", + Enabled: false, + OnClicked: a.stopOperations, + }, HSpacer{}, PushButton{Text: "config.ini", OnClicked: a.openConfig}, PushButton{Text: "Logs", OnClicked: a.openLogs}, @@ -540,6 +550,30 @@ func (a *App) makeProgressCB(row int) ProgressCallback { } } +// ── Annulation ──────────────────────────────────────────────────────────────── + +// newCancelCtx crée un nouveau contexte annulable et stocke la fonction cancel. +func (a *App) newCancelCtx() context.Context { + a.cancelMu.Lock() + defer a.cancelMu.Unlock() + ctx, cancel := context.WithCancel(context.Background()) + a.cancelFunc = cancel + return ctx +} + +// stopOperations annule toutes les opérations git en cours. +func (a *App) stopOperations() { + a.cancelMu.Lock() + fn := a.cancelFunc + a.cancelFunc = nil + a.cancelMu.Unlock() + if fn != nil { + fn() + a.appendLog("Opérations annulées par l'utilisateur") + logInfo("Opérations annulées par l'utilisateur") + } +} + // ── Actions dépôt ───────────────────────────────────────────────────────────── func (a *App) onSelectionChanged() { @@ -575,22 +609,25 @@ func (a *App) doAction() { } a.btnAction.SetEnabled(false) + a.btnStop.SetEnabled(true) a.appendLog(fmt.Sprintf("[%s] Mise à jour en cours...", res.Name)) + ctx := a.newCancelCtx() cb := a.makeProgressCB(idx) go func() { var err error if res.NeedsClone { - err = doCloneWithProgress(cfg, cb) + err = doCloneWithProgress(ctx, cfg, cb) } else { if res.LocalChanges > 0 { err = doCheckout(res) } if err == nil && res.HasUpdate { - err = doPullWithProgress(res, cb) + err = doPullWithProgress(ctx, res, cb) } } a.mw.Synchronize(func() { + a.btnStop.SetEnabled(false) if err != nil { a.model.setProgress(idx, 0, "Erreur") a.appendLog(fmt.Sprintf("[%s] Erreur: %v", res.Name, err)) @@ -614,7 +651,9 @@ func (a *App) doAction() { func (a *App) updateAll() { a.btnUpdateAll.SetEnabled(false) a.btnRefresh.SetEnabled(false) + a.btnStop.SetEnabled(true) pending := atomic.Int32{} + ctx := a.newCancelCtx() for i, cfg := range a.reposConfig { res, ok := a.model.getResult(i) @@ -627,13 +666,13 @@ func (a *App) updateAll() { go func() { var err error if res.NeedsClone { - err = doCloneWithProgress(cfg, cb) + err = doCloneWithProgress(ctx, cfg, cb) } else { if res.LocalChanges > 0 { err = doCheckout(res) } if err == nil && res.HasUpdate { - err = doPullWithProgress(res, cb) + err = doPullWithProgress(ctx, res, cb) } if err == nil && res.UntrackedFiles > 0 { err = doClean(res) @@ -650,12 +689,14 @@ func (a *App) updateAll() { a.appendLog(fmt.Sprintf("[%s] Mise à jour OK", res.Name)) } if pending.Add(-1) == 0 { + a.btnStop.SetEnabled(false) a.startCheck() } }) }() } if pending.Load() == 0 { + a.btnStop.SetEnabled(false) a.startCheck() } } diff --git a/main.go b/main.go index f3033ad..0a2ef93 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,7 @@ import ( "github.com/lxn/walk" ) -const VERSION = "0.7.7" +const VERSION = "0.7.8" func exeDir() string { exe, err := os.Executable() diff --git a/version.txt b/version.txt index 879be8a..e7c7d3c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.7.7 +0.7.8