tutorial stackoverflow git version-control rebase git-rebase

stackoverflow - git rebase vs merge



Inserte una confirmaciĆ³n antes de la confirmaciĆ³n de root en Git? (14)

Pregunté antes sobre cómo aplastar los primeros dos commits en un repositorio de git.

Si bien las soluciones son bastante interesantes y no tan desquiciantes como algunas otras cosas en git, siguen siendo un poco proverbiales de daño si necesitas repetir el procedimiento muchas veces a lo largo del desarrollo de tu proyecto.

Por lo tanto, preferiría pasar por el dolor solo una vez, y luego poder usar la base de datos interactiva estándar para siempre.

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

Mi pregunta es, entonces, tener un repositorio existente, ¿cómo hago para insertar una nueva confirmación vacía antes de la primera y hacer avanzar a todos los demás?


Respuesta de mitad de 2017

La creación de una nueva confirmación completamente vacía sin efectos secundarios probablemente se haga mejor utilizando la tubería de Git directamente. Hacerlo de esa manera evita cualquier efecto secundario: no tocar la copia de trabajo o el índice, no hay ramas temporales para limpiar, etc. Entonces:

  1. Para crear una confirmación, necesitamos un árbol de directorios para ella, así que primero creamos una vacía:

    tree=`git hash-object -wt tree --stdin < /dev/null`

  2. Ahora podemos envolver un compromiso al respecto:

    commit=`git commit-tree -m ''root commit'' $tree`

  3. Y ahora podemos volver a basarnos en eso:

    git rebase --onto $commit --root master

Y eso es. Puedes reorganizar todo eso en una línea si conoces tu capa lo suficientemente bien.

(NB: en la práctica ahora usaría filter-branch . Lo editaré más adelante).

Respuesta histórica (referenciada por otras respuestas)

Aquí hay una implementación más limpia de la misma solución, ya que funciona sin la necesidad de crear un repositorio adicional, usar controles remotos y corregir una cabeza desprendida:

# first you need a new empty branch; let''s call it `newroot` git checkout --orphan newroot git rm -rf . # then you apply the same steps git commit --allow-empty -m ''root commit'' git rebase --onto newroot --root master git branch -d newroot

Voila, has terminado en master con su historia reescrita para incluir una confirmación de raíz vacía.

Nota: en versiones anteriores de Git que carecen del --orphan para checkout , necesita la plomería para crear una ramificación vacía:

git symbolic-ref HEAD refs/heads/newroot git rm --cached -r . git clean -f -d


Aquí está mi script bash basado en la respuesta de Kent con mejoras:

  • comprueba la rama original, no solo la master , cuando termina;
  • Traté de evitar la rama temporal, pero la git checkout --orphan solo funciona con un estado de bifurcación, no de cabecera separada, por lo que se verificó el tiempo suficiente para hacer que la nueva raíz se confirme y luego se elimine;
  • usa el hash de la nueva confirmación de raíz durante la filter-branch (Kent dejó un marcador de posición allí para el reemplazo manual);
  • la operación de filter-branch reescribe solo las ramas locales, no remotos también
  • los metadatos del autor y del committer están estandarizados de modo que la confirmación raíz sea idéntica en todos los repositorios.

#!/bin/bash # Save the current branch so we can check it out again later INITIAL_BRANCH=`git symbolic-ref --short HEAD` TEMP_BRANCH=''newroot'' # Create a new temporary branch at a new root, and remove everything from the tree git checkout --orphan "$TEMP_BRANCH" git rm -rf . # Commit this empty state with generic metadata that will not change - this should result in the same commit hash every time export GIT_AUTHOR_NAME=''nobody'' export GIT_AUTHOR_EMAIL=''[email protected]'' export GIT_AUTHOR_DATE=''2000-01-01T00:00:00+0000'' export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git commit --allow-empty -m ''empty root'' NEWROOT=`git rev-parse HEAD` # Check out the commit we just made and delete the temporary branch git checkout --detach "$NEWROOT" git branch -D "$TEMP_BRANCH" # Rewrite all the local branches to insert the new root commit, delete the # original/* branches left behind, and check out the rewritten initial branch git filter-branch --parent-filter "sed /"s/^/$/-p $NEWROOT//"" --tag-name-filter cat -- --branches git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d git checkout "$INITIAL_BRANCH"


Aquí hay una línea simple que se puede usar para agregar una confirmación vacía al inicio de un repositorio, si olvidó crear una confirmación vacía inmediatamente después de "git init":

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


Bueno, esto es lo que se me ocurrió:

# Just setting variables on top for clarity. # Set this to the path to your original repository. ORIGINAL_REPO=/path/to/original/repository # Create a new repository… mkdir fun cd fun git init # …and add an initial empty commit to it git commit --allow-empty -m "The first evil." # Add the original repository as a remote git remote add previous $ORIGINAL_REPO git fetch previous # Get the hash for the first commit in the original repository FIRST=`git log previous/master --pretty=format:%H --reverse | head -1` # Cherry-pick it git cherry-pick $FIRST # Then rebase the remainder of the original branch on top of the newly # cherry-picked, previously first commit, which is happily the second # on this branch, right after the empty one. git rebase --onto master master previous/master # rebase --onto leaves your head detached, I don''t really know why) # So now you overwrite your master branch with the newly rebased tree. # You''re now kinda done. git branch -f master git checkout master # But do clean up: remove the remote, you don''t need it anymore git remote rm previous


Comience un nuevo repositorio.

Establezca su fecha a la fecha de inicio que desee.

Haz todo lo que desees haber hecho, ajustando el tiempo del sistema para reflejar cuando hubieras deseado hacerlo de esa manera. Extraiga archivos del repositorio existente según sea necesario para evitar una gran cantidad de tipeos innecesarios.

