Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a478d92
fix: preserve module exports when rebasing import PRs
Vilin97 Sep 11, 2026
9fa39b9
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 11, 2026
5f175b2
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 11, 2026
2693412
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 11, 2026
7b8ef52
Merge remote-tracking branch 'origin/main' into HEAD
Vilin97 Sep 11, 2026
8eb80e9
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 12, 2026
7ac4463
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 13, 2026
008390f
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 13, 2026
dc48640
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 13, 2026
59bac6a
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 14, 2026
7e36c12
Preserve module exports across independent conflict sides
Sep 15, 2026
c474ce7
Merge remote-tracking branch 'origin/main' into HEAD
Sep 15, 2026
1f6ec8f
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 16, 2026
a78b00b
Merge current main to validate module export preservation
Sep 16, 2026
ad25bf8
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
eec138a
Merge remote-tracking branch 'origin/main' into HEAD
Sep 17, 2026
20e5673
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
a115c9e
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
fd7c57a
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
3bd1bfb
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
2122afd
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
993cab9
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
85dcb8c
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
1799c9a
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
755595c
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 17, 2026
ee5a7ae
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
ebef27b
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
4eb23ad
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
17b7d07
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
62fd046
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
d353246
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
05fdef7
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
fdb0d9a
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
009f237
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
f019eb1
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
71d6bc7
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 18, 2026
02c1f78
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 19, 2026
2c27ddb
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 19, 2026
c0bc90c
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 20, 2026
48c1e8a
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 21, 2026
55de373
Merge remote-tracking branch 'origin/main' into HEAD
Vilin97 Sep 21, 2026
826e8d6
Document import rebase behavior and module exports
Vilin97 Sep 21, 2026
dad108a
Merge remote-tracking branch 'origin/main' into HEAD
Vilin97 Sep 21, 2026
9ed4b11
Merge remote-tracking branch 'origin/main' into pr-416
github-actions[bot] Sep 21, 2026
acffc14
Merge remote-tracking branch 'origin/codex/module-tooling-compatibili…
Vilin97 Sep 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ See [Challenge mode](CONTRIBUTING.md#challenge-mode) in `CONTRIBUTING.md`.

See [`CONTRIBUTING.md`](CONTRIBUTING.md).

Import PRs can be refreshed automatically after other projects merge. The
[rebase helper](python/lean_pool/rebase.py) resolves conflicts in the project registry
and generated index, preserving module headers and public imports when the index uses them.
Conflicts in proof files require a manual rebase.

### Credits

Created as part of the [UW Lean Hackathon](https://uw2026leanhackathon.github.io/) by [Vasily Ilin](https://github.com/Vilin97) and [Justin Asher](https://github.com/justincasher).
Expand Down
32 changes: 31 additions & 1 deletion python/lean_pool/rebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@

import argparse
import logging
import re
import sys
from pathlib import Path

from lean_pool.exposition.source_text import code_view

logger = logging.getLogger(__name__)

REGISTRY = "LeanPool/projects.yml"
Expand All @@ -37,6 +40,26 @@
RESOLVABLE = frozenset({INDEX, REGISTRY})


def _uses_module_system(source: str) -> bool:
"""Detect a module header in either current version of a conflicted index."""
conflict = re.compile(
r"^<<<<<<<[^\n]*\n(?P<ours>.*?)"
r"(?:^\|{7}[^\n]*\n.*?)?^=======\n"
r"(?P<theirs>.*?)^>>>>>>>[^\n]*(?:\n|$)",
re.MULTILINE | re.DOTALL,
)
# Keep shared text in both versions so comments spanning a conflict remain
# comments, while a malformed comment on one side cannot mask the other.
return any(
re.search(
r"^\s*module(?:\s|$)",
code_view(conflict.sub(lambda match: match[side], source)),
re.MULTILINE,
)
for side in ("ours", "theirs")
Comment thread
Vilin97 marked this conversation as resolved.
)


def render_index(root: Path) -> str:
"""Regenerate the ``mk_all`` index from the Lean files on disk."""
pool = root / "LeanPool"
Expand All @@ -45,7 +68,14 @@ def render_index(root: Path) -> str:
+ str(path.relative_to(pool)).removesuffix(".lean").replace("/", ".")
for path in pool.rglob("*.lean")
)
return "".join(f"import {module}\n" for module in modules)
index = root / INDEX
existing = index.read_text(encoding="utf-8") if index.exists() else ""
uses_modules = _uses_module_system(existing)
header = "module -- shake: keep-all --deprecated_module: ignore\n\n"
prefix = "public " if uses_modules else ""
return (header if uses_modules else "") + "".join(
f"{prefix}import {module}\n" for module in modules
)


def split_cards(text: str) -> tuple[str, list[tuple[str, str]]]:
Expand Down
67 changes: 67 additions & 0 deletions python/tests/test_rebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from pathlib import Path

import pytest

from lean_pool.rebase import (
main,
merge_registry,
Expand Down Expand Up @@ -78,6 +80,71 @@ def test_render_index_reproduces_the_committed_index() -> None:
assert render_index(root) == (root / "LeanPool.lean").read_text(encoding="utf-8")


def test_render_index_preserves_module_exports_during_conflicts(tmp_path: Path) -> None:
"""A conflicting import PR cannot revert the library to plain imports."""
pool = tmp_path / "LeanPool"
pool.mkdir()
(pool / "Alpha.lean").touch()
(pool / "Beta.lean").touch()
(tmp_path / "LeanPool.lean").write_text(
"<<<<<<< HEAD\nmodule -- shake: keep-all --deprecated_module: ignore\n\n"
"public import LeanPool.Alpha\n=======\n"
"import LeanPool.Beta\n>>>>>>> incoming\n"
)
expected = (
"module -- shake: keep-all --deprecated_module: ignore\n\n"
"public import LeanPool.Alpha\npublic import LeanPool.Beta\n"
)
assert render_index(tmp_path) == expected
(tmp_path / "LeanPool.lean").write_text(expected)
assert render_index(tmp_path) == expected


def test_render_index_ignores_module_inside_comments(tmp_path: Path) -> None:
"""Documentation cannot accidentally opt an index into the module system."""
(tmp_path / "LeanPool").mkdir()
(tmp_path / "LeanPool" / "Alpha.lean").touch()
(tmp_path / "LeanPool.lean").write_text(
"/-\nmodule\n/- nested -/\n-/\nimport LeanPool.Alpha\n"
)
assert render_index(tmp_path) == "import LeanPool.Alpha\n"


@pytest.mark.parametrize("unfinished", ["/- unclosed comment", '"unclosed string'])
def test_render_index_scans_conflict_sides_independently(
tmp_path: Path, unfinished: str
) -> None:
"""Lexical state on one conflict side cannot hide the other side's header."""
(tmp_path / "LeanPool").mkdir()
(tmp_path / "LeanPool" / "Alpha.lean").touch()
(tmp_path / "LeanPool.lean").write_text(
f"<<<<<<< HEAD\n{unfinished}\n=======\nmodule\n>>>>>>> incoming\n"
)
assert render_index(tmp_path) == (
"module -- shake: keep-all --deprecated_module: ignore\n\n"
"public import LeanPool.Alpha\n"
)


@pytest.mark.parametrize(
"source",
[
"/- shared comment\n<<<<<<< HEAD\nmodule\n=======\nmodule\n"
">>>>>>> incoming\n-/\nimport LeanPool.Alpha\n",
"<<<<<<< HEAD\nimport LeanPool.Alpha\n||||||| base\nmodule\n"
"=======\nimport LeanPool.Alpha\n>>>>>>> incoming\n",
],
)
def test_render_index_ignores_noncode_and_base_conflict_headers(
tmp_path: Path, source: str
) -> None:
"""Only a code header in a current side can opt into the module system."""
(tmp_path / "LeanPool").mkdir()
(tmp_path / "LeanPool" / "Alpha.lean").touch()
(tmp_path / "LeanPool.lean").write_text(source)
assert render_index(tmp_path) == "import LeanPool.Alpha\n"


# --------------------------------------------------------------------------- #
# Registry merging
# --------------------------------------------------------------------------- #
Expand Down
Loading