mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
[update_lib] todo shows last updated date (#7053)
This commit is contained in:
1 parent
15cd2e3764
commit
ea1b917712
2 files changed
+160
-4
No files matched your search
@@ -15,6 +15,9 @@ sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
|
||||
|
||||
from update_lib.deps import (
|
||||
count_test_todos,
|
||||
get_module_diff_stat,
|
||||
get_module_last_updated,
|
||||
get_test_last_updated,
|
||||
is_test_tracked,
|
||||
is_test_up_to_date,
|
||||
)
|
||||
@@ -368,6 +371,18 @@ def compute_test_todo_list(
|
||||
return result
|
||||
|
||||
|
||||
def _format_meta_suffix(item: dict) -> str:
|
||||
"""Format metadata suffix (last updated date and diff count)."""
|
||||
parts = []
|
||||
last_updated = item.get("last_updated")
|
||||
diff_lines = item.get("diff_lines", 0)
|
||||
if last_updated:
|
||||
parts.append(last_updated)
|
||||
if diff_lines > 0:
|
||||
parts.append(f"Δ{diff_lines}")
|
||||
return f" | {' '.join(parts)}" if parts else ""
|
||||
|
||||
|
||||
def _format_test_suffix(item: dict) -> str:
|
||||
"""Format suffix for test item (TODO count or untracked)."""
|
||||
tracked = item.get("tracked", True)
|
||||
@@ -410,13 +425,15 @@ def format_test_todo_list(
|
||||
primary = tests[0]
|
||||
done_mark = "[x]" if primary["up_to_date"] else "[ ]"
|
||||
suffix = _format_test_suffix(primary)
|
||||
lines.append(f"- {done_mark} {primary['name']}{suffix}")
|
||||
meta = _format_meta_suffix(primary)
|
||||
lines.append(f"- {done_mark} {primary['name']}{suffix}{meta}")
|
||||
|
||||
# Rest are indented
|
||||
for item in tests[1:]:
|
||||
done_mark = "[x]" if item["up_to_date"] else "[ ]"
|
||||
suffix = _format_test_suffix(item)
|
||||
lines.append(f" - {done_mark} {item['name']}{suffix}")
|
||||
meta = _format_meta_suffix(item)
|
||||
lines.append(f" - {done_mark} {item['name']}{suffix}{meta}")
|
||||
|
||||
return lines
|
||||
|
||||
@@ -462,7 +479,8 @@ def format_todo_list(
|
||||
if rev_str:
|
||||
parts.append(f"({rev_str})")
|
||||
|
||||
lines.append(" ".join(parts))
|
||||
line = " ".join(parts) + _format_meta_suffix(item)
|
||||
lines.append(line)
|
||||
|
||||
# Show hard_deps:
|
||||
# - Normal mode: only show if lib is up-to-date but hard_deps are not
|
||||
@@ -482,7 +500,8 @@ def format_todo_list(
|
||||
for test_info in test_by_lib[name]:
|
||||
test_done_mark = "[x]" if test_info["up_to_date"] else "[ ]"
|
||||
suffix = _format_test_suffix(test_info)
|
||||
lines.append(f" - {test_done_mark} {test_info['name']}{suffix}")
|
||||
meta = _format_meta_suffix(test_info)
|
||||
lines.append(f" - {test_done_mark} {test_info['name']}{suffix}{meta}")
|
||||
|
||||
# Verbose mode: show detailed dependency info
|
||||
if verbose:
|
||||
@@ -556,6 +575,29 @@ def format_all_todo(
|
||||
if include_done or lib_not_done or has_pending_test:
|
||||
lib_todo.append(item)
|
||||
|
||||
# Add metadata (last updated date and diff stat) to lib items
|
||||
for item in lib_todo:
|
||||
item["last_updated"] = get_module_last_updated(
|
||||
item["name"], cpython_prefix, lib_prefix
|
||||
)
|
||||
item["diff_lines"] = (
|
||||
0
|
||||
if item["up_to_date"]
|
||||
else get_module_diff_stat(item["name"], cpython_prefix, lib_prefix)
|
||||
)
|
||||
|
||||
# Add last_updated to displayed test items (verbose only - slow)
|
||||
if verbose:
|
||||
for tests in test_by_lib.values():
|
||||
for test in tests:
|
||||
test["last_updated"] = get_test_last_updated(
|
||||
test["name"], cpython_prefix, lib_prefix
|
||||
)
|
||||
for test in no_lib_tests:
|
||||
test["last_updated"] = get_test_last_updated(
|
||||
test["name"], cpython_prefix, lib_prefix
|
||||
)
|
||||
|
||||
# Format lib todo with embedded tests
|
||||
lines.extend(format_todo_list(lib_todo, test_by_lib, limit, verbose))
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ Handles:
|
||||
"""
|
||||
|
||||
import ast
|
||||
import difflib
|
||||
import functools
|
||||
import pathlib
|
||||
import re
|
||||
@@ -1011,6 +1012,119 @@ def is_up_to_date(name: str, cpython_prefix: str, lib_prefix: str) -> bool:
|
||||
return found_any
|
||||
|
||||
|
||||
def _count_file_diff(file_a: pathlib.Path, file_b: pathlib.Path) -> int:
|
||||
"""Count changed lines between two text files using difflib."""
|
||||
a_content = safe_read_text(file_a)
|
||||
b_content = safe_read_text(file_b)
|
||||
if a_content is None or b_content is None:
|
||||
return 0
|
||||
if a_content == b_content:
|
||||
return 0
|
||||
a_lines = a_content.splitlines()
|
||||
b_lines = b_content.splitlines()
|
||||
count = 0
|
||||
for line in difflib.unified_diff(a_lines, b_lines, lineterm=""):
|
||||
if (line.startswith("+") and not line.startswith("+++")) or (
|
||||
line.startswith("-") and not line.startswith("---")
|
||||
):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def _count_path_diff(path_a: pathlib.Path, path_b: pathlib.Path) -> int:
|
||||
"""Count changed lines between two paths (file or directory, *.py only)."""
|
||||
if path_a.is_file() and path_b.is_file():
|
||||
return _count_file_diff(path_a, path_b)
|
||||
if path_a.is_dir() and path_b.is_dir():
|
||||
total = 0
|
||||
a_files = {f.relative_to(path_a) for f in path_a.rglob("*.py")}
|
||||
b_files = {f.relative_to(path_b) for f in path_b.rglob("*.py")}
|
||||
for rel in a_files & b_files:
|
||||
total += _count_file_diff(path_a / rel, path_b / rel)
|
||||
for rel in a_files - b_files:
|
||||
content = safe_read_text(path_a / rel)
|
||||
if content:
|
||||
total += len(content.splitlines())
|
||||
for rel in b_files - a_files:
|
||||
content = safe_read_text(path_b / rel)
|
||||
if content:
|
||||
total += len(content.splitlines())
|
||||
return total
|
||||
return 0
|
||||
|
||||
|
||||
def get_module_last_updated(
|
||||
name: str, cpython_prefix: str, lib_prefix: str
|
||||
) -> str | None:
|
||||
"""Get the last git commit date for a module's Lib files."""
|
||||
local_paths = []
|
||||
for cpython_path in get_lib_paths(name, cpython_prefix):
|
||||
if not cpython_path.exists():
|
||||
continue
|
||||
try:
|
||||
rel_path = cpython_path.relative_to(cpython_prefix)
|
||||
local_path = pathlib.Path(lib_prefix) / rel_path.relative_to("Lib")
|
||||
if local_path.exists():
|
||||
local_paths.append(str(local_path))
|
||||
except ValueError:
|
||||
continue
|
||||
if not local_paths:
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "log", "-1", "--format=%cd", "--date=short", "--"] + local_paths,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
return result.stdout.strip()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def get_module_diff_stat(name: str, cpython_prefix: str, lib_prefix: str) -> int:
|
||||
"""Count differing lines between cpython and local Lib for a module."""
|
||||
total = 0
|
||||
for cpython_path in get_lib_paths(name, cpython_prefix):
|
||||
if not cpython_path.exists():
|
||||
continue
|
||||
try:
|
||||
rel_path = cpython_path.relative_to(cpython_prefix)
|
||||
local_path = pathlib.Path(lib_prefix) / rel_path.relative_to("Lib")
|
||||
except ValueError:
|
||||
continue
|
||||
if not local_path.exists():
|
||||
continue
|
||||
total += _count_path_diff(cpython_path, local_path)
|
||||
return total
|
||||
|
||||
|
||||
def get_test_last_updated(
|
||||
test_name: str, cpython_prefix: str, lib_prefix: str
|
||||
) -> str | None:
|
||||
"""Get the last git commit date for a test's files."""
|
||||
cpython_path = _get_cpython_test_path(test_name, cpython_prefix)
|
||||
if cpython_path is None:
|
||||
return None
|
||||
local_path = _get_local_test_path(cpython_path, lib_prefix)
|
||||
if not local_path.exists():
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "log", "-1", "--format=%cd", "--date=short", "--", str(local_path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
return result.stdout.strip()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def get_test_dependencies(
|
||||
test_path: pathlib.Path,
|
||||
) -> dict[str, list[pathlib.Path]]:
|
||||
|
||||
Reference in new issue
Block a user