¿Insertar una confirmación antes de la confirmación root en Git?


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

Si bien las soluciones son bastante interesantes y no tan distorsionadoras como algunas otras cosas en git, siguen siendo un poco la proverbial bolsa de dolor 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 ser capaz de usar para siempre la rebase interactiva estándar.

Lo que quiero do, entonces, es tener un commit inicial vacío que existe ú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.

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

Author: Community, 2009-03-14

14 answers

Respuesta a mediados de 2017

La creación de un nuevo commit completamente vacío sin efectos secundarios probablemente se haga mejor usando la plomerí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. Así que:

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

    tree=`git hash-object -wt tree --stdin < /dev/null`
    
  2. Ahora podemos envolver un commit it:

    commit=`git commit-tree -m 'root commit' $tree`
    
  3. Y ahora podemos rebase sobre eso:{[12]]}

    git rebase --onto $commit --root master
    

Y eso es todo. Puedes reorganizar todo eso en un solo revestimiento si conoces bien tu caparazón.

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


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, futz alrededor con controles remotos, y corregir una cabeza separada:

# 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

Listo, has terminado en master con su historial reescrito para incluir una confirmación de root vacía.


NB.: en versiones antiguas de Git que carecen del interruptor --orphan a checkout, necesitas la plomería para crear una rama vacía:

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
 262
Author: Aristotle Pagaltzis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-02 11:17:05

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)

 26
Author: Antony Hatchkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-23 03:49:50

Me gusta la respuesta de Aristóteles. Pero encontramos que para un repositorio grande (>5000 commits) 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 preserving preservándolas. Tenga en cuenta que filter-branch funciona porque no hay duda sobre el contenido de cada commit is es exactamente el 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 la las opciones 'tag tag-name-filter cat' significan que las etiquetas se reescribirán para apuntar a las confirmaciones recién creadas.

 11
Author: Kent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-29 15:55:35

Utilicé piezas 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.

 5
Author: ldav1s,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-23 12:35:10

git rebase --root --onto $emptyrootcommit

Debería hacer el truco fácilmente

 3
Author: Uwe Kleine-König,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-17 19:27:47

Me emocioné y escribí una versión 'idempotente' de este bonito guión ... siempre insertará el mismo commit vacío, y si lo ejecuta dos veces, no cambiará sus hashes de commit cada vez. Por lo tanto, 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 compatible ... prueba ... sí, funciona, pero también necesita eliminar y agregar sus controles remotos nuevamente, por ejemplo:

git remote rm origin
git remote add --track master user@host:path/to/repo
 3
Author: Sam Watkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-06 13:36:15

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
 3
Author: kch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-23 12:34:24

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

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

La idea detrás de esto es:

  • Crear una nueva confirmación vacía en el pasado
  • Reemplace la antigua confirmación raíz por una confirmación exactamente similar, excepto que la nueva commit se añade como padre
  • Verifique que todo es como se espera y ejecute git filter-branch
  • Una vez más, verifica que todo está bien y limpia los archivos git que ya no se necesitan

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 hacer 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 e inténtelo de nuevo ;)

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

git filter-branch -- --all

Ahora, debes ver 2 historias, la antigua y la nueva (ver ayuda en filter-branch para más información). Usted podría comparar el 2 y comprobar de nuevo si todo está bien. Si está satisfecho, elimine los archivos no más necesarios:

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

Puede volver a su rama master y eliminar el temporal rama:

git checkout master
git branch -D new-root

Ahora, todo debe hacerse;)

 2
Author: Philippe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-06 20:50:51

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

  • comprueba la rama original, no solo master, cuando se hace;
  • Traté de evitar la rama temporal, pero git checkout --orphan solo funciona con una rama, no con un estado de cabeza separada, por lo que se comprueba el tiempo suficiente para hacer la nueva confirmación de root y luego se elimina;
  • utiliza el hash de la nueva confirmación de root durante el filter-branch (Kent dejó un marcador de posición allí para el reemplazo manual);
  • el filter-branch la operación reescribe solo las ramas locales, no también los controles remotos
  • los metadatos autor y committer están estandarizados para 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"
 1
Author: luxagen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:39

Para cambiar la confirmación de root:

Primero, crea el commit que deseas como el primero.

Segundo, cambia el orden de las confirmaciones usando:

Git rebase - i root root

Aparecerá un editor con las confirmaciones hasta la confirmación root, como:

Pick 1234 old root message

Pick 0294 Un commit en el medio

Pick 5678 commit que desea poner en la raíz

Luego puede poner primero la confirmación que desea, por colocándolo en la primera línea. En el ejemplo:

Pick 5678 commit que desea poner en la raíz

Pick 1234 old root message

Pick 0294 Un commit en el medio

Salir del editor el orden de confirmación habrá cambiado.

PS: Para cambiar el editor que usa git, ejecute:

Git config core núcleo global.editor name_of_the_editor_program_you_want_to_use

 1
Author: tigre200,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-18 16:17:06

Aquí hay una simple línea 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)
 1
Author: Rory,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-09 21:00:41

Siguiente respuesta Aristóteles Pagaltzis y otros, pero utilizando 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 esperando a ser comprometidas.
Note git checkout --orphan funcionará en nuevas versiones de git, supongo.
Nota la mayor parte del tiempo git status da consejos útiles.

 0
Author: ony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-18 08:28:04

Iniciar un nuevo repositorio.

Vuelva a establecer la fecha de inicio que desee.

Haga todo de la manera que desee haberlo hecho, ajustando el tiempo del sistema para reflejar cuándo desearía haberlo hecho de esa manera. Extraiga archivos del repositorio existente según sea necesario para evitar una gran cantidad de escritura innecesaria.

Cuando llegues a hoy, intercambia los repositorios y listo.

Si solo estás loco (establecido) pero razonablemente inteligente (probablemente, porque tienes que tener un cierta cantidad de inteligencia para pensar en ideas locas como esta) se script 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.

 -5
Author: MarkusQ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-14 05:49:02

Sé que este post es antiguo, pero esta página es la primera cuando se busca en Google "inserting commit git".

¿Por qué complicar las cosas simples?

Tienes A-B-C y quieres A-B-Z-C.

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

[Puedes hacer tantos git commit como quieras aquí para insertar más confirmaciones. Por supuesto, usted puede tienes problemas con el paso 5, pero resolver conflictos de fusión con git es una habilidad que deberías tener. ¡Si no, practica!]

  1. git rebase --continue

Simple, ¿no?

Si entiendes git rebase, agregar un commit 'root' no debería ser un problema.

Diviértete con git!

 -7
Author: Alex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-07 17:28:28