From 5e71892e2b8215876fad69985e0502967d5c6dde Mon Sep 17 00:00:00 2001 From: Joseph Hughes Date: Wed, 5 Aug 2026 12:39:24 -0500 Subject: [PATCH 1/2] docs(create_rstfiles): order the notebook galleries by when a notebook was added The galleries were built from an alphabetical list, so a new notebook landed wherever its name happened to fall and the reader had no way to tell what had just been added. Notebooks are now ordered by the commit that added them, and a notebook that is not in the history yet, as when it is added in the same pull request, sorts last. Notebooks added in the same commit keep their alphabetical order, so the majority of the galleries are unchanged. The order falls back to alphabetical when the history is not available, as in a shallow clone, rather than failing the documentation build. --- .docs/create_rstfiles.py | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/.docs/create_rstfiles.py b/.docs/create_rstfiles.py index b63a70c567..56ad196434 100644 --- a/.docs/create_rstfiles.py +++ b/.docs/create_rstfiles.py @@ -1,8 +1,30 @@ +import subprocess from pathlib import Path project_root_path = Path(__file__).parent.parent +def added_order(path): + """Sort key that puts notebooks in the order they were added + + Notebooks added in the same commit keep their alphabetical order, and one + that is not in the history yet sorts last. The order falls back to + alphabetical when the history is not available, as in a shallow clone. + """ + try: + added = subprocess.run( + ["git", "log", "--diff-filter=A", "--format=%at", "-1", "--", str(path)], + capture_output=True, + text=True, + cwd=project_root_path, + timeout=10, + check=False, + ).stdout.strip() + except (OSError, subprocess.SubprocessError): + added = "" + return (int(added) if added else 2**31, path.name) + + def get_section(f): lines = Path(f).open().readlines() line = next(iter([l for l in lines if "# section:" in l]), None) @@ -33,9 +55,11 @@ def create_gallery_section(f, name, title, stems): def create_tutorials_rst(): rst_path = project_root_path / ".docs" / "tutorials.rst" nbs_path = project_root_path / ".docs" / "Notebooks" - filenames = sorted( - [path.name for path in nbs_path.rglob("*.py") if "tutorial" in path.name] - ) + filenames = [ + path.name + for path in sorted(nbs_path.rglob("*.py"), key=added_order) + if "tutorial" in path.name + ] print(f"Creating {rst_path}") with open(rst_path, "w") as rst_file: @@ -73,9 +97,11 @@ def create_tutorials_rst(): def create_examples_rst(): rst_path = project_root_path / ".docs" / "examples.rst" nbs_path = project_root_path / ".docs" / "Notebooks" - filenames = sorted( - [path.name for path in nbs_path.rglob("*.py") if "example" in path.name] - ) + filenames = [ + path.name + for path in sorted(nbs_path.rglob("*.py"), key=added_order) + if "example" in path.name + ] print(f"Creating {rst_path}") with open(rst_path, "w") as rst_file: From c2be913d32e5ae581ccd78d848d77bc1aa3c9cbb Mon Sep 17 00:00:00 2001 From: Joseph Hughes Date: Wed, 5 Aug 2026 13:37:48 -0500 Subject: [PATCH 2/2] docs(create_rstfiles): make the notebook ordering cheaper and more portable The sort key ran git log on an absolute path, which is not a pathspec git accepts everywhere and would have silently fallen back to alphabetical order. It also used a sentinel that stops sorting an unreleased notebook last in 2038, sorted every notebook before filtering by name, and ran once for the tutorials and again for the examples. The pathspec is now relative to the repository, the sentinel is infinity, the notebooks are filtered before they are sorted, and the key is cached. That is 84 calls to git log rather than 174. --- .docs/create_rstfiles.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.docs/create_rstfiles.py b/.docs/create_rstfiles.py index 56ad196434..a2d99a62d0 100644 --- a/.docs/create_rstfiles.py +++ b/.docs/create_rstfiles.py @@ -1,9 +1,11 @@ import subprocess +from functools import lru_cache from pathlib import Path project_root_path = Path(__file__).parent.parent +@lru_cache(maxsize=None) def added_order(path): """Sort key that puts notebooks in the order they were added @@ -12,17 +14,19 @@ def added_order(path): alphabetical when the history is not available, as in a shallow clone. """ try: + # git wants a pathspec relative to the repository it is run in + pathspec = str(path.relative_to(project_root_path)) added = subprocess.run( - ["git", "log", "--diff-filter=A", "--format=%at", "-1", "--", str(path)], + ["git", "log", "--diff-filter=A", "--format=%at", "-1", "--", pathspec], capture_output=True, text=True, cwd=project_root_path, timeout=10, check=False, ).stdout.strip() - except (OSError, subprocess.SubprocessError): + except (OSError, ValueError, subprocess.SubprocessError): added = "" - return (int(added) if added else 2**31, path.name) + return (float(added) if added else float("inf"), path.name) def get_section(f): @@ -57,8 +61,10 @@ def create_tutorials_rst(): nbs_path = project_root_path / ".docs" / "Notebooks" filenames = [ path.name - for path in sorted(nbs_path.rglob("*.py"), key=added_order) - if "tutorial" in path.name + for path in sorted( + (p for p in nbs_path.rglob("*.py") if "tutorial" in p.name), + key=added_order, + ) ] print(f"Creating {rst_path}") @@ -99,8 +105,10 @@ def create_examples_rst(): nbs_path = project_root_path / ".docs" / "Notebooks" filenames = [ path.name - for path in sorted(nbs_path.rglob("*.py"), key=added_order) - if "example" in path.name + for path in sorted( + (p for p in nbs_path.rglob("*.py") if "example" in p.name), + key=added_order, + ) ] print(f"Creating {rst_path}")