package main import ( "os" "path/filepath" ) func syncCreate(srcPath, destPath, relPath string, manifest *Manifest, stack *GitignoreStack, dryRun bool, watcher Watcher) { info, err := os.Lstat(srcPath) if err != nil { if os.IsNotExist(err) { return } logErrorf("stat %s: %v", relPath, err) return } if info.IsDir() { if hasGitignore(srcPath) { stack.Push(srcPath) } if watcher != nil { watcher.Add(srcPath) filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error { if err != nil || !info.IsDir() || path == srcPath { return nil } watcher.Add(path) return nil }) } if dryRun { logInfo("[dry-run] would create dir: %s", relPath) return } if err := os.MkdirAll(destPath, info.Mode()); err != nil { logErrorf("mkdir %s: %v", relPath, err) } return } linkTarget := "" if info.Mode()&os.ModeSymlink != 0 { linkTarget, err = os.Readlink(srcPath) if err != nil { logErrorf("readlink %s: %v", relPath, err) return } } meta := FileMeta{ Size: info.Size(), Mode: info.Mode(), ModTime: info.ModTime(), Symlink: linkTarget, } manifest.Files[relPath] = meta if dryRun { logInfo("[dry-run] would sync: %s", relPath) return } os.MkdirAll(filepath.Dir(destPath), 0755) os.Remove(destPath) if linkTarget != "" { if err := os.Symlink(linkTarget, destPath); err != nil { logErrorf("symlink %s: %v", relPath, err) } logInfo("sync: %s -> %s (symlink)", relPath, linkTarget) return } if err := copyFileWithRetry(srcPath, destPath, info.Mode()); err != nil { logErrorf("copy %s: %v", relPath, err) } else { logInfo("sync: %s", relPath) } } func syncWrite(srcPath, destPath, relPath string, manifest *Manifest, dryRun bool) { info, err := os.Lstat(srcPath) if err != nil { if os.IsNotExist(err) { return } logErrorf("stat %s: %v", relPath, err) return } linkTarget := "" if info.Mode()&os.ModeSymlink != 0 { linkTarget, err = os.Readlink(srcPath) if err != nil { logErrorf("readlink %s: %v", relPath, err) return } } meta, exists := manifest.Files[relPath] srcHash := hashFile(srcPath) if exists && meta.Size == info.Size() && meta.Symlink == linkTarget && (srcHash == 0 || meta.Hash == srcHash) { return } manifest.Files[relPath] = FileMeta{ Size: info.Size(), Mode: info.Mode(), ModTime: info.ModTime(), Symlink: linkTarget, Hash: srcHash, } if dryRun { logInfo("[dry-run] would update: %s", relPath) return } os.Remove(destPath) if linkTarget != "" { if err := os.Symlink(linkTarget, destPath); err != nil { logErrorf("symlink %s: %v", relPath, err) } logInfo("sync: %s -> %s (symlink)", relPath, linkTarget) return } if err := copyFileWithRetry(srcPath, destPath, info.Mode()); err != nil { logErrorf("copy %s: %v", relPath, err) } else { logInfo("sync: %s", relPath) } } func syncRemove(destPath, relPath string, manifest *Manifest, dryRun bool) { delete(manifest.Files, relPath) if dryRun { logInfo("[dry-run] would remove: %s", relPath) return } if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { logErrorf("remove %s: %v", relPath, err) } else { logInfo("remove: %s", relPath) } } type Watcher interface { Add(path string) error }