Compare commits

..

No commits in common. "3cb2c862175b753c012b2439cb3ee2c5e072570d" and "fd29bf93eedc960e0199f2e332d85ddc85cc4af8" have entirely different histories.

View File

@ -12,11 +12,6 @@ Loop prevention, which was structural back when only Spanish could be a source:
never itself treated as a source; never itself treated as a source;
* the bot's own commits carry [skip-translate] and are skipped outright. * the bot's own commits carry [skip-translate] and are skipped outright.
Deletion is handled too, and has to be: removing a post in the CMS deletes one
file, the original, and the siblings this script wrote would otherwise stay on
the site as posts in a language with no original. Any sibling whose source has
disappeared is removed, unless it is frozen.
`manual_translation: true` means "hands off", on both sides: `manual_translation: true` means "hands off", on both sides:
* on an authored source — do not generate siblings for this post at all; * on an authored source — do not generate siblings for this post at all;
* on a generated sibling — never overwrite it again. * on a generated sibling — never overwrite it again.
@ -185,41 +180,6 @@ def missing_siblings() -> list[Path]:
return incomplete return incomplete
def orphaned_siblings() -> list[Path]:
"""Generated siblings whose source no longer exists.
Deleting a post in the CMS removes one file — the Spanish original. The
German and Portuguese siblings were written by this script, not by the
author, so nothing else deletes them and they stay on the site as posts in
a language that has no original. A rename leaves the same debris, since it
is a delete plus an add.
This scans the whole content tree rather than the push diff, so it also
clears siblings orphaned by earlier runs that predate this check.
"""
orphans = []
for path in sorted(CONTENT_DIR.rglob("*.md")):
parsed = split_lang(path)
if not parsed:
continue
basename, lang = parsed
fm = read_frontmatter(path)
source_lang = fm.get("translated_from")
if not source_lang:
continue # authored by a human; only its author deletes it
if path.with_name(f"{basename}.{source_lang}.md").exists():
continue
rel = path.relative_to(REPO_ROOT)
if is_frozen(fm):
# Someone edited this translation by hand. Deleting it would throw
# that work away on the strength of an inference, so say so instead.
print(f" {rel}: source is gone but manual_translation=true — left in place; "
f"delete it by hand if the post is meant to disappear")
continue
orphans.append(path)
return orphans
def translate_file(source: Path, basename: str, src_lang: str, provider) -> list[Path]: def translate_file(source: Path, basename: str, src_lang: str, provider) -> list[Path]:
fm, body = split_frontmatter(source.read_text(encoding="utf-8")) fm, body = split_frontmatter(source.read_text(encoding="utf-8"))
written: list[Path] = [] written: list[Path] = []
@ -258,7 +218,7 @@ def translate_file(source: Path, basename: str, src_lang: str, provider) -> list
return written return written
def commit_and_push(files: list[Path], summary: str) -> None: def commit_and_push(files: list[Path]) -> None:
branch = os.environ.get("CI_COMMIT_BRANCH", "main") branch = os.environ.get("CI_COMMIT_BRANCH", "main")
run("git", "config", "user.name", BOT_NAME) run("git", "config", "user.name", BOT_NAME)
run("git", "config", "user.email", BOT_EMAIL) run("git", "config", "user.email", BOT_EMAIL)
@ -267,13 +227,11 @@ def commit_and_push(files: list[Path], summary: str) -> None:
if token: # clone credentials are read-only; pushing needs the bot token if token: # clone credentials are read-only; pushing needs the bot token
run("git", "remote", "set-url", "origin", run("git", "remote", "set-url", "origin",
f"https://{BOT_NAME}:{token}@git.vienalatina.com/{repo}.git") f"https://{BOT_NAME}:{token}@git.vienalatina.com/{repo}.git")
# --all so a path that no longer exists stages as a deletion rather than run("git", "add", *[str(f) for f in files])
# failing; reaped orphans arrive here alongside freshly written siblings.
run("git", "add", "--all", "--", *[str(f) for f in files])
if not run("git", "status", "--porcelain"): if not run("git", "status", "--porcelain"):
print("Translations identical to committed siblings — nothing to push.") print("Translations identical to committed siblings — nothing to push.")
return return
run("git", "commit", "-m", f"translate: {summary} {SKIP_MARKER}") run("git", "commit", "-m", f"translate: update generated siblings {SKIP_MARKER}")
run("git", "push", "origin", f"HEAD:{branch}") run("git", "push", "origin", f"HEAD:{branch}")
print(f"Pushed sibling commit to {branch}.") print(f"Pushed sibling commit to {branch}.")
@ -292,39 +250,23 @@ def main() -> None:
paths = missing_siblings() if args.backfill else changed_markdown() paths = missing_siblings() if args.backfill else changed_markdown()
sources = authored_sources(paths) sources = authored_sources(paths)
if not sources:
print("No authored content changed — nothing to translate.")
return
written: list[Path] = []
if sources:
provider = build_provider() provider = build_provider()
written: list[Path] = []
for source, basename, lang in sources: for source, basename, lang in sources:
print(f"Translating {source.relative_to(REPO_ROOT)} (from {lang}):") print(f"Translating {source.relative_to(REPO_ROOT)} (from {lang}):")
written.extend(translate_file(source, basename, lang, provider)) written.extend(translate_file(source, basename, lang, provider))
else:
print("No authored content changed — nothing to translate.")
# Runs whether or not anything was translated: a push that only deletes if not written:
# posts reaches this point with no sources at all, and that is exactly the print("Every sibling is frozen — nothing written.")
# case where siblings are left stranded.
removed = []
for orphan in orphaned_siblings():
orphan.unlink()
removed.append(orphan)
print(f" {orphan.relative_to(REPO_ROOT)}: source deleted — removed")
touched = written + removed
if not touched:
print("Nothing to commit.")
return return
if args.no_push: if args.no_push:
print(f"--no-push: wrote {len(written)} and removed {len(removed)} files, " print(f"--no-push: wrote {len(written)} files, leaving them uncommitted.")
f"leaving them uncommitted.")
return return
parts = [] commit_and_push(written)
if written:
parts.append(f"update {len(written)} generated siblings")
if removed:
parts.append(f"remove {len(removed)} orphaned by a deleted source")
commit_and_push(touched, " and ".join(parts))
if __name__ == "__main__": if __name__ == "__main__":