gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

⎇ main scripts/fuentes/upstream-bump.py Python · 102 siq'ikuna · 5.0 KB Ñawpa willay Pi imata llamkarqan ch'uya

SDD 33 + la vigía de río arriba: 660 fuentes git en 17 s, y 462 tienen algo más nuevo a13edfa9c · Sergio ·

1#!/usr/bin/env python3
2# upstream-bump.py — sube el pin de lo NUESTRO, de a un repositorio entero.
3#
4# ══ QUÉ SUBE Y QUÉ NO ═════════════════════════════════════════════════════════════════════════
5# Sólo recetas cuyo `repo` case con `--only` (por defecto `tawasuyu`). **Terceros no**, y no es
6# pereza: una versión nueva de upstream puede cambiar el sistema de build, las banderas, las
7# dependencias y el resultado, así que aplicarla sola es cambiar la receta sin leerla. Para eso
8# está la vigía, que PROPONE (SDD 33 §3).
9#
10# ⚠ **EL BUMP ES ATÓMICO POR REPOSITORIO, y ésa es la razón de que este guion exista.** tawasuyu
11# son 37 recetas sobre el MISMO monorepo. Subirlas de a una da una imagen con 37 commits distintos
12# del mismo árbol — una mezcla que nunca existió junta y que nadie probó. O saltan todas al mismo
13# commit, o no salta ninguna.
14#
15# No construye y no commitea: deja el árbol modificado y dice qué tocó. Construir es del worker
16# (con su `flock`) y commitear es de quien mira el resultado.
17#
18# Uso:  scripts/fuentes/upstream-bump.py [--only tawasuyu] [--branch main] [--apply]
19#       Sin `--apply` no escribe nada: dice qué haría.
20import argparse, glob, os, re, subprocess, sys
21
22RAIZ = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
23
24
25def recetas():
26    for r in sorted(set(glob.glob(os.path.join(RAIZ, 'recipes/*.toml'))
27                        + glob.glob(os.path.join(RAIZ, 'recipes/*/*.toml')))):
28        txt = open(r, encoding='utf-8', errors='replace').read()
29        repo = re.search(r'^\s*repo\s*=\s*"([^"]+)"', txt, re.M)
30        com = re.search(r'^(\s*commit\s*=\s*")([0-9a-fA-F]{7,40})(")', txt, re.M)
31        if repo and com:
32            yield os.path.relpath(r, RAIZ), repo.group(1), com.group(2), txt
33
34
35def cabeza(repo, rama, timeout=30):
36    env = dict(os.environ, GIT_TERMINAL_PROMPT='0', GIT_ASKPASS='/bin/true')
37    p = subprocess.run(['git', 'ls-remote', repo, 'refs/heads/' + rama],
38                       capture_output=True, text=True, timeout=timeout, env=env)
39    if p.returncode != 0 or not p.stdout.strip():
40        return None
41    return p.stdout.split()[0]
42
43
44def main():
45    ap = argparse.ArgumentParser(description='sube el pin de un repositorio propio, entero')
46    ap.add_argument('--only', default='tawasuyu', help='subcadena que el `repo` tiene que contener')
47    ap.add_argument('--branch', default='main')
48    ap.add_argument('--apply', action='store_true', help='escribir; sin esto es un ensayo')
49    a = ap.parse_args()
50
51    porrepo = {}
52    for receta, repo, commit, txt in recetas():
53        if a.only not in repo:
54            continue
55        porrepo.setdefault(repo, []).append((receta, commit, txt))
56    if not porrepo:
57        print('no hay ninguna receta cuyo repo contenga «%s»' % a.only)
58        return 0
59
60    tocadas = 0
61    for repo, filas in sorted(porrepo.items()):
62        nuevo = cabeza(repo, a.branch)
63        if not nuevo:
64            print('✗ %s — no pude leer refs/heads/%s' % (repo, a.branch))
65            continue
66        pins = sorted({c for _, c, _ in filas})
67        print('== %s' % repo)
68        print('   %d receta(s) · %d pin(s) distinto(s) · cabeza de %s: %s' %
69              (len(filas), len(pins), a.branch, nuevo[:12]))
70        # ⚠ Si las recetas del mismo repo ya venían con pins DISTINTOS, el árbol estaba mezclado
71        # antes de que este guion tocara nada. Subirlas todas lo arregla, pero se dice: es un dato
72        # sobre cómo se llegó hasta acá, y callarlo haría parecer que el estado anterior era sano.
73        if len(pins) > 1:
74            print('   ⚠ venían con %d pins distintos: el árbol ya estaba mezclado' % len(pins))
75        if len(pins) == 1 and pins[0] == nuevo:
76            print('   = ya está en la cabeza; nada que hacer')
77            continue
78        for receta, commit, txt in filas:
79            if commit == nuevo:
80                continue
81            nuevotxt = re.sub(r'^(\s*commit\s*=\s*")([0-9a-fA-F]{7,40})(")',
82                              lambda m: m.group(1) + nuevo + m.group(3), txt, count=1, flags=re.M)
83            if nuevotxt == txt:
84                print('   ✗ %s — no pude reescribir el commit' % receta)
85                continue
86            if a.apply:
87                open(os.path.join(RAIZ, receta), 'w', encoding='utf-8').write(nuevotxt)
88            print('   %s %-44s %s… → %s…' % ('·' if a.apply else '~', receta, commit[:10], nuevo[:10]))
89            tocadas += 1
90
91    print()
92    if a.apply:
93        print('%d receta(s) reescritas. NO se construyó ni se commiteó nada:' % tocadas)
94        print('  · construir es del worker, con su flock')
95        print('  · commitear es de quien mire el resultado (y acotado por pathspec, regla 2)')
96    else:
97        print('%d receta(s) subirían. Ensayo: no se tocó nada (pasá --apply).' % tocadas)
98    return 0
99
100
101if __name__ == '__main__':
102    sys.exit(main())