aboutsummaryrefslogtreecommitdiffstats
path: root/sync.go
diff options
context:
space:
mode:
authorBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-06 21:15:17 +0200
committerBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-06 21:15:17 +0200
commitb505ac2fe010fcc24b774c313d33dc099214e8ed (patch)
treeff3f6472c84abd45e7918722e70ee681b859b780 /sync.go
downloadsourcewatch-b505ac2fe010fcc24b774c313d33dc099214e8ed.tar.gz
sourcewatch-b505ac2fe010fcc24b774c313d33dc099214e8ed.zip
Initial project scaffolding
- main.go: CLI entry point with flag parsing - gitignore.go: .gitignore loading and parsing - sync.go: initial sync with manifest tracking - watcher.go: fsnotify-based file watcher with debouncing - Dockerfile: multi-stage build for Alpine runtime
Diffstat (limited to 'sync.go')
-rw-r--r--sync.go106
1 files changed, 106 insertions, 0 deletions
diff --git a/sync.go b/sync.go
new file mode 100644
index 0000000..293f20b
--- /dev/null
+++ b/sync.go
@@ -0,0 +1,106 @@
+package main
+
+import (
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ ignore "github.com/sabhiram/go-gitignore"
+)
+
+type FileMeta struct {
+ Size int64
+ Mode os.FileMode
+ Symlink string
+}
+
+type Manifest struct {
+ Files map[string]FileMeta
+}
+
+func initialSync(hostDir, dataDir string, rules *ignore.GitIgnore, 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 relPath == ".git" || strings.HasPrefix(relPath, ".git"+string(filepath.Separator)) {
+ return filepath.SkipDir
+ }
+
+ if rules.MatchesPath(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
+}