#!/usr/bin/env python3 # upstream-bump.py — sube el pin de lo NUESTRO, de a un repositorio entero. # # ══ QUÉ SUBE Y QUÉ NO ═════════════════════════════════════════════════════════════════════════ # Sólo recetas cuyo `repo` case con `--only` (por defecto `tawasuyu`). **Terceros no**, y no es # pereza: una versión nueva de upstream puede cambiar el sistema de build, las banderas, las # dependencias y el resultado, así que aplicarla sola es cambiar la receta sin leerla. Para eso # está la vigía, que PROPONE (SDD 33 §3). # # ⚠ **EL BUMP ES ATÓMICO POR REPOSITORIO, y ésa es la razón de que este guion exista.** tawasuyu # son 37 recetas sobre el MISMO monorepo. Subirlas de a una da una imagen con 37 commits distintos # del mismo árbol — una mezcla que nunca existió junta y que nadie probó. O saltan todas al mismo # commit, o no salta ninguna. # # No construye y no commitea: deja el árbol modificado y dice qué tocó. Construir es del worker # (con su `flock`) y commitear es de quien mira el resultado. # # Uso: scripts/fuentes/upstream-bump.py [--only tawasuyu] [--branch main] [--apply] # Sin `--apply` no escribe nada: dice qué haría. import argparse, glob, os, re, subprocess, sys RAIZ = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) def recetas(): for r in sorted(set(glob.glob(os.path.join(RAIZ, 'recipes/*.toml')) + glob.glob(os.path.join(RAIZ, 'recipes/*/*.toml')))): txt = open(r, encoding='utf-8', errors='replace').read() repo = re.search(r'^\s*repo\s*=\s*"([^"]+)"', txt, re.M) com = re.search(r'^(\s*commit\s*=\s*")([0-9a-fA-F]{7,40})(")', txt, re.M) if repo and com: yield os.path.relpath(r, RAIZ), repo.group(1), com.group(2), txt def cabeza(repo, rama, timeout=30): env = dict(os.environ, GIT_TERMINAL_PROMPT='0', GIT_ASKPASS='/bin/true') p = subprocess.run(['git', 'ls-remote', repo, 'refs/heads/' + rama], capture_output=True, text=True, timeout=timeout, env=env) if p.returncode != 0 or not p.stdout.strip(): return None return p.stdout.split()[0] def main(): ap = argparse.ArgumentParser(description='sube el pin de un repositorio propio, entero') ap.add_argument('--only', default='tawasuyu', help='subcadena que el `repo` tiene que contener') ap.add_argument('--branch', default='main') ap.add_argument('--apply', action='store_true', help='escribir; sin esto es un ensayo') a = ap.parse_args() porrepo = {} for receta, repo, commit, txt in recetas(): if a.only not in repo: continue porrepo.setdefault(repo, []).append((receta, commit, txt)) if not porrepo: print('no hay ninguna receta cuyo repo contenga «%s»' % a.only) return 0 tocadas = 0 for repo, filas in sorted(porrepo.items()): nuevo = cabeza(repo, a.branch) if not nuevo: print('✗ %s — no pude leer refs/heads/%s' % (repo, a.branch)) continue pins = sorted({c for _, c, _ in filas}) print('== %s' % repo) print(' %d receta(s) · %d pin(s) distinto(s) · cabeza de %s: %s' % (len(filas), len(pins), a.branch, nuevo[:12])) # ⚠ Si las recetas del mismo repo ya venían con pins DISTINTOS, el árbol estaba mezclado # antes de que este guion tocara nada. Subirlas todas lo arregla, pero se dice: es un dato # sobre cómo se llegó hasta acá, y callarlo haría parecer que el estado anterior era sano. if len(pins) > 1: print(' ⚠ venían con %d pins distintos: el árbol ya estaba mezclado' % len(pins)) if len(pins) == 1 and pins[0] == nuevo: print(' = ya está en la cabeza; nada que hacer') continue for receta, commit, txt in filas: if commit == nuevo: continue nuevotxt = re.sub(r'^(\s*commit\s*=\s*")([0-9a-fA-F]{7,40})(")', lambda m: m.group(1) + nuevo + m.group(3), txt, count=1, flags=re.M) if nuevotxt == txt: print(' ✗ %s — no pude reescribir el commit' % receta) continue if a.apply: open(os.path.join(RAIZ, receta), 'w', encoding='utf-8').write(nuevotxt) print(' %s %-44s %s… → %s…' % ('·' if a.apply else '~', receta, commit[:10], nuevo[:10])) tocadas += 1 print() if a.apply: print('%d receta(s) reescritas. NO se construyó ni se commiteó nada:' % tocadas) print(' · construir es del worker, con su flock') print(' · commitear es de quien mire el resultado (y acotado por pathspec, regla 2)') else: print('%d receta(s) subirían. Ensayo: no se tocó nada (pasá --apply).' % tocadas) return 0 if __name__ == '__main__': sys.exit(main())