package main import ( "io" "log" "os" "path/filepath" ) type FileMeta struct { Size int64 Mode os.FileMode 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 { return err } } meta := FileMeta{ Size: info.Size(), Mode: info.Mode(), Symlink: linkTarget, } manifest.Files[relPath] = meta if dryRun { log.Printf("[dry-run] would sync: %s", relPath) return nil } if info.IsDir() { return os.MkdirAll(destPath, info.Mode()) } if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil { return err } if linkTarget != "" { return os.Symlink(linkTarget, destPath) } return copyFile(path, destPath, info.Mode()) }) 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 }