Pregunta

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

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

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

Lo que quiero hacer, entonces, es tener un commit inicial vacío que exista ú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, teniendo 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?

¿Fue útil?

Solución

Respuesta a mediados de 2017

La creación de una nueva confirmación completamente vacía sin efectos secundarios probablemente se haga mejor usando la fontanería de Git & # 8217; s directamente. Hacerlo de esta 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 un commit, necesitamos un árbol de directorios para él, así que primero creamos uno vacío:

    tree=`git hash-object -wt tree --stdin < /dev/null`
    
  2. Ahora podemos envolver una confirmación a su alrededor:

    commit=`git commit-tree -m 'root commit' $tree`
    
  3. Y ahora podemos basarnos en eso:

    git rebase --onto $commit --root master
    

Y eso es todo. Puede reorganizar todo en una sola línea si conoce su caparazón lo suficientemente bien.

(N.B .: en la práctica, ahora & # 8217; d 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, trabajar con controles remotos y corregir un cabezal separado:

# 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 historial reescrito para incluir una confirmación de raíz vacía.


NB .: en versiones antiguas de Git que carecen del --orphan cambie a checkout , necesita la tubería para crear una rama vacía:

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

Otros consejos

Fusión de las respuestas de Aristóteles Pagaltzis y Uwe Kleine-K & # 246; 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 gusta la respuesta de Aristóteles. Pero descubrí que para un repositorio grande (> 5000 commits) filter-branch funciona mejor que el 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 commit; 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 los commits recién creados.

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

git rebase --root --onto $ emptyrootcommit

debería hacer el truco fácilmente

Me emocioné y escribí una versión 'idempotente' de este lindo script ... siempre insertará el mismo commit vacío, y si lo ejecutas dos veces, no cambia tus hashes de commit 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 adicional? tal vez no, pero usaré este.

Esto DEBERÍA también permitir realizar esta operación en varias copias clonadas del repositorio, y terminar con los mismos resultados, por lo que aún son compatibles ... pruebas ... sí, funciona, pero también necesita eliminar y agregue sus controles remotos nuevamente, por ejemplo:

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

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

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

  • mejor rendimiento
  • más fácil y menos riesgoso (puede 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:

  • Crear una nueva confirmación vacía en el pasado
  • Reemplace la confirmación de raíz anterior por una confirmación exactamente similar, excepto que la nueva confirmación de raíz se agrega como padre
  • Verifique que todo esté 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 necesita

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"

Puede ejecutar este script sin riesgo (incluso si hacer una copia de seguridad antes de realizar 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 < code> .git / refs / replace e intente nuevamente;)

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

git filter-branch -- --all

Ahora, debe ver 2 historias, la antigua y la nueva (consulte la ayuda en filter-branch para obtener más información). Puede comparar el 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

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

git checkout master
git branch -D new-root

Ahora, todo debe hacerse;)

Aquí hay una línea simple que se puede usar para agregar una confirmación vacía al comienzo 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)

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

  • comprueba la rama original, no solo master , cuando se hace;
  • Intenté evitar la bifurcación temporal, pero git checkout --orphan solo funciona con una bifurcación, no en un estado de cabecera separada, por lo que se desprotegió el tiempo suficiente para que la nueva raíz se confirme y luego eliminado;
  • 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 filter-branch reescribe solo las ramas locales, no los remotos también
  • los metadatos del autor y el confirmador 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='nobody@example.org'
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"

Para cambiar la confirmación de raíz:

Primero, cree la confirmación que desee como primera.

Segundo, cambie el orden de las confirmaciones usando:

git rebase -i --root

Aparecerá un editor con las confirmaciones hasta la confirmación raíz, como:

seleccione 1234 mensaje raíz antiguo

pick 0294 Una confirmación en el medio

seleccione 5678 commit que desea poner en la raíz

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

seleccione 5678 commit que desea poner en la raíz

seleccione 1234 mensaje raíz antiguo

pick 0294 Una confirmación en el medio

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

PD: para cambiar el editor que usa git, ejecute:

git config --global core.editor name_of_the_editor_program_you_want_to_use

Siguiendo la respuesta 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 esperando ser confirmadas.
Nota git checkout --orphan funcionará en nuevas versiones de git, supongo.
Tenga en cuenta que la mayoría de las veces git status ofrece sugerencias útiles.

Iniciar un nuevo repositorio.

Establezca su fecha de regreso a la fecha de inicio que desee.

Haz todo de la manera que deseas haberlo hecho, ajustando el tiempo del sistema para que refleje cuándo hubieras deseado hacerlo de esa manera. Extraiga archivos del repositorio existente según sea necesario para evitar una gran cantidad de escritura innecesaria.

Cuando llegue hoy, cambie los repositorios y ya está.

Si solo estás loco (establecido) pero razonablemente inteligente (probablemente, porque tienes que tener una cierta cantidad de inteligencia para pensar ideas 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 a partir de ahora.

Sé que esta publicación es antigua, pero esta página es la primera cuando buscas en Google " insertando 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 selección para editar en la línea B
  3. haz tus cambios: git add ..
  4. git commit ( git commit --amend que editará B y no creará Z)

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

  1. git rebase - continuar

Simple, ¿no?

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

¡Diviértete con git!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top