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:
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`
Ahora podemos envolver un compromiso al respecto:
commit=`git commit-tree -m ''root commit'' $tree`
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.
-
git rebase -i trunk
(o cualquier cosa antes de B) - cambiar selección para editar en la línea B
- haga sus cambios:
git add ..
-
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!]
-
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