v0.7.6 - Clone dossier non-vide et verification rapide

Changements :
- Clone dans dossier non-vide (git init + remote add + fetch + checkout)
- Verification rapide via git ls-remote au lieu de git fetch (timeout 15s)
- Support branche par repo dans config.ini (champ branch)
- Suppression fichiers Python et artefacts PyInstaller (_internal/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 11:41:11 +01:00
parent 55663e3a19
commit d8f3a29f8e
958 changed files with 165 additions and 116233 deletions

163
git.go
View File

@@ -19,12 +19,13 @@ type RepoResult struct {
Name string
Path string
URL string
Branch string // branche configurée
Pending bool
UpToDate bool
Offline bool
NeedsClone bool
HasUpdate bool // MAJ disponible (hash local != distant)
Error string
NewCommits int
LocalChanges int
UntrackedFiles int
UntrackedList []string // liste des fichiers non suivis
@@ -65,8 +66,17 @@ func absRepoPath(rel string) string {
return filepath.Join(exeDir(), rel)
}
func checkRemoteOffline(stderr string) bool {
for _, kw := range []string{"could not resolve", "connection refused", "unable to connect", "timed out", "the remote end hung up", "timeout"} {
if strings.Contains(strings.ToLower(stderr), kw) {
return true
}
}
return false
}
func checkRepo(cfg RepoConfig) RepoResult {
res := RepoResult{Name: cfg.Name, URL: cfg.URL}
res := RepoResult{Name: cfg.Name, URL: cfg.URL, Branch: cfg.Branch}
local := absRepoPath(cfg.Path)
res.Path = local
@@ -74,12 +84,10 @@ func checkRepo(cfg RepoConfig) RepoResult {
// 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)
if code != 0 {
for _, kw := range []string{"could not resolve", "connection refused", "unable to connect", "timed out", "the remote end hung up"} {
if strings.Contains(strings.ToLower(stderr), kw) {
res.Offline = true
res.Error = "Hors ligne"
return res
}
if checkRemoteOffline(stderr) {
res.Offline = true
res.Error = "Hors ligne"
return res
}
if strings.Contains(strings.ToLower(stderr), "not found") || strings.Contains(strings.ToLower(stderr), "repository not found") {
res.Error = "Dépôt introuvable : " + cfg.URL
@@ -94,27 +102,45 @@ func checkRepo(cfg RepoConfig) RepoResult {
runGit([]string{"remote", "set-url", "origin", cfg.URL}, local, 10*time.Second)
code, _, stderr := runGit([]string{"fetch", "origin"}, local, 5*time.Minute)
if code != 0 {
for _, kw := range []string{"could not resolve", "connection refused", "unable to connect", "timed out", "the remote end hung up"} {
if strings.Contains(strings.ToLower(stderr), kw) {
res.Offline = true
res.Error = "Hors ligne"
return res
}
}
res.Error = "Fetch: " + stderr
// Hash local
_, localHash, _ := runGit([]string{"rev-parse", "HEAD"}, local, 5*time.Second)
if localHash == "" {
res.Error = "Impossible de lire le commit local"
return res
}
_, branch, _ := runGit([]string{"rev-parse", "--abbrev-ref", "HEAD"}, local, 5*time.Second)
if branch == "" {
branch = "master"
// 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) {
res.Offline = true
res.Error = "Hors ligne"
return res
}
res.Error = "ls-remote: " + stderr
return res
}
_, countStr, _ := runGit([]string{"rev-list", "--count", "HEAD..origin/" + branch}, local, 5*time.Second)
fmt.Sscanf(countStr, "%d", &res.NewCommits)
// Extraire le hash distant
remoteHash := ""
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
if localHash != remoteHash {
res.HasUpdate = true
}
// Modifications locales
_, status, _ := runGit([]string{"status", "--porcelain"}, local, 5*time.Second)
if status != "" {
for _, line := range strings.Split(strings.TrimSpace(status), "\n") {
@@ -127,7 +153,7 @@ func checkRepo(cfg RepoConfig) RepoResult {
}
}
res.UpToDate = res.NewCommits == 0 && res.LocalChanges == 0 && res.UntrackedFiles == 0
res.UpToDate = !res.HasUpdate && res.LocalChanges == 0 && res.UntrackedFiles == 0
return res
}
@@ -136,9 +162,49 @@ func doClone(cfg RepoConfig) error {
if err := os.MkdirAll(filepath.Dir(local), 0755); err != nil {
return err
}
code, _, stderr := runGit([]string{"clone", cfg.URL, local}, "", 300*time.Second)
// Si le dossier n'existe pas ou est vide, clone classique
entries, _ := os.ReadDir(local)
if len(entries) == 0 {
code, _, stderr := runGit([]string{"clone", cfg.URL, local}, "", 300*time.Second)
if code != 0 {
return fmt.Errorf("%s", stderr)
}
return nil
}
// Dossier non-vide sans .git : init + remote + fetch + checkout
return doCloneInPlace(cfg, local)
}
func doCloneInPlace(cfg RepoConfig, local string) error {
code, _, stderr := runGit([]string{"init"}, local, 30*time.Second)
if code != 0 {
return fmt.Errorf("%s", stderr)
return fmt.Errorf("git init: %s", stderr)
}
code, _, stderr = runGit([]string{"remote", "add", "origin", cfg.URL}, local, 10*time.Second)
if code != 0 {
// remote existe déjà, mettre à jour l'URL
runGit([]string{"remote", "set-url", "origin", cfg.URL}, local, 10*time.Second)
}
code, _, stderr = runGit([]string{"fetch", "origin"}, local, 5*time.Minute)
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 {
// Branche locale existe déjà
code, _, stderr = runGit([]string{"checkout", branch}, local, 30*time.Second)
if code == 0 {
code, _, stderr = runGit([]string{"reset", "--hard", "origin/" + branch}, local, 30*time.Second)
}
}
if code != 0 {
return fmt.Errorf("checkout: %s", stderr)
}
return nil
}
@@ -291,12 +357,49 @@ func doCloneWithProgress(cfg RepoConfig, cb ProgressCallback) error {
if err := os.MkdirAll(filepath.Dir(local), 0755); err != nil {
return err
}
code, _, stderr := runGitWithProgress(
[]string{"clone", "--progress", cfg.URL, local},
"", 2*time.Hour, cb,
// Si le dossier n'existe pas ou est vide, clone classique avec progression
entries, _ := os.ReadDir(local)
if len(entries) == 0 {
code, _, stderr := runGitWithProgress(
[]string{"clone", "--progress", cfg.URL, local},
"", 2*time.Hour, cb,
)
if code != 0 {
return fmt.Errorf("%s", stderr)
}
return nil
}
// Dossier non-vide sans .git : init + remote + fetch avec progression + checkout
code, _, stderr := runGit([]string{"init"}, local, 30*time.Second)
if code != 0 {
return fmt.Errorf("git init: %s", stderr)
}
code, _, stderr = runGit([]string{"remote", "add", "origin", cfg.URL}, local, 10*time.Second)
if code != 0 {
runGit([]string{"remote", "set-url", "origin", cfg.URL}, local, 10*time.Second)
}
code, _, stderr = runGitWithProgress(
[]string{"fetch", "--progress", "origin"},
local, 2*time.Hour, cb,
)
if code != 0 {
return fmt.Errorf("%s", stderr)
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)
if code == 0 {
code, _, stderr = runGit([]string{"reset", "--hard", "origin/" + branch}, local, 30*time.Second)
}
}
if code != 0 {
return fmt.Errorf("checkout: %s", stderr)
}
return nil
}