package main import ( "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 } func initialSync(hostDir, dataDir string, stack *GitignoreStack, dryRun bool) (*Manifest, error) { manifest := &Manifest{Files: make(map[string]FileMeta)} err := filepath.Walk(hostDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } 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) { if info.IsDir() { return filepath.SkipDir } return nil } destPath := filepath.Join(dataDir, relPath) linkTarget := "" if info.Mode()&os.ModeSymlink != 0 { linkTarget, err = os.Readlink(path) if err != nil { logError("readlink", 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 { logError("mkdir", relPath, err) } return nil } if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil { logError("mkdir", filepath.Dir(relPath), err) return nil } if linkTarget != "" { if err := os.Symlink(linkTarget, destPath); err != nil { logError("symlink", relPath, err) } return nil } if err := copyFileWithRetry(path, destPath, info.Mode()); err != nil { logError("copy", relPath, err) } return nil }) 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 }