¿Insertar una confirmación antes de la confirmación raíz en Git?

11 minutos de lectura

¿Insertar una confirmacion antes de la confirmacion raiz en Git
kch

He preguntado antes sobre cómo aplastar las dos primeras confirmaciones en un repositorio de git.

Si bien las soluciones son bastante interesantes y no tan alucinantes como otras cosas en git, siguen siendo un poco la bolsa de dolor proverbial si necesita repetir el procedimiento muchas veces a lo largo del desarrollo de su proyecto.

Por lo tanto, prefiero pasar por el dolor solo una vez y luego poder usar para siempre el rebase interactivo estándar.

Entonces, lo que quiero hacer es tener un compromiso inicial vacío que exista únicamente con el propósito de ser el primero. Sin código, sin nada. Simplemente ocupando espacio para que pueda ser la base para rebase.

Entonces, mi pregunta es, teniendo un repositorio existente, ¿cómo hago para insertar un compromiso nuevo y vacío antes del primero y cambiar a todos los demás hacia adelante?

  • 😉 Supongo que merece una respuesta de todos modos. Estoy explorando las muchas formas en que uno puede volverse loco editando obsesivamente la historia. No te preocupes, no es un repositorio compartido.

    – kch

    14 de marzo de 2009 a las 5:38

  • De un editor de historia loco y obsesivo a otro, ¡gracias por publicar la pregunta! ;D

    – marco

    2 de julio de 2011 a las 9:30

  • En defensa de @kch, una razón perfectamente legítima es una en la que me encuentro: Agregar una instantánea de una versión histórica que nunca fue capturada en el repositorio.

    – Viejo McStopher

    4 de julio de 2012 a las 2:45

  • ¡Tengo otra razón legítima! Agregar una confirmación vacía antes de la primera para poder volver a la base de la primera confirmación y eliminar la acumulación binaria agregada en la confirmación inicial de un repositorio (:

    – pospi

    13 de septiembre de 2012 a las 5:04

  • Relacionado: ¿Editar/enmendar/modificar/cambiar la primera/raíz/compromiso inicial en Git?.

    usuario456814

    25 de mayo de 2014 a las 5:43

¿Insertar una confirmacion antes de la confirmacion raiz en Git
Aristóteles Pagaltzis

Hay 2 pasos para lograr esto:

  1. Crear una nueva confirmación vacía
  2. Reescribir el historial para comenzar desde este compromiso vacío

Pondremos la nueva confirmación vacía en una rama temporal newroot por conveniencia.

1. Crea una nueva confirmación vacía

Hay varias maneras de hacer esto.

Usando solo plomería

El enfoque más limpio es usar la plomería de Git para crear una confirmación directamente, lo que evita tocar la copia de trabajo o el índice o qué rama está desprotegida, etc.

  1. Cree un objeto de árbol para un directorio vacío:

    tree=`git hash-object -wt tree --stdin < /dev/null`
    
  2. Envuelva un compromiso a su alrededor:

    commit=`git commit-tree -m 'root commit' $tree`
    
  3. Cree una referencia a él:

    git branch newroot $commit
    

Por supuesto, puede reorganizar todo el procedimiento en una sola línea si conoce su caparazón lo suficientemente bien.

sin fontanería

Con los comandos de porcelana regulares, no puede crear una confirmación vacía sin verificar el newroot rama y actualizando el índice y la copia de trabajo repetidamente, sin una buena razón. Pero algunos pueden encontrar esto más fácil de entender:

git checkout --orphan newroot
git rm -rf .
git clean -fd
git commit --allow-empty -m 'root commit'

Tenga en cuenta que en versiones muy antiguas de Git que carecen de la --orphan cambiar a checkouttienes que reemplazar la primera línea con esto:

git symbolic-ref HEAD refs/heads/newroot

2. Vuelva a escribir el historial para comenzar desde este compromiso vacío

Aquí tiene dos opciones: reorganización o una reescritura limpia del historial.

rebase

git rebase --onto newroot --root master

Esto tiene la virtud de la sencillez. Sin embargo, también actualizará el nombre y la fecha del confirmador en cada último compromiso en la rama.

Además, con algunas historias de casos de borde, incluso puede fallar debido a conflictos de fusión, a pesar de que está rebasando en un compromiso que no contiene nada.

reescritura de la historia

El enfoque más limpio es reescribir la rama. A diferencia de con git rebasedeberá buscar desde qué confirmación comienza su rama:

git replace <currentroot> --graft newroot
git filter-branch master

La reescritura ocurre en el segundo paso, obviamente; es el primer paso que necesita explicación. Qué git replace lo que hace es decirle a Git que cada vez que vea una referencia a un objeto que desea reemplazar, Git debería mirar el reemplazo de ese objeto.

Con el --graft interruptor, le está diciendo algo ligeramente diferente de lo normal. Está diciendo que aún no tiene un objeto de reemplazo, pero desea reemplazar el <currentroot> objeto commit con una copia exacta de sí mismo excepto las confirmaciones principales del reemplazo deben ser las que enumeró (es decir, las newroot cometer). Luego git replace continúa y crea este compromiso para usted, y luego declara ese compromiso como el reemplazo de su compromiso original.

Ahora si haces un git logverás que las cosas ya se ven como tú quieres: la rama comienza desde newroot.

Sin embargo, tenga en cuenta que git replace en realidad no modifica la historia – ni se propaga fuera de su repositorio. Simplemente agrega una redirección local a su repositorio de un objeto a otro. Lo que esto significa es que nadie más ve el efecto de este reemplazo, solo usted.

es por eso que el filter-branch es necesario el paso. Con git replace crea una copia exacta con confirmaciones principales ajustadas para la confirmación raíz; git filter-branch luego repite este proceso para todas las siguientes confirmaciones también. Ahí es donde la historia se reescribe para que puedas compartirla.

  • Que --onto newroot la opción es redundante; puedes prescindir de él porque el argumento te lo pasa, newrootes lo mismo que el argumento anterior — newroot.

    – Guillermo Tell

    27 de junio de 2010 a las 15:55

  • ¿Por qué no utilizar los mandos de fontanería en lugar de los porcelánicos?. yo reemplazaría git simbólico-ref HEAD refs/heads/newroot con git checkout –nueva raíz huérfana

    – albfán

    13 de octubre de 2012 a las 8:09

  • @nenopera: porque esta respuesta fue escrita antes git-checkout tenia ese interruptor. Lo actualicé para mencionar ese enfoque primero, gracias por el puntero.

    – Aristóteles Pagaltzis

    3 de abril de 2013 a las 5:09

  • Si su nueva raíz no está vacía, use git rebase --merge -s recursive -X theirs --onto newroot --root master para resolver todos los conflictos automáticamente (ver esta respuesta). @AlexanderKuzin

    – usuario

    22 de junio de 2017 a las 19:48

  • @Geremia Puede modificar solo la última confirmación, por lo que si su repositorio solo contiene la confirmación raíz, puede funcionar; de lo contrario, tendrá que reorganizar todas las demás confirmaciones en el repositorio sobre la confirmación raíz modificada de todos modos. Pero incluso entonces, el tema implica que no desea cambiar la confirmación raíz, sino que desea insertar otra antes de la raíz existente.

    – usuario

    2 oct 2019 a las 20:43

1646958491 623 ¿Insertar una confirmacion antes de la confirmacion raiz en Git
Antonio Hatchkins

Fusión de las respuestas de Aristóteles Pagaltzis y Uwe Kleine-König y el comentario de Richard Bronosky.

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
# touch .gitignore && git add .gitignore # if necessary
git commit --allow-empty -m 'initial'
git rebase --onto newroot --root master
git branch -d newroot

(solo para poner todo en un solo lugar)

  • Esto es excelente. Sería bueno si esto pudiera ser lo que hizo git rebase -i –root internamente.

    – aredridel

    8 de julio de 2012 a las 2:39

  • Sí, me sorprendió descubrir que no es así.

    –Antony Hatchkins

    9 de julio de 2012 a las 13:53

  • Tuve que cambiar el comando rebase a git rebase newroot masterpor error.

    – marbel82

    24 de julio de 2019 a las 23:11

  • @antony-hatchkins gracias por esto. Tengo un repositorio de git existente y (por varias razones que no mencionaré aquí) estoy tratando de agregar un compromiso de git NO VACÍO como mi primer compromiso. Así que reemplacé git commit –allow-empty -m ‘initial’ con git add .; git commit -m “commit inicial de laravel”; empujar git; Y luego este paso de rebase: git rebase –onto newroot –root master está fallando con una TONELADA de conflictos de combinación. ¿Algún consejo? :((

    – kp123

    31 de diciembre de 2019 a las 20:03

  • @ kp123 intente una confirmación vacía 🙂

    –Antony Hatchkins

    31 de diciembre de 2019 a las 20:32


Me gusta la respuesta de Aristóteles. Pero descubrió que para un repositorio grande (> 5000 confirmaciones) filter-branch funciona mejor que rebase por varias razones 1) es más rápido 2) no requiere intervención humana cuando hay un conflicto de fusión. 3) puede reescribir las etiquetas, preservándolas. Tenga en cuenta que filter-branch funciona porque no hay dudas sobre el contenido de cada confirmación; es exactamente lo mismo que antes de este ‘rebase’.

