Skip to content

Updating modification times in --sync can cause problems with editors #1411

@td-anne

Description

@td-anne

The VSCode plugin for Jupytext (https://github.com/caenrigen/vscode-jupytext-sync) is having a problem with file modification times: caenrigen/vscode-jupytext-sync#12

Specifically, the problem is that when a paired file is open in the editor and is saved (having been changed), the plugin runs jupytext --sync. This updates the other version of that file, then changes the timestamp on the file that was just saved. The editor then detects that the version of the file on disk is newer than the time the open editor saved it. If the user has made some change by this point, the editor sees active changes in the editor, and a file on disk that has changed since it was written out - saving again could cause data loss, so the editor pops up an error that the user has to deal with.

(In VSCode, if the user has not changed the contents of the open editor, the editor attempts to reload the file from disk, updating the active editor. This is unfortunately not without impact, at least for the notebook editor; it can mess up capturing the output from running cells, and it drops the user out of cell-editing mode.)

I don't have a simple solution for this, and maybe it has to be somehow handled by the editor plugin, but the phenomenon can arise even if one is running the jupytext --sync by hand from the command line.

Not updating the modification time of the original file leaves the timestamps in a misleading state, where the original appears older than the converted file; another --sync run will copy data the other way, possibly clobbering any changes to the original made before the converted file finished being written.

Setting the modification time of the converted file to exactly that the original had when it was checked would seem to resolve these issues, but I'm not sure that's possible on all operating systems/filesystems.

Here is a python file that demonstrates the behaviour (though not, obviously, its interaction with the editor):

import time
from pathlib import Path
from subprocess import run
from tempfile import TemporaryDirectory

PYTHON_CONTENTS = """

print("Hello")

"""


def main():
    with TemporaryDirectory() as tmpdir:
        tmp_path = Path(tmpdir)

        py_path = tmp_path / "jupytext-test.py"
        ipynb_path = tmp_path / "jupytext-test.ipynb"

        py_path.write_text(PYTHON_CONTENTS)

        start_time = py_path.stat().st_mtime

        time.sleep(0.01)
        # Run jupytext to convert the script to a notebook
        run(["jupytext", "--set-formats", "py:percent,ipynb", str(py_path)], check=True)
        post_pair_py_time = py_path.stat().st_mtime
        post_pair_ipynb_time = ipynb_path.stat().st_mtime
        print(
            f"Post-pairing times: py={post_pair_py_time - start_time}, ipynb={post_pair_ipynb_time - start_time}"
        )

        time.sleep(0.01)
        # touch .py file to update its timestamp
        with open(py_path, "a+") as f:
            f.write("\n# Touching the file to update its timestamp\n")

        post_touch_py_time = py_path.stat().st_mtime
        post_touch_ipynb_time = ipynb_path.stat().st_mtime
        print(
            f"Post-touch times: py={post_touch_py_time - start_time}, ipynb={post_touch_ipynb_time - start_time}"
        )

        pre_sync_py_contents = py_path.read_text()
        time.sleep(0.01)
        run(["jupytext", "--sync", str(py_path)], check=True)
        post_sync_py_time = py_path.stat().st_mtime
        post_sync_ipynb_time = ipynb_path.stat().st_mtime
        print(
            f"Post-sync times: py={post_sync_py_time - start_time}, ipynb={post_sync_ipynb_time - start_time}"
        )

        post_sync_py_contents = py_path.read_text()


if __name__ == "__main__":
    main()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions