package main import ( "fmt" "os" "path/filepath" "strings" "github.com/bmatcuk/doublestar/v4" ) func IsSymlink(path string) (bool, error) { fileInfo, err := os.Lstat(path) if err != nil { return false, err } // os.ModeSymlink is a bitmask that identifies the symlink mode. // If the file mode & os.ModeSymlink is non-zero, the file is a symlink. return fileInfo.Mode()&os.ModeSymlink != 0, nil } func FileExists(path string) bool { _, err := os.Lstat(path) return err == nil } func NormalizePath(input, workdir string) string { input = filepath.Clean(input) input = filepath.ToSlash(input) input = strings.ReplaceAll(input, "\"", "") if !filepath.IsAbs(input) { LogInfo("Input '%s' is not absolute, prepending work dir '%s'", input, workdir) var err error input = filepath.Join(workdir, input) input, err = filepath.Abs(input) if err != nil { LogError("Failed to get absolute path for %s: %v", FormatSourcePath(input), err) return input } } input = filepath.Clean(input) input = filepath.ToSlash(input) return input } func AreSame(lhs string, rhs string) bool { lhsinfo, err := os.Stat(lhs) if err != nil { return false } rhsinfo, err := os.Stat(rhs) if err != nil { return false } return os.SameFile(lhsinfo, rhsinfo) } func ConvertHome(input string) (string, error) { if strings.HasPrefix(input, "~/") { homedir, err := os.UserHomeDir() if err != nil { return input, fmt.Errorf("unable to convert ~ to user directory with error %+v", err) } return strings.Replace(input, "~", homedir, 1), nil } return input, nil } // ResolvePath resolves a path according to the following rules: // 1. If path starts with ~/, replace ~ with user's home directory // 2. If path is absolute, return as-is // 3. If path is relative, join it with the provided base directory // This function ensures tilde expansion happens BEFORE path joining to avoid the bug // where ~/path becomes /base/dir/~/path instead of /home/user/path func ResolvePath(path, baseDir string) string { // First, convert any tilde to home directory if strings.HasPrefix(path, "~/") { homedir, err := os.UserHomeDir() if err != nil { LogError("unable to get user home directory: %v", err) // Fall back to treating as relative path } else { path = strings.Replace(path, "~", homedir, 1) return path // After tilde expansion, it's an absolute path } } // If it's already absolute, return as-is if filepath.IsAbs(path) { return path } // Otherwise, it's relative - join with base directory return filepath.Join(baseDir, path) } func GetSyncFilesRecursively(input string, output chan string, status chan error) { defer close(output) defer close(status) workdir, _ := os.Getwd() input = NormalizePath(input, workdir) LogInfo("Searching for sync files recursively starting in %s", FormatPathValue(input)) // Use doublestar to find all sync.yml and sync.yaml files recursively pattern := "**/sync.y*ml" files, err := doublestar.Glob(os.DirFS(input), pattern) if err != nil { LogError("Failed to search for pattern %s: %v", pattern, err) status <- err return } for _, file := range files { fullPath := filepath.Join(input, file) LogInfo("Found sync file: %s", FormatPathValue(fullPath)) output <- fullPath } LogInfo("Completed recursive search for sync files") }