Cuando llegues a hoy, intercambia los repositorios y listo.

Si estás loco (establecido) pero razonablemente inteligente (probablemente, porque tienes que tener una cierta cantidad de inteligencia para pensar ideas tan locas como esta), escribirás el proceso.

Eso también lo hará más agradable cuando decidas que quieres que el pasado haya sucedido de otra manera dentro de una semana.


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

  • mejor rendimiento
  • más fácil y menos arriesgado (podría verificar su resultado en cada paso y deshacer lo que hizo ...)
  • funciona bien con múltiples sucursales con resultados garantizados

La idea detrás de esto es:

  • Crea un nuevo compromiso vacío en el pasado
  • Reemplace la confirmación raíz anterior por una confirmación exactamente similar, excepto que la nueva confirmación raíz se agrega como padre
  • Verifique que todo sea como se esperaba y ejecute git filter-branch
  • Una vez más, verifique que todo esté bien y limpie los archivos git que ya no son necesarios.

Aquí hay un script 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"

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

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

git filter-branch -- --all

Ahora, debe ver 2 historias, la anterior y la nueva (consulte la ayuda en filter-branch para obtener más información). Podría comparar los 2 y verificar nuevamente si todo está bien. Si está satisfecho, elimine los archivos que no necesita más:

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

Podrías regresar a tu rama master y eliminar la rama temporal:

git checkout master git branch -D new-root

Ahora, todo debe hacerse;)


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)


Me entusiasmé y escribí una versión ''idempotente'' de este bonito script ... siempre insertará la misma confirmación vacía, y si la ejecuta dos veces, no cambia los hashes de confirmación cada vez. Entonces, aquí está mi opinión sobre git-insert-empty-root :

#!/bin/sh -ev # idempotence achieved! tmp_branch=__tmp_empty_root git symbolic-ref HEAD refs/heads/$tmp_branch git rm --cached -r . || true git clean -f -d touch -d ''1970-01-01 UTC'' . GIT_COMMITTER_DATE=''1970-01-01T00:00:00 +0000'' git commit / --date=''1970-01-01T00:00:00 +0000'' --allow-empty -m ''initial'' git rebase --committer-date-is-author-date --onto $tmp_branch --root master git branch -d $tmp_branch

¿Vale la pena la complejidad extra? tal vez no, pero usaré este.

Esto también DEBERÍA permitir realizar esta operación en varias copias clonadas del repositorio, y terminar con los mismos resultados, por lo que siguen siendo compatibles ... probando ... sí, funciona, pero también es necesario eliminar y agregar su controles remotos de nuevo, por ejemplo:

git remote rm origin git remote add --track master user@host:path/to/repo


Me gusta la respuesta de Aristóteles. Pero descubrió que para un repositorio grande (> 5000 confirmaciones) la rama de filtro funciona mejor que la 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, preservarlas. 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 esta ''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'' significa que las etiquetas se reescribirán para apuntar a las confirmaciones recién creadas.


Para cambiar la confirmación raíz:

Primero, crea la confirmación que deseas como la primera.

En segundo lugar, cambie el orden de las confirmaciones usando:

git rebase -i --root

Aparecerá un editor con los commits hasta que la raíz se confirme, como:

recoger 1234 mensaje raíz anterior

recoger 0294 Un compromiso en el medio

pick 5678 commit que quieres poner en la raíz

A continuación, puede poner el compromiso que desea primero, colocándolo en la primera línea. En el ejemplo:

pick 5678 commit que quieres poner en la raíz

recoger 1234 mensaje raíz anterior

recoger 0294 Un compromiso en el medio

Salga del editor, la orden de confirmación habrá cambiado.

PD: Para cambiar el editor que usa git, ejecuta:

git config --global core.editor name_of_the_editor_program_you_want_to_use


Sé que esta publicación es antigua, pero esta página es la primera al buscar en Google "insertar commit git".

¿Por qué complicar las cosas simples?

Tienes ABC y quieres ABZC.

  1. git rebase -i trunk (o cualquier cosa antes de B)
  2. cambiar selección para editar en la línea B
  3. haga sus cambios: git add ..
  4. git commit ( git commit git commit --amend que editará B y no creará Z)

[Puedes hacer tantos git commit como quieras aquí para insertar más commits. Por supuesto, puede tener problemas con el paso 5, pero resolver la combinación del conflicto con git es una habilidad que debería tener. Si no, practica!]

  1. git rebase --continue

Simple, ¿no es así?

Si comprendes git rebase , agregar una confirmación ''raíz'' no debería ser un problema.

Diviértete con git!


Siguiendo la respuesta de Aristóteles Pagaltzis y otros pero usando comandos más simples

zsh% git checkout --orphan empty Switched to a new branch ''empty'' zsh% git rm --cached -r . zsh% git clean -fdx zsh% git commit --allow-empty -m ''initial empty commit'' [empty (root-commit) 64ea894] initial empty commit zsh% git checkout master Switched to branch ''master'' zsh% git rebase empty First, rewinding head to replay your work on top of it... zsh% git branch -d empty Deleted branch empty (was 64ea894).

Tenga en cuenta que su repositorio no debe contener modificaciones locales que esperan ser confirmadas.
Tenga en cuenta que git checkout --orphan funcionará en las nuevas versiones de git, supongo.
Tenga en cuenta que la mayoría de las veces el git status brinda consejos útiles.


Utilicé piezas de la respuesta de Aristóteles y de 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 el master ) además de las etiquetas.


git rebase --root --onto $emptyrootcommit

debería hacer el truco fácilmente