diff --git a/daemon.go b/daemon.go index 5f86c7f..0c7f9c1 100644 --- a/daemon.go +++ b/daemon.go @@ -8,6 +8,7 @@ import ( "github.com/cortesi/modd/conf" "github.com/cortesi/modd/shell" + "github.com/cortesi/modd/utils" "github.com/cortesi/modd/varcmd" "github.com/cortesi/termlog" ) @@ -123,7 +124,7 @@ func NewDaemonPen(block conf.Block, vars map[string]string, log termlog.TermLog) if block.InDir != "" { indir = block.InDir } else { - indir, err = os.Getwd() + indir, err = utils.GetRealWd() if err != nil { return nil, err } diff --git a/modd.go b/modd.go index 8eb1409..d434d7a 100644 --- a/modd.go +++ b/modd.go @@ -10,6 +10,7 @@ import ( "github.com/cortesi/modd/conf" "github.com/cortesi/modd/notify" "github.com/cortesi/modd/shell" + "github.com/cortesi/modd/utils" "github.com/cortesi/moddwatch" "github.com/cortesi/termlog" ) @@ -108,7 +109,7 @@ func (mr *ModRunner) PrepOnly(initial bool) error { func (mr *ModRunner) runBlock(b conf.Block, mod *moddwatch.Mod, dpen *DaemonPen) { if b.InDir != "" { - currentDir, err := os.Getwd() + currentDir, err := utils.GetRealWd() if err != nil { mr.Log.Shout("Error getting current working directory: %s", err) return @@ -184,14 +185,14 @@ func (mr *ModRunner) runOnChan(modchan chan *moddwatch.Mod, readyCallback func() ipatts = append(ipatts, filepath.Dir(mr.ConfPath)) } - currentDir, err := os.Getwd() + currentDir, err := utils.GetRealWd() if err != nil { return err } + // FIXME: This takes a long time. We could start it in parallel with the // first process run in a goroutine watcher, err := moddwatch.Watch(currentDir, ipatts, []string{}, lullTime, modchan) - if err != nil { return fmt.Errorf("Error watching: %s", err) } diff --git a/utils/utils.go b/utils/utils.go index 700e705..edf6256 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -2,6 +2,8 @@ package utils import ( "os" + "path/filepath" + "strings" "testing" ) @@ -34,3 +36,56 @@ func WithTempDir(t *testing.T) func() { } } } + +// GetRealWd returns the current working directory with the path +// segments changed to the actual case on disk. Sticks to the +// logic of os.Getwd, without resolving symlinks. +func GetRealWd() (string, error) { + indir, err := os.Getwd() + if err != nil { + return "", err + } + + // Initial stat to check if the os considers the file to exist. + // INFO: If we pass this point, the os considers this a path to an existing + // file. If we now find case discrepancies, we can assume that the + // filesystem is case-insensitive. + _, err = os.Stat(indir) + if err != nil { + return "", err + } + + sepString := string(filepath.Separator) + prefix := filepath.VolumeName(indir) // empty on unix + relativePath := indir[len(prefix)+len(sepString):] + + // Split the relative path into segments + segments := strings.Split(relativePath, sepString) + realPath := prefix + sepString + + // Validate each segment +Seg: + for _, segment := range segments { + currentPath := filepath.Join(realPath, segment) + parentDir := filepath.Dir(currentPath) + if parentDir == "." { + parentDir = "" + } + + entries, err := os.ReadDir(parentDir) + if err != nil { + return "", err + } + + for _, entry := range entries { + if strings.EqualFold(entry.Name(), segment) { + realPath = filepath.Join(realPath, entry.Name()) + continue Seg + } + } + + return "", os.ErrNotExist + } + + return realPath, nil +}