Git Filter-Repo es una herramienta versátil para reescribir el historial, que incluye capacidades que no he encontrado en ningún otro lugar. Se cae aproximadamente en el mismo espacio de herramienta que la rama de filtro GIT, pero sin el bajo rendimiento que induce la capitulación, con muchas más capacidades y con un diseño que escala en cuanto a usabilidad más allá de los casos de reescritura trivial. El proyecto Git Filter-Repo ahora es recomendado por el proyecto GIT en lugar de la rama de filtro GIT.
Si bien la mayoría de los usuarios probablemente usarán Filter-Repo como una herramienta de línea de comandos simple (y probablemente solo usen algunas de sus banderas), en su núcleo Filter-Repo contiene una biblioteca para crear herramientas de reescritura de historia. Como tal, los usuarios con necesidades especializadas pueden aprovecharlo para crear rápidamente herramientas de reescritura de historia completamente nuevas.
Filter-Repo requiere:
git-filter-repo
es un script de Python de una sola archivo, que se realizó para hacer la instalación de uso básico en muchos sistemas triviales: simplemente coloque esa archiva en su ruta $.
Consulte Install.MD para ver cosas más allá del uso básico o casos especiales. Las instrucciones más involucradas solo se necesitan si se aplica una de las siguientes opciones:
Para una documentación integral:
Si prefiere aprender de los ejemplos:
Esto se cubrió con más detalle en un artículo de Git Rev News en Filter-Repo, pero algunos aspectos destacados para los principales competidores:
La rama de filtro es extremadamente lento (múltiples órdenes de magnitud más lenta de lo que debería ser) para repositorios no triviales.
Filter Branch está plagado de gotchas que pueden corromper en silencio su reescritura o al menos frustrar sus esfuerzos de "limpieza" al darle algo más problemático y desordenado de lo que comenzó.
La rama de filtro es muy onerosa de usar para cualquier reescritura que incluso sea ligeramente no trivial.
El proyecto GIT ha declarado que los problemas anteriores con Filter Branch no se pueden solucionar compatiblemente; Recomiendan que deje de usar Filter Branch
Los ventiladores acérrimos de la rama de filtro pueden estar interesados en el filtro (también conocido como Filter-Branch-ish), una reimplementación de la rama de filtro basada en Filter-Repo que es más perfilante (aunque no tan rápido o seguro como el filtro repo).
Hay una hoja de trucos disponible que muestra cómo convertir los comandos de ejemplo del manual de Filter-Branch en los comandos Filter-RepO.
Gran herramienta para su tiempo, pero si bien hace que algunas cosas sean simples, se limita a algunos tipos de reescrituras.
Su arquitectura no es susceptible de manejar más tipos de reescrituras.
Su arquitectura presenta algunas deficiencias y errores incluso para su uso USECSE previsto.
Los fanáticos de BFG pueden estar interesados en BFG-ISS, una reimplementación de BFG basada en Filter-RepO que incluye varias características y correcciones de errores en relación con BFG.
Hay una hoja de trucos disponible que muestra cómo convertir los comandos de ejemplo del manual de BFG Repo Cleaner en los comandos de filtro-Repo.
Digamos que queremos extraer una pieza de un repositorio, con la intención de fusionar esa pieza en algún otro repositorio más grande. Para la extracción, queremos:
Hacer esto con Filter-Repo es tan simple como el siguiente comando:
git filter-repo --path src/ --to-subdirectory-filter my-module --tag-rename ' ' : ' my-module- '
(Las citas individuales son innecesarias, pero deja más claro a un humano que estamos reemplazando la cadena vacía como prefijo con my-module-
)
BFG Repo Cleaner no es capaz de este tipo de reescritura; De hecho, los tres tipos de cambios buscados están fuera de sus capacidades.
Filter-Branch viene con una pila de advertencias (más sobre eso a continuación) incluso una vez que descubra las invocas necesarias:
git filter-branch
--tree-filter ' mkdir -p my-module &&
git ls-files
| grep -v ^src/
| xargs git rm -f -q &&
ls -d *
| grep -v my-module
| xargs -I files mv files my-module/ '
--tag-name-filter ' echo "my-module-$(cat)" '
--prune-empty -- --all
git clone file:// $( pwd ) newcopy
cd newcopy
git for-each-ref --format= " delete %(refname) " refs/tags/
| grep -v refs/tags/my-module-
| git update-ref --stdin
git gc --prune=now
Algunos podrían notar que la invocación de rama de filtro anterior será realmente lenta debido a usar --tree-filter; Alternativamente, puede usar la opción-Index-Filter de Filter-Branch, cambiando los comandos anteriores a:
git filter-branch
--index-filter ' git ls-files
| grep -v ^src/
| xargs git rm -q --cached;
git ls-files -s
| sed "s%$(printf \t)%&my-module/%"
| git update-index --index-info;
git ls-files
| grep -v ^my-module/
| xargs git rm -q --cached '
--tag-name-filter ' echo "my-module-$(cat)" '
--prune-empty -- --all
git clone file:// $( pwd ) newcopy
cd newcopy
git for-each-ref --format= " delete %(refname) " refs/tags/
| grep -v refs/tags/my-module-
| git update-ref --stdin
git gc --prune=now
Sin embargo, para cualquier comando de rama de filtro hay un montón de advertencias. Primero, algunos pueden estar preguntándose por qué enumero cinco comandos aquí para Filter Branch. A pesar del uso de-todo y--Tag-Name-Filter, y Filter-Branch, que afirma que un clon es suficiente para deshacerse de los objetos antiguos, los pasos adicionales para eliminar las otras etiquetas y hacer otro GC aún se requieren Limpie los objetos viejos y evite mezclar historia nueva y antigua antes de empujar en algún lugar. Otras advertencias:
Uno puede hackear esto junto con algo como:
git fast-export --no-data --reencode=yes --mark-tags --fake-missing-tagger
--signed-tags=strip --tag-of-filtered-object=rewrite --all
| grep -vP ' ^M [0-9]+ [0-9a-f]+ (?!src/) '
| grep -vP ' ^D (?!src/) '
| perl -pe ' s%^(M [0-9]+ [0-9a-f]+ )(.*)$%1my-module/2% '
| perl -pe ' s%^(D )(.*)$%1my-module/2% '
| perl -pe s%refs/tags/%refs/tags/my-module-%
| git -c core.ignorecase=false fast-import --date-format=raw-permissive
--force --quiet
git for-each-ref --format= " delete %(refname) " refs/tags/
| grep -v refs/tags/my-module-
| git update-ref --stdin
git reset --hard
git reflog expire --expire=now --all
git gc --prune=now
Pero esto viene con algunas advertencias y limitaciones desagradables:
Ninguna de las herramientas de filtrado de repositorio existentes hizo lo que quería; Todos se quedaron cortos para mis necesidades. Ninguna herramienta proporcionó ninguno de los primeros ocho rasgos a continuación que quería, y tampoco hay una herramienta de más de dos de los últimos cuatro rasgos:
[Informe inicial] Proporcione al usuario un análisis de su repositorio para ayudarlos a comenzar sobre qué podar o cambiar el nombre, en lugar de esperar que adivinen o encuentren otras herramientas para resolverlo. (Desencadenado, por ejemplo, ejecutando la primera vez con una bandera especial, como -Analyze).
[Mantener vs. Eliminar] en lugar de proporcionar una forma para que los usuarios eliminen fácilmente las rutas seleccionadas, también proporcionen banderas para que los usuarios solo mantengan ciertas rutas. Claro, los usuarios podrían solucionar esto especificando para eliminar todas las rutas que no sean las que desean mantener, pero la necesidad de especificar todas las rutas que alguna vez existieron en cualquier versión del repositorio a veces podría ser bastante dolorosa. Para la rama de filtro, usando tuberías como git ls-files | grep -v ... | xargs -r git rm
podría ser una solución razonable, pero puede ser difícil de manejar y no es tan sencillo para los usuarios; Además, esos comandos a menudo son específicos del sistema operativo (¿puedes detectar el gnuismo en el fragmento que proporcioné?).
[Renaming] Debería ser fácil cambiar el nombre de las rutas. Por ejemplo, además de permitir que uno trate algún subdirectorio como la raíz del repositorio, también proporciona opciones para que los usuarios hagan que la raíz del repositorio se convierta en un subdirectorio. Y más generalmente permiten que los archivos y directorios se renombren fácilmente. Proporcione verificaciones de cordura si el cambio de nombre hace que existan varios archivos en la misma ruta. (Y agregue un manejo especial para que si un compromiso simplemente copió el OldName-> Newname sin modificaciones, entonces filtrar el nombre de la vida antigua-> Newname no active la verificación de la sanidad y muera en esa confirmación).
[Seguridad más inteligente] Escribir copias de las referencias originales a un espacio de nombres especial dentro del repositorio no proporciona un mecanismo de recuperación fácil de usar. Muchos tendrían dificultades para recuperarse usando eso. Casi todos los que he visto hacen una operación de filtrado de repositorio lo han hecho con un clon nuevo, porque eliminar el clon en caso de error es un mecanismo de recuperación mucho más fácil. Fomente encarecidamente ese flujo de trabajo detectando y rescate si no estamos en un clon nuevo, a menos que el usuario anule con --force.
[Auto -Reclute] eliminar automáticamente el viejo Cruft y volver a empaquetar el repositorio del usuario después del filtrado (a menos que se anule); Esto simplifica las cosas para el usuario, ayuda a evitar mezclar antecedentes antiguos y nuevos juntos, y evita problemas en los que el proceso de varios pasos para reducir el repositorio documentado en la página de manejo no funciona en algunos casos. (Te estoy mirando, rama de filtro).
[Separación limpia] Evite a los usuarios confusos (y evite un rehacer accidental de cosas viejas) debido a la mezcla de repositorio antiguo y repositorio reescrito juntos. (Esto es particularmente un problema con la rama de filtro cuando se usa la opción--name-filter, y a veces también es un problema cuando solo filtra un subconjunto de ramas).
[Versatilidad] Proporcionar al usuario la capacidad de extender la herramienta o incluso escribir nuevas herramientas que aprovechen las capacidades existentes y proporcionen esta extensibilidad de una manera que (a) evite la necesidad de desembolsar procesos separados (lo que destruiría el rendimiento), (b) Evite hacer que el usuario especifique los comandos de shell dependientes del sistema operativo (lo que evitaría que los usuarios compartan comandos entre sí), (c) aproveche las estructuras de datos ricas (porque los hashes, los dictados, las listas y las matrices son prohibitivamente difíciles en el shell) y (((((( d) proporciona capacidades razonables de manipulación de cadenas (que carecen de caparazón).
[Referencias de confirmación antigua] Proporcionan una forma para que los usuarios usen ID de confirmación antiguas con el nuevo repositorio (en particular mediante la asignación de hashes antiguos a nuevos con refs/ reemplazar/ referencias).
[Consistencia de mensajes de confirmación] Si los mensajes de confirmación se refieren a otras compromisos por ID (por ejemplo, "esto revierte Commit 01234567890ABCDEF", "en Commit 0013DeadBeef9a ..."), esos mensajes de confirmación deben reescribirse para referirse a las nuevas ID de comet.
[Poda de vacío vacío] Los compromisos que se vuelven vacíos debido al filtrado deben ser podados. Si se poda el padre de una confirmación, el primer antepasado no realizado debe convertirse en el nuevo padre. Si no existe un antepasado no pronunciado y el compromiso no fue una fusión, entonces se convierte en una nueva confirmación de raíz. Si no existe un antepasado no prefiritado y el commit se fusionó, entonces la fusión tendrá un padre menos (y, por lo tanto, hará que se convierta en una confirmación no fantástica que se poda en sí misma si no tuviera cambios de archivo propios) . Una cosa especial a tener en cuenta aquí es que podamos las comodidades que se vuelven vacías, no comprometidas que comienzan vacías. Algunos proyectos crean intencionalmente compromisos vacíos por razones de versiones o publicaciones, y estos no deben eliminarse. (Como caso especial, los compromisos que comenzaron vacíos pero cuyo padre fue podado también se considerará que se había "vacío").
[Poda de defensa-designada] La poda de compromisos que se vuelve vacíos puede causar cambios en la topología, y hay muchos casos especiales. Normalmente, los confirmaciones de fusión no se eliminan ya que son necesarios para preservar la topología del gráfico, pero la poda de los padres y otros antepasados puede dar lugar a la pérdida de uno o más padres. Un caso simple ya se observó anteriormente: si un confirmación de fusiones pierde suficientes padres para convertirse en un confirmación no femenina y no tiene cambios de archivo, entonces también se puede podar. Merge Commits también puede tener una topología que se vuelve degenerada: podría terminar con la fusión de Merge_Base como padres (si se podan todos los compromisos intermedios del repositorio original), o podría terminar con un padre que es un antepasado de su otro padre. En tales casos, si la fusión no tiene cambios de archivo propios, entonces la confirmación de fusión también se puede podar. Sin embargo, por lo que lo hacemos con la poda vacía, no podamos fusionar las confirmaciones que comenzaron degeneradas (lo que indica que puede haber sido intencional, como con las fusiones-no-ff), sino que solo se fusionará que se vuelven degenerados y no tienen cambios de archivo de lo suyo.
[Velocidad] El filtrado debe ser razonablemente rápido
Ver las pautas contribuyentes.
Se espera que los participantes en la comunidad Filter-Repo se adhieran a los mismos estándares que para el proyecto GIT, por lo que se aplica el código de conducta GIT.
El trabajo en Filter-Repo y su predecesor también ha impulsado numerosas mejoras a la exportación rápida y al impuesto rápido (y ocasionalmente a otros comandos) en el Core GIT, en función de las cosas que Filter-Repo necesita para hacer su trabajo: