From 9c34b613bc0aa5cd6b727ad56407fa1ba7938472 Mon Sep 17 00:00:00 2001 From: postoso Date: Mon, 31 Aug 2026 22:37:45 -0400 Subject: [PATCH] fix: create destination directory when moving a note os.Rename fails with ENOENT when the destination directory does not exist, and Move mapped every rename error to NoteDoesNotExistError, so moving a note into a new folder reported "Cannot find note in vault" even though the source note was there. Create the intermediate directories before the rename, matching what create and daily already do for their note paths. The source is checked first so a genuinely missing note still reports NoteDoesNotExistError and does not leave an empty destination directory behind. Fixes #134 --- README.md | 2 +- pkg/obsidian/note.go | 14 ++++++++++-- pkg/obsidian/note_test.go | 45 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2637a517..cdf81ffb 100644 --- a/README.md +++ b/README.md @@ -415,7 +415,7 @@ notesmd-cli create "{note-name}" --content "abcde" --open --editor ### Move / Rename Note -Moves a given note(path from top level of vault) with new name given (top level of vault). If given same path but different name then its treated as a rename. All links inside vault are updated to match new name. +Moves a given note(path from top level of vault) with new name given (top level of vault). If given same path but different name then its treated as a rename. All links inside vault are updated to match new name. Intermediate directories in the destination path are created automatically. ```bash # Renames a note in default obsidian diff --git a/pkg/obsidian/note.go b/pkg/obsidian/note.go index f337447d..2c2c91af 100644 --- a/pkg/obsidian/note.go +++ b/pkg/obsidian/note.go @@ -39,9 +39,19 @@ func (m *Note) Move(originalPath string, newPath string) error { o := AddMdSuffix(originalPath) n := AddMdSuffix(newPath) - err := os.Rename(o, n) + // Check the source first so a missing note is reported as such and does not + // leave behind a destination directory created below. + if _, err := os.Stat(o); err != nil { + return errors.New(NoteDoesNotExistError) + } - if err != nil { + // Create any intermediate directories the destination requires, as create + // and daily already do for their note paths. + if err := os.MkdirAll(filepath.Dir(n), 0755); err != nil { + return fmt.Errorf("failed to create note directory: %w", err) + } + + if err := os.Rename(o, n); err != nil { return errors.New(NoteDoesNotExistError) } diff --git a/pkg/obsidian/note_test.go b/pkg/obsidian/note_test.go index 46357ff1..64270a69 100644 --- a/pkg/obsidian/note_test.go +++ b/pkg/obsidian/note_test.go @@ -201,6 +201,51 @@ func TestMoveNote(t *testing.T) { // Assert assert.Equal(t, err.Error(), obsidian.NoteDoesNotExistError) }) + + t.Run("Move note to destination directory that does not exist", func(t *testing.T) { + // Arrange + tempDir := t.TempDir() + originalPath := filepath.Join(tempDir, "original.md") + newPath := filepath.Join(tempDir, "one", "two", "moved") + expectedNewPath := filepath.Join(tempDir, "one", "two", "moved.md") + + err := os.WriteFile(originalPath, []byte(originalContent), 0644) + if err != nil { + t.Fatal(err) + } + + // Act + noteManager := obsidian.Note{} + err = noteManager.Move(filepath.Join(tempDir, "original"), newPath) + + // Assert + assert.NoError(t, err, "Expected no error while moving note into a missing directory") + + _, err = os.Stat(originalPath) + assert.True(t, os.IsNotExist(err), "Original file still exists at %s, expected it to be moved", originalPath) + + newContent, err := os.ReadFile(expectedNewPath) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, originalContent, string(newContent), "New file content is %q, expected %q", string(newContent), originalContent) + }) + + t.Run("Missing note does not create destination directory", func(t *testing.T) { + // Arrange + tempDir := t.TempDir() + newFolder := filepath.Join(tempDir, "newFolder") + + // Act + noteManager := obsidian.Note{} + err := noteManager.Move(filepath.Join(tempDir, "missing"), filepath.Join(newFolder, "missing")) + + // Assert + assert.Equal(t, obsidian.NoteDoesNotExistError, err.Error()) + + _, statErr := os.Stat(newFolder) + assert.True(t, os.IsNotExist(statErr), "Destination directory %s was created for a note that does not exist", newFolder) + }) } func createTmpDirAndFiles(t *testing.T, perm os.FileMode, files []string, content []byte) string {