Compare commits
2 Commits
feature/go
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ba377a4e4a | |||
| 19efbe6dd7 |
Binary file not shown.
@@ -38,15 +38,11 @@ func loadConfig() ([]RepoConfig, SelfUpdateConfig, error) {
|
||||
case strings.HasPrefix(section, "repo:"):
|
||||
name := strings.TrimPrefix(section, "repo:")
|
||||
if kv["url"] != "" && kv["path"] != "" {
|
||||
branch := kv["branch"]
|
||||
if branch == "" {
|
||||
branch = "master"
|
||||
}
|
||||
repos = append(repos, RepoConfig{
|
||||
Name: name,
|
||||
URL: kv["url"],
|
||||
Path: kv["path"],
|
||||
Branch: branch,
|
||||
Branch: kv["branch"], // vide = auto-détection
|
||||
})
|
||||
}
|
||||
case section == "self-update":
|
||||
|
||||
76
git.go
76
git.go
@@ -75,11 +75,39 @@ func checkRemoteOffline(stderr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// detectDefaultBranch détecte la branche par défaut d'un remote via ls-remote --symref HEAD.
|
||||
// Retourne "main", "master", etc. ou "" si indétectable.
|
||||
func detectDefaultBranch(urlOrRemote string, cwd string) string {
|
||||
_, out, _ := runGit([]string{"ls-remote", "--symref", urlOrRemote, "HEAD"}, cwd, 15*time.Second)
|
||||
// Format attendu : "ref: refs/heads/main\tHEAD"
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
if strings.HasPrefix(line, "ref: refs/heads/") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 2 {
|
||||
return strings.TrimPrefix(parts[0], "ref: refs/heads/")
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkRepo(cfg RepoConfig) RepoResult {
|
||||
res := RepoResult{Name: cfg.Name, URL: cfg.URL, Branch: cfg.Branch}
|
||||
local := absRepoPath(cfg.Path)
|
||||
res.Path = local
|
||||
|
||||
// Détecter la branche si non spécifiée dans la config
|
||||
branch := cfg.Branch
|
||||
if branch == "" {
|
||||
detected := detectDefaultBranch(cfg.URL, "")
|
||||
if detected != "" {
|
||||
branch = detected
|
||||
} else {
|
||||
branch = "master"
|
||||
}
|
||||
res.Branch = branch
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(local, ".git")); os.IsNotExist(err) {
|
||||
// Vérifier que le dépôt distant existe avant de proposer le clone
|
||||
code, _, stderr := runGit([]string{"ls-remote", "--exit-code", cfg.URL}, "", 15*time.Second)
|
||||
@@ -110,7 +138,6 @@ func checkRepo(cfg RepoConfig) RepoResult {
|
||||
}
|
||||
|
||||
// Vérification rapide du remote via ls-remote (timeout court)
|
||||
branch := cfg.Branch
|
||||
code, lsOut, stderr := runGit([]string{"ls-remote", "origin", "refs/heads/" + branch}, local, 15*time.Second)
|
||||
if code != 0 {
|
||||
if checkRemoteOffline(stderr) {
|
||||
@@ -130,9 +157,27 @@ func checkRepo(cfg RepoConfig) RepoResult {
|
||||
remoteHash = parts[0]
|
||||
}
|
||||
}
|
||||
// Si la branche n'existe pas sur le remote, détecter la branche par défaut
|
||||
if remoteHash == "" {
|
||||
res.Error = fmt.Sprintf("Branche '%s' introuvable sur le remote", branch)
|
||||
return res
|
||||
detected := detectDefaultBranch("origin", local)
|
||||
if detected == "" || detected == branch {
|
||||
res.Error = fmt.Sprintf("Branche '%s' introuvable sur le remote", branch)
|
||||
return res
|
||||
}
|
||||
logInfo(fmt.Sprintf("[%s] Branche '%s' introuvable, utilisation de '%s'", cfg.Name, branch, detected))
|
||||
branch = detected
|
||||
res.Branch = detected
|
||||
_, lsOut, _ = runGit([]string{"ls-remote", "origin", "refs/heads/" + branch}, local, 15*time.Second)
|
||||
if lsOut != "" {
|
||||
parts := strings.Fields(lsOut)
|
||||
if len(parts) > 0 {
|
||||
remoteHash = parts[0]
|
||||
}
|
||||
}
|
||||
if remoteHash == "" {
|
||||
res.Error = fmt.Sprintf("Branche '%s' introuvable sur le remote", branch)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
// Comparer les hashs
|
||||
@@ -280,8 +325,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 +384,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 +400,8 @@ 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 {
|
||||
// branch est la branche à checkout (détectée ou configurée).
|
||||
func doCloneWithProgress(ctx context.Context, cfg RepoConfig, branch string, cb ProgressCallback) error {
|
||||
local := absRepoPath(cfg.Path)
|
||||
if err := os.MkdirAll(filepath.Dir(local), 0755); err != nil {
|
||||
return err
|
||||
@@ -361,9 +410,13 @@ func doCloneWithProgress(cfg RepoConfig, cb ProgressCallback) error {
|
||||
// Si le dossier n'existe pas ou est vide, clone classique avec progression
|
||||
entries, _ := os.ReadDir(local)
|
||||
if len(entries) == 0 {
|
||||
args := []string{"clone", "--progress"}
|
||||
if branch != "" {
|
||||
args = append(args, "-b", branch)
|
||||
}
|
||||
args = append(args, cfg.URL, local)
|
||||
code, _, stderr := runGitWithProgress(
|
||||
[]string{"clone", "--progress", cfg.URL, local},
|
||||
"", 2*time.Hour, cb,
|
||||
ctx, args, "", 2*time.Hour, cb,
|
||||
)
|
||||
if code != 0 {
|
||||
return fmt.Errorf("%s", stderr)
|
||||
@@ -383,14 +436,13 @@ 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 {
|
||||
return fmt.Errorf("fetch: %s", stderr)
|
||||
}
|
||||
|
||||
branch := cfg.Branch
|
||||
code, _, stderr = runGit([]string{"checkout", "origin/" + branch, "-b", branch}, local, 30*time.Second)
|
||||
if code != 0 {
|
||||
code, _, stderr = runGit([]string{"checkout", branch}, local, 30*time.Second)
|
||||
@@ -405,13 +457,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 {
|
||||
|
||||
49
gui.go
49
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, res.Branch, 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, res.Branch, 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()
|
||||
}
|
||||
}
|
||||
|
||||
2
main.go
2
main.go
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/lxn/walk"
|
||||
)
|
||||
|
||||
const VERSION = "0.7.7"
|
||||
const VERSION = "0.7.9"
|
||||
|
||||
func exeDir() string {
|
||||
exe, err := os.Executable()
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.7
|
||||
0.7.9
|
||||
|
||||
Reference in New Issue
Block a user