Initial import of the codespaces skill: query .belief_map.sexp for module boundaries, dependencies, blast radius, and the minimal files to read before non-trivial code changes. Includes the belief-map and infra-topology builders, the belief-search query engine and its tests, and reference docs for the S-expression notation and query patterns.
125 lines
3.6 KiB
Python
Executable File
125 lines
3.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Git Descendants Finder -- find all commits that descend from HEAD.
|
|
|
|
Builds a parent-child graph from git rev-list, then BFS-walks forward
|
|
from HEAD to find all descendant commits. Useful for understanding
|
|
what work was built on top of a given commit.
|
|
|
|
Usage::
|
|
|
|
git_descendants.py # descendants of HEAD
|
|
git_descendants.py <ref> # descendants of a specific commit/ref
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
def run_git(cmd: str, cwd: str | None = None) -> list[str]:
|
|
"""Run a git command and return output lines."""
|
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, cwd=cwd)
|
|
if result.returncode != 0:
|
|
print(f"Error running: {cmd}", file=sys.stderr)
|
|
print(result.stderr, file=sys.stderr)
|
|
sys.exit(1)
|
|
return result.stdout.strip().split("\n") if result.stdout.strip() else []
|
|
|
|
|
|
def get_commit_sha(ref: str = "HEAD", cwd: str | None = None) -> str:
|
|
"""Resolve a ref to a full SHA."""
|
|
result = subprocess.run(
|
|
["git", "rev-parse", ref], capture_output=True, text=True, cwd=cwd,
|
|
)
|
|
if result.returncode != 0:
|
|
print(f"Error: cannot resolve ref '{ref}'", file=sys.stderr)
|
|
sys.exit(1)
|
|
return result.stdout.strip()
|
|
|
|
|
|
def build_parent_child_map(
|
|
cwd: str | None = None,
|
|
) -> tuple[dict[str, str], dict[str, list[str]]]:
|
|
"""Build a map of commit -> leftmost parent and parent -> children."""
|
|
print("Building commit graph...", file=sys.stderr)
|
|
|
|
commits = run_git("git rev-list --all --reflog --parents", cwd=cwd)
|
|
|
|
parent_map: dict[str, str] = {}
|
|
children_map: dict[str, list[str]] = {}
|
|
|
|
for line in commits:
|
|
parts = line.split()
|
|
if not parts:
|
|
continue
|
|
|
|
commit = parts[0]
|
|
parents = parts[1:]
|
|
|
|
if parents:
|
|
leftmost_parent = parents[0]
|
|
parent_map[commit] = leftmost_parent
|
|
|
|
if leftmost_parent not in children_map:
|
|
children_map[leftmost_parent] = []
|
|
children_map[leftmost_parent].append(commit)
|
|
|
|
print(f"Found {len(parent_map)} commits with parents", file=sys.stderr)
|
|
return parent_map, children_map
|
|
|
|
|
|
def find_all_descendants(
|
|
commit: str, children_map: dict[str, list[str]],
|
|
) -> set[str]:
|
|
"""Find all descendants of a commit using BFS."""
|
|
descendants: set[str] = set()
|
|
queue = [commit]
|
|
|
|
while queue:
|
|
current = queue.pop(0)
|
|
|
|
if current in children_map:
|
|
for child in children_map[current]:
|
|
if child not in descendants:
|
|
descendants.add(child)
|
|
queue.append(child)
|
|
|
|
return descendants
|
|
|
|
|
|
def main() -> None:
|
|
ref = sys.argv[1] if len(sys.argv) > 1 else "HEAD"
|
|
|
|
full_sha = get_commit_sha(ref)
|
|
short_sha = subprocess.run(
|
|
["git", "rev-parse", "--short", full_sha],
|
|
capture_output=True, text=True,
|
|
).stdout.strip()
|
|
|
|
print(f"Finding descendants of {ref}: {short_sha}", file=sys.stderr)
|
|
print(f"Full SHA: {full_sha}", file=sys.stderr)
|
|
print(file=sys.stderr)
|
|
|
|
_parent_map, children_map = build_parent_child_map()
|
|
|
|
descendants = find_all_descendants(full_sha, children_map)
|
|
|
|
print(f"Found {len(descendants)} descendant commits", file=sys.stderr)
|
|
print(file=sys.stderr)
|
|
|
|
if descendants:
|
|
for commit in sorted(descendants):
|
|
commit_info = subprocess.run(
|
|
["git", "log", "-1", "--format=%h %s", commit],
|
|
capture_output=True, text=True,
|
|
).stdout.strip()
|
|
print(commit_info)
|
|
else:
|
|
print("No descendants found", file=sys.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|