Mis pasos son:

# first you need a new empty branch; let's call it `newroot`
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

# then you apply the same steps
git commit --allow-empty -m 'root commit'

# then use filter-branch to rebase everything on newroot
git filter-branch --parent-filter 'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat master

Tenga en cuenta que las opciones ‘–tag-name-filter cat’ significan que las etiquetas se reescribirán para apuntar a las confirmaciones recién creadas.

  • Esto no ayuda a crear confirmaciones no vacías que también son un caso de uso interesante.

    – ceztko

    21 de abril de 2015 a las 22:26


  • En comparación con otras soluciones, la suya solo tiene un efecto secundario insignificante: cambia los hashes, pero todo el historial permanece intacto. ¡Gracias!

    – Vladislav Savchenko

    8 de noviembre de 2016 a las 11:52

¿Insertar una confirmacion antes de la confirmacion raiz en Git
Hiedra

Para agregar una confirmación vacía al comienzo de un repositorio, si olvidaste crear una confirmación vacía inmediatamente después de “git init”:

git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)

1646958492 217 ¿Insertar una confirmacion antes de la confirmacion raiz en Git
Felipe

creo que usando git replace y git filter-branch es una mejor solución que usar un git rebase:

  • mejor presentación
  • más fácil y menos riesgoso (podrías verificar tu resultado en cada paso y deshacer lo que hiciste…)
  • trabajar bien con múltiples sucursales con resultados garantizados

La idea detrás de esto es:

  1. Crear una nueva confirmación vacía en el pasado
  2. Reemplace la confirmación raíz anterior por una confirmación exactamente similar, excepto que la nueva confirmación raíz se agrega como principal
  3. Verifique que todo sea como se esperaba y ejecute git filter-branch
  4. Una vez más, verifique que todo esté bien y limpie los archivos git que no necesite más.

Aquí hay un guión para los 2 primeros pasos:

#!/bin/bash
root_commit_sha=$(git rev-list --max-parents=0 HEAD)
git checkout --force --orphan new-root
find . -path ./.git -prune -o -exec rm -rf {} \; 2> /dev/null
git add -A
GIT_COMMITTER_DATE="2000-01-01T12:00:00" git commit --date==2000-01-01T12:00:00 --allow-empty -m "empty root commit"
new_root_commit_sha=$(git rev-parse HEAD)

echo "The commit '$new_root_commit_sha' will be added before existing root commit '$root_commit_sha'..."

parent="parent $new_root_commit_sha"
replacement_commit=$(
 git cat-file commit $root_commit_sha | sed "s/author/$parent\nauthor/" |
 git hash-object -t commit -w --stdin
) || return 3
git replace "$root_commit_sha" "$replacement_commit"

Puede ejecutar este script sin riesgo (incluso si hacer una copia de seguridad antes de hacer una acción que nunca antes hizo es una buena idea 😉 ), y si el resultado no es el esperado, simplemente elimine los archivos creados en la carpeta .git/refs/replace e intenta de nuevo 😉

Una vez que haya verificado que el estado del repositorio es el esperado, ejecute el siguiente comando para actualizar el historial de todas las sucursales:

git filter-branch -- --all

Ahora, debes ver 2 historias, la antigua y la nueva (ver ayuda en filter-branch para más información). Puede comparar los 2 y verificar nuevamente si todo está bien. Si está satisfecho, elimine los archivos que ya no necesita:

rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace

Podrías volver a tu master rama y elimine la rama temporal:

git checkout master
git branch -D new-root

Ahora, todo debe estar hecho;)

Usé partes de la respuesta de Aristóteles y Kent con éxito:

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .
git commit --allow-empty -m 'root commit'
git filter-branch --parent-filter \
'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat -- --all
# clean up
git checkout master
git branch -D newroot
# make sure your branches are OK first before this...
git for-each-ref --format="%(refname)" refs/original/ | \
xargs -n 1 git update-ref -d

Esto también reescribirá todas las ramas (no solo master) además de las etiquetas.

1646958492 725 ¿Insertar una confirmacion antes de la confirmacion raiz en Git
Uwe Kleine-König

git rebase --root --onto $emptyrootcommit

debería hacer el truco fácilmente

  • $emptyrootcommit es una variable de shell que se expande a nada, seguramente?

    – Flamm

    5 oct 2016 a las 11:12


  • @Flimm: $emptyrootcommit es el sha1 de una confirmación vacía que el cartel original ya parece tener.

    – Uwe Kleine-König

    30 de julio de 2018 a las 17:36

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad