Release 2026.09.27.r002: clarify rsync dry runs and expand run diagnostics

This commit is contained in:
Mikei386
2026-09-27 16:23:01 +02:00
parent 106f9c9387
commit 74a69b1580
13 changed files with 451 additions and 46 deletions
+37 -19
View File
@@ -40,16 +40,19 @@ type Progress struct {
Bytes int64
Files int64
CurrentFile string
Change string
}
type Summary struct {
Bytes int64
Files int64
Bytes int64
Files int64
Deleted int64
}
var (
progressPattern = regexp.MustCompile(`^\s*([0-9,]+)\s+([0-9]+)%`)
filesPattern = regexp.MustCompile(`^Number of regular files transferred:\s*([0-9,]+)`)
deletedPattern = regexp.MustCompile(`^Number of deleted files:\s*([0-9,]+)`)
bytesPattern = regexp.MustCompile(`^Total transferred file size:\s*([0-9,]+) bytes`)
)
@@ -88,17 +91,12 @@ func (r *Runner) Run(ctx context.Context, runID string, job model.Job, callback
done := make(chan struct{})
go func() {
scanOutput(stdout, func(line string) {
if value, ok := parseCount(filesPattern, line); ok {
summary.Files = value
}
if value, ok := parseCount(bytesPattern, line); ok {
summary.Bytes = value
}
summary.parseLine(line)
if callback != nil {
if progress, ok := parseProgress(line); ok {
callback(progress)
} else if strings.HasPrefix(line, "FILE|") {
callback(Progress{CurrentFile: strings.TrimPrefix(line, "FILE|")})
} else if change, ok := parseChange(line); ok {
callback(change)
}
}
})
@@ -106,9 +104,10 @@ func (r *Runner) Run(ctx context.Context, runID string, job model.Job, callback
}()
stderrDone := make(chan []byte, 1)
go func() { stderrDone <- drainLimited(stderr, 1024*1024) }()
err = cmd.Wait()
// Drain both pipes before Wait closes them, including the final statistics.
<-done
errBytes := <-stderrDone
err = cmd.Wait()
if err != nil {
if errors.Is(ctx.Err(), context.Canceled) {
return Summary{}, fmt.Errorf("cancelled: %w", ctx.Err())
@@ -163,20 +162,16 @@ func (r *Runner) Estimate(ctx context.Context, runID string, job model.Job) (Sum
done := make(chan struct{})
go func() {
scanOutput(stdout, func(line string) {
if value, ok := parseCount(filesPattern, line); ok {
summary.Files = value
}
if value, ok := parseCount(bytesPattern, line); ok {
summary.Bytes = value
}
summary.parseLine(line)
})
close(done)
}()
stderrDone := make(chan []byte, 1)
go func() { stderrDone <- drainLimited(stderr, 1024*1024) }()
err = cmd.Wait()
// Drain both pipes before Wait closes them, including the final statistics.
<-done
errBytes := <-stderrDone
err = cmd.Wait()
if err != nil {
if errors.Is(ctx.Err(), context.Canceled) {
return Summary{}, fmt.Errorf("cancelled: %w", ctx.Err())
@@ -188,7 +183,7 @@ func (r *Runner) Estimate(ctx context.Context, runID string, job model.Job) (Sum
func Arguments(job model.Job) []string {
options := job.Rsync
args := []string{"--recursive", "--human-readable", "--info=progress2", "--stats", "--partial", "--out-format=FILE|%n"}
args := []string{"--recursive", "--info=progress2", "--stats", "--partial", "--outbuf=L", "--out-format=CHANGE|%i|%n%L"}
if !options.Overwrite {
args = append(args, "--ignore-existing")
}
@@ -322,3 +317,26 @@ func parseCount(pattern *regexp.Regexp, line string) (int64, bool) {
value, err := strconv.ParseInt(strings.ReplaceAll(match[1], ",", ""), 10, 64)
return value, err == nil
}
func (s *Summary) parseLine(line string) {
if value, ok := parseCount(filesPattern, line); ok {
s.Files = value
}
if value, ok := parseCount(bytesPattern, line); ok {
s.Bytes = value
}
if value, ok := parseCount(deletedPattern, line); ok {
s.Deleted = value
}
}
func parseChange(line string) (Progress, bool) {
if !strings.HasPrefix(line, "CHANGE|") {
return Progress{}, false
}
fields := strings.SplitN(strings.TrimPrefix(line, "CHANGE|"), "|", 2)
if len(fields) != 2 || len(fields[0]) != 11 {
return Progress{}, false
}
return Progress{Change: fields[0], CurrentFile: fields[1]}, true
}