`deepnote sync` directory moves can land on another project's directory · Issue #537 · deepnote/deepnote · GitHub
Skip to content

deepnote sync directory moves can land on another project's directory #537

Open

Description

@jankoritak

Summary

When deepnote sync renames a project directory after a cloud rename, it calls fs.rename without checking that the destination is free or that nothing under the source belongs to another project. Four scenarios, each present on main:

1. High. Renaming onto an empty project directory cross-links two projects. fs.rename succeeds when the destination is an existing empty directory. Project A (has notebooks) is renamed to B's path while B's directory has no notebooks. A's directory lands on B's, and the manifest records both projects at one directory. On the next run B's local hash equals A's content, so sync classifies B as a push and imports A's notebooks into project B in the cloud.

2. Medium. A failed move does not block moves into its source. X Foo → Zed fails because Zed is occupied. Y moves into Foo/Y, inside X's still-present directory. On the next run X's move carries Foo/Y to Zed/Y, Y is pulled fresh into Foo/Y, and Y's unpushed edits stay hidden in Zed/Y. They are not reported as untracked because Zed is tracked.

3. Medium-low. The untracked-destination check can target another project's live directory. When a record's source directory is missing and its planned directory exists, sync treats that directory as untracked. If it is another project's current directory whose own move failed, the prompt says the directory is not linked, and --on-conflict override overwrites the other project's files.

4. Medium. A new project can be synced into a tracked project's directory before that project moves out. A new project Y is planned at A while tracked project X moves from A to B. Projects sync in path order, so Y can run first. Y has no manifest record, so A looks like an untracked directory, and --on-conflict override writes Y's notebooks over X's local files. X then moves A to B and pulls its cloud copy, so X's unpushed local edits are lost.

Expected

Before any rename, refuse when the destination exists (lstat, allowing case-only renames), matches another record's current directory, or when the source contains another pending move's destination. When a move fails, fail every pending move whose destination is under the failed move's source. Run every directory move before any project writes into its planned directory. Report each refused move as an error outcome and leave disk and manifest untouched.

Found while reviewing #535. All three exist on main, so they are not part of that change.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions