Maj branch
This commit is contained in:
152
git.go
152
git.go
@@ -1,12 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -71,7 +75,7 @@ func checkRepo(cfg RepoConfig) RepoResult {
|
||||
|
||||
runGit([]string{"remote", "set-url", "origin", cfg.URL}, local, 10*time.Second)
|
||||
|
||||
code, _, stderr := runGit([]string{"fetch", "origin"}, local, 60*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) {
|
||||
@@ -132,3 +136,149 @@ func doCheckout(res RepoResult) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── Progression Git ───────────────────────────────────────────────────────────
|
||||
|
||||
// ProgressInfo contient l'état de progression d'une opération git.
|
||||
type ProgressInfo struct {
|
||||
Phase string // ex: "Receiving objects", "Resolving deltas"
|
||||
Percent float64 // 0.0 à 1.0
|
||||
Current int64
|
||||
Total int64
|
||||
Speed string // ex: "1.2 MiB/s"
|
||||
}
|
||||
|
||||
// ProgressCallback est appelé à chaque mise à jour de la progression.
|
||||
type ProgressCallback func(ProgressInfo)
|
||||
|
||||
// reGitProgress capture les lignes de progression git :
|
||||
//
|
||||
// "Receiving objects: 45% (123/456), 1.20 MiB | 500.00 KiB/s"
|
||||
// "Resolving deltas: 100% (89/89), done."
|
||||
var reGitProgress = regexp.MustCompile(
|
||||
`(?i)([\w\s]+):\s+(\d+)%\s+\((\d+)/(\d+)\)(?:.*\|\s*(.+/s))?`,
|
||||
)
|
||||
|
||||
// parseGitProgress analyse une ligne de sortie git et renvoie un ProgressInfo.
|
||||
func parseGitProgress(line string) (ProgressInfo, bool) {
|
||||
m := reGitProgress.FindStringSubmatch(line)
|
||||
if m == nil {
|
||||
return ProgressInfo{}, false
|
||||
}
|
||||
pct, _ := strconv.Atoi(m[2])
|
||||
cur, _ := strconv.ParseInt(m[3], 10, 64)
|
||||
tot, _ := strconv.ParseInt(m[4], 10, 64)
|
||||
speed := strings.TrimSpace(m[5])
|
||||
return ProgressInfo{
|
||||
Phase: strings.TrimSpace(m[1]),
|
||||
Percent: float64(pct) / 100.0,
|
||||
Current: cur,
|
||||
Total: tot,
|
||||
Speed: speed,
|
||||
}, true
|
||||
}
|
||||
|
||||
// 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)
|
||||
defer cancel()
|
||||
|
||||
fullArgs := append([]string{"-c", "safe.directory=*"}, args...)
|
||||
cmd := newGitCmd(ctx, fullArgs, cwd)
|
||||
|
||||
var outBuf strings.Builder
|
||||
cmd.Stdout = &outBuf
|
||||
|
||||
// Pipe stderr pour lire la progression en temps réel
|
||||
stderrPipe, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return -1, "", err.Error()
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return -1, "", err.Error()
|
||||
}
|
||||
|
||||
// Lire stderr byte par byte pour détecter les \r (git écrase la ligne)
|
||||
var stderrBuf strings.Builder
|
||||
reader := bufio.NewReader(stderrPipe)
|
||||
var lineBuf strings.Builder
|
||||
|
||||
for {
|
||||
b, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
stderrBuf.WriteString(err.Error())
|
||||
}
|
||||
break
|
||||
}
|
||||
stderrBuf.WriteByte(b)
|
||||
|
||||
if b == '\r' || b == '\n' {
|
||||
line := lineBuf.String()
|
||||
lineBuf.Reset()
|
||||
if cb != nil && line != "" {
|
||||
if info, ok := parseGitProgress(line); ok {
|
||||
cb(info)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lineBuf.WriteByte(b)
|
||||
}
|
||||
}
|
||||
// Dernière ligne sans \r\n
|
||||
if lineBuf.Len() > 0 && cb != nil {
|
||||
if info, ok := parseGitProgress(lineBuf.String()); ok {
|
||||
cb(info)
|
||||
}
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
stdout := strings.TrimSpace(outBuf.String())
|
||||
stderr := strings.TrimSpace(stderrBuf.String())
|
||||
|
||||
if err != nil {
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return 1, stdout, "Timeout"
|
||||
}
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
return exitErr.ExitCode(), stdout, stderr
|
||||
}
|
||||
return -1, stdout, err.Error()
|
||||
}
|
||||
return 0, stdout, stderr
|
||||
}
|
||||
|
||||
// doCloneWithProgress clone un dépôt avec suivi de progression.
|
||||
func doCloneWithProgress(cfg RepoConfig, cb ProgressCallback) error {
|
||||
local := absRepoPath(cfg.Path)
|
||||
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,
|
||||
)
|
||||
if code != 0 {
|
||||
return fmt.Errorf("%s", stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// doPullWithProgress fait un pull avec suivi de progression.
|
||||
func doPullWithProgress(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},
|
||||
res.Path, 2*time.Hour, cb,
|
||||
)
|
||||
if code != 0 {
|
||||
return fmt.Errorf("%s", stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user