package main import ( "fmt" "io" "os" "path/filepath" "time" ) type FileMeta struct { Size int64 Mode os.FileMode ModTime time.Time Symlink string } type Manifest struct { Files map[string]FileMeta } type syncStats struct { files int dirs int skipped int errors int } func initialSync(hostDir, dataDir string, stack *GitignoreStack, dryRun bool) (*Manifest, error) { manifest := &Manifest{Files: make(map[string]FileMeta)} stats := &syncStats{} start := time.Now() logInfo("initial sync: scanning %s ...", hostDir) err := filepath.Walk(hostDir, func(path string, info os.FileInfo, err error) error { if err != nil { stats.errors++ logErrorf("walk %s: %v", path, err) return nil } relPath, err := filepath.Rel(hostDir, path) if err != nil { return err } if relPath == "." { return nil } if isGitDir(relPath) { if info.IsDir() { return filepath.SkipDir } return nil } if info.IsDir() && hasGitignore(path) { stack.Push(path) } if stack.IsIgnored(relPath) { stats.skipped++ if info.IsDir() { return filepath.SkipDir } return nil } if info.IsDir() { stats.dirs++ } else { stats.files++ } if stats.files%1000 == 0 && stats.files > 0 { fmt.Fprintf(os.Stderr, "\r synced %d files, %d dirs ...", stats.files, stats.dirs) } destPath := filepath.Join(dataDir, relPath) linkTarget := "" if info.Mode()&os.ModeSymlink != 0 { linkTarget, err = os.Readlink(path) if err != nil { stats.errors++ logErrorf("readlink %s: %v", relPath, err) return nil } } meta := FileMeta{ Size: info.Size(), Mode: info.Mode(), ModTime: info.ModTime(), Symlink: linkTarget, } manifest.Files[relPath] = meta if dryRun { return nil } if info.IsDir() { if err := os.MkdirAll(destPath, info.Mode()); err != nil { stats.errors++ logErrorf("mkdir %s: %v", relPath, err) } return nil } if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil { stats.errors++ logErrorf("mkdir %s: %v", filepath.Dir(relPath), err) return nil } if linkTarget != "" { if err := os.Symlink(linkTarget, destPath); err != nil { stats.errors++ logErrorf("symlink %s: %v", relPath, err) } return nil } if err := copyFileWithRetry(path, destPath, info.Mode()); err != nil { stats.errors++ logErrorf("copy %s: %v", relPath, err) } return nil }) elapsed := time.Since(start) fmt.Fprintf(os.Stderr, "\r") logInfo("initial sync: done in %s — %d files, %d dirs, %d skipped, %d errors", elapsed.Round(time.Millisecond), stats.files, stats.dirs, stats.skipped, stats.errors) return manifest, err } func copyFile(src, dst string, mode os.FileMode) error { srcFile, err := os.Open(src) if err != nil { return err } defer srcFile.Close() dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode) if err != nil { return err } defer dstFile.Close() _, err = io.Copy(dstFile, srcFile) return err }