Tenemos un repositorio Git con más de 400 confirmaciones, las primeras dos docenas de las cuales fueron muchas pruebas y errores. Queremos limpiar estos commits aplastando muchos en un solo commit. Naturalmente, git-rebase parece el camino a seguir. Mi problema es que termina con conflictos de fusión, y estos conflictos no son fáciles de resolver. No entiendo por qué debería haber ningún conflicto, ya que solo estoy aplastando las confirmaciones (no eliminando ni reorganizando). Muy probablemente, esto demuestra que no estoy entendiendo completamente cómo git-rebase hace sus aplastamientos.
Aquí hay una versión modificada de los scripts que estoy usando:
repo_squash.sh (este es el script que realmente se ejecuta):
rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
GIT_EDITOR=../repo_squash_helper.sh git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a
repo_squash_helper.sh (este script solo lo usa repo_squash.sh):
if grep -q "pick " $1
then
# cp $1 ../repo_squash_history.txt
# emacs -nw $1
sed -f ../repo_squash_list.txt < $1 > $1.tmp
mv $1.tmp $1
else
if grep -q "initial import" $1
then
cp ../repo_squash_new_message1.txt $1
elif grep -q "fixing bad import" $1
then
cp ../repo_squash_new_message2.txt $1
else
emacs -nw $1
fi
fi
repo_squash_list.txt: (este archivo solo lo usa repo_squash_helper.sh)
# Initial import
s/pick \(251a190\)/squash \1/g
# Leaving "Needed subdir" for now
# Fixing bad import
s/pick \(46c41d1\)/squash \1/g
s/pick \(5d7agf2\)/squash \1/g
s/pick \(3da63ed\)/squash \1/g
Dejaré el contenido del "nuevo mensaje" a tu imaginación. Inicialmente, hice esto sin la opción "--estrategia suya" (es decir, usando la estrategia predeterminada, que si entiendo la documentación correctamente es recursiva, pero no estoy seguro de qué estrategia recursiva se usa), y tampoco lo hizo. t trabajo. Además, debo señalar que, utilizando el código comentado en repo_squash_helper.sh, guardé el archivo original en el que funciona el script sed y ejecuté el script sed contra él para asegurarme de que estaba haciendo lo que quería que hiciera ( era). De nuevo, ni siquiera sé por qué habría un conflicto, por lo que no parece importar mucho qué estrategia se utilice. Cualquier consejo o idea sería útil, pero sobre todo solo quiero que este aplastamiento funcione.
Actualizado con información adicional de la discusión con Jefromi:
Antes de trabajar en nuestro repositorio masivo "real", utilicé scripts similares en un repositorio de prueba. Era un repositorio muy simple y la prueba funcionó limpiamente.
El mensaje que recibo cuando falla es:
Finished one cherry-pick.
# Not currently on any branch.
nothing to commit (working directory clean)
Could not apply 66c45e2... Needed subdir
Esta es la primera elección después de la primera confirmación de squash. La ejecución git status
produce un directorio de trabajo limpio. Si luego hago un git rebase --continue
, recibo un mensaje muy similar después de algunas confirmaciones más. Si luego lo hago nuevamente, recibo otro mensaje muy similar después de un par de docenas de confirmaciones. Si lo hago una vez más, esta vez pasa por un centenar de confirmaciones y muestra este mensaje:
Automatic cherry-pick failed. After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply f1de3bc... Incremental
Si luego corro git status
, obtengo:
# Not currently on any branch.
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: repo/file_A.cpp
# modified: repo/file_B.cpp
#
# Unmerged paths:
# (use "git reset HEAD <file>..." to unstage)
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: repo/file_X.cpp
#
# Changed but not updated:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: repo/file_Z.imp
El bit "ambos modificados" me parece extraño, ya que esto fue solo el resultado de una elección. También vale la pena señalar que si miro el "conflicto", se reduce a una sola línea con una versión que comienza con un carácter [tabulador] y la otra con cuatro espacios. Esto parecía ser un problema con la forma en que configuré mi archivo de configuración, pero no hay nada de eso. (Noté que core.ignorecase está configurado como verdadero, pero evidentemente git-clone lo hizo automáticamente. No estoy completamente sorprendido por eso teniendo en cuenta que la fuente original estaba en una máquina con Windows).
Si reparo manualmente file_X.cpp, falla poco después con otro conflicto, esta vez entre un archivo (CMakeLists.txt) que una versión piensa que debería existir y una versión piensa que no debería. Si soluciono este conflicto diciendo que sí quiero este archivo (que es lo que hago), algunas confirmaciones más tarde obtengo otro conflicto (en este mismo archivo) donde ahora hay algunos cambios no triviales. Todavía es solo alrededor del 25% del camino a través de los conflictos.
También debo señalar, ya que esto podría ser muy importante, que este proyecto comenzó en un repositorio svn. Ese historial inicial muy probablemente fue importado de ese repositorio svn.
Actualización n. ° 2:
En una alondra (influenciado por los comentarios de Jefromi), decidí hacer el cambio de mi repo_squash.sh para ser:
rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a
Y luego, acabo de aceptar las entradas originales, tal como están. Es decir, el "rebase" no debería haber cambiado nada. Terminó con los mismos resultados descritos anteriormente.
Actualización n. ° 3:
Alternativamente, si omito la estrategia y reemplazo el último comando con:
git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a
Ya no recibo los problemas de rebase "nada que cometer", pero todavía me quedan los otros conflictos.
Actualice con el repositorio de juguetes que recrea el problema:
test_squash.sh (este es el archivo que realmente ejecuta):
#========================================================
# Initialize directories
#========================================================
rm -rf test_squash/ test_squash_clone/
mkdir -p test_squash
mkdir -p test_squash_clone
#========================================================
#========================================================
# Create repository with history
#========================================================
cd test_squash/
git init
echo "README">README
git add README
git commit -m"Initial commit: can't easily access for rebasing"
echo "Line 1">test_file.txt
git add test_file.txt
git commit -m"Created single line file"
echo "Line 2">>test_file.txt
git add test_file.txt
git commit -m"Meant for it to be two lines"
git checkout -b dev
echo Meaningful code>new_file.txt
git add new_file.txt
git commit -m"Meaningful commit"
git checkout master
echo Conflicting meaningful code>new_file.txt
git add new_file.txt
git commit -m"Conflicting meaningful commit"
# This will conflict
git merge dev
# Fixes conflict
echo Merged meaningful code>new_file.txt
git add new_file.txt
git commit -m"Merged dev with master"
cd ..
#========================================================
# Save off a clone of the repository prior to squashing
#========================================================
git clone test_squash test_squash_clone
#========================================================
#========================================================
# Do the squash
#========================================================
cd test_squash
GIT_EDITOR=../test_squash_helper.sh git rebase -i HEAD@{7}
#========================================================
#========================================================
# Show the results
#========================================================
git log
git gc
git reflog
#========================================================
test_squash_helper.sh (utilizado por test_sqash.sh):
# If the file has the phrase "pick " in it, assume it's the log file
if grep -q "pick " $1
then
sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash \1 \2/g" < $1 > $1.tmp
mv $1.tmp $1
# Else, assume it's the commit message file
else
# Use our pre-canned message
echo "Created two line file" > $1
fi
PD: Sí, sé que algunos de ustedes se encogen cuando me ven usando emacs como editor alternativo.
PPS: Sabemos que tendremos que eliminar todos nuestros clones del repositorio existente después del rebase. (En la línea de "no rebasarás un repositorio después de que se haya publicado").
PPPS: ¿Alguien puede decirme cómo agregar una recompensa a esto? No veo la opción en ninguna parte de esta pantalla si estoy en modo de edición o modo de vista.
fuente
rebase -p
todos modos)rebase --interactive
, esas son una especie de lista de acciones para que git intente. Esperaba que pudieras reducir esto a una sola calabaza que causaba conflictos y evitar toda la complejidad adicional de tus scripts de ayuda. La otra información que falta es cuándo ocurren los conflictos: ¿cuándo git aplica los parches para formar la calabaza o cuando intenta pasar de la calabaza y aplicar el siguiente parche? (Y ¿está seguro de que nada malo sucede con su kludge GIT_EDITOR Otro voto para el caso simple prueba?.)Respuestas:
Muy bien, tengo la confianza suficiente para lanzar una respuesta. Quizás tenga que editarlo, pero creo que sé cuál es su problema.
Su caso de prueba de repositorio de juguete tiene una fusión, peor aún, tiene una fusión con conflictos. Y estás rebatiendo a través de la fusión. Sin
-p
(que no funciona totalmente con-i
), las fusiones se ignoran. Esto significa que todo lo que hiciste en tu resolución de conflictos no está allí cuando el rebase intenta elegir el próximo commit, por lo que su parche puede no aplicarse. (Creo que esto se muestra como un conflicto de fusión porquegit cherry-pick
puede aplicar el parche haciendo una fusión de tres vías entre la confirmación original, la confirmación actual y el antepasado común).Desafortunadamente, como señalamos en los comentarios,
-i
y-p
(preservar fusiones) no se llevan muy bien. Sé que la edición / reescritura funciona, y que la reordenación no. Sin embargo, creo que funciona bien con las calabazas. Esto no está documentado, pero funcionó para los casos de prueba que describo a continuación. Si su caso es mucho más complejo, es posible que tenga muchos problemas para hacer lo que quiere, aunque aún será posible. (Moraleja de la historia: limpiarrebase -i
antes de fusionar).Entonces, supongamos que tenemos un caso muy simple, donde queremos juntar A, B y C:
Ahora, como dije, si no hubiera conflictos en X,
git rebase -i -p
funciona como es de esperar.Si hay conflictos, las cosas se ponen un poco más complicadas. Hará un buen aplastamiento, pero luego, cuando intente recrear la fusión, los conflictos volverán a ocurrir. Tendrá que resolverlos nuevamente, agregarlos al índice y luego usar
git rebase --continue
para continuar . (Por supuesto, puede resolverlos nuevamente revisando la versión del commit de fusión original).Si le sucede que tiene
rerere
habilitadas en su repo (rerere.enabled
conjunto en true), esta será manera más fácil - Git será capaz de volver a utilizar el nuevo con cable re solución a partir de cuando se tenía originalmente los conflictos, y todo lo que tiene que hacer es inspeccionarlo para asegurarse de que funcionó correctamente, agregue los archivos al índice y continúe. (Incluso puede ir un paso más allá, encendiéndosererere.autoupdate
, y los agregará por usted, para que la fusión ni siquiera falle). Supongo, sin embargo, que nunca habilitaste rerere, por lo que tendrás que resolver el conflicto tú mismo. ** O bien, puede probar el
rerere-train.sh
script de git-contrib, que intenta "cebar [la] recuperación de la base de datos de los compromisos de fusión existentes", básicamente, comprueba todos los compromisos de fusión, intenta fusionarlos y, si la fusión falla, toma los resultados y se los muestragit-rerere
. Esto podría llevar mucho tiempo, y en realidad nunca lo he usado, pero podría ser muy útil.fuente
-p
opción.git commit -a -m"Some message"
ygit rebase --continue
, y continuará. Esto funciona incluso sin la-p
opción, pero funciona aún mejor con la-p
opción (ya que no estoy haciendo ningún reordenamiento, parece que-p
está bien). De todos modos, los mantendré informados.git config --global rerere.enabled true
ygit config --global rerere.autoupdate true
antes de ejecutar el ejemplo de prueba resuelve el problema principal. Curiosamente, sin embargo, no conserva las fusiones, incluso cuando se especifica--preserve-merges
. Sin embargo, si no tengo esos conjuntos, y escribogit commit -a -m"Meaningful commit"
ygit rebase --continue
al especificar--preserve-merges
, conserva las fusiones. De todos modos, ¡gracias por ayudarme a superar este problema!Si no te importa crear una nueva rama, así es como solucioné el problema:
Estar en main:
fuente
git diff new_clean_branch..original_messy_branch
, ¿ deberían ser lo mismo? Para mí son diferentes.Estaba buscando un requisito similar, es decir, descartar los compromisos intermedios de mi rama de desarrollo, he encontrado que este procedimiento funcionó para mí.
en mi rama de trabajo
¡viola hemos conectado el último commit al start commit de la sucursal! No hay problemas de conflicto de fusión! En mi práctica de aprendizaje, he llegado a esta conclusión en esta etapa. ¿Existe un mejor enfoque para este propósito?
fuente
Sobre la base de la excelente respuesta anterior de @ hlidka que minimiza la intervención manual, quería agregar una versión que conserve cualquier nueva confirmación en master que no esté en la rama para aplastar.
Como creo, estos podrían perderse fácilmente en el
git reset
paso de ese ejemplo.fuente
Tenga en cuenta que las
-X
opciones de estrategia se ignoraron cuando se usaron en un rebase interactivo.Ver commit db2b3b820e2b28da268cc88adff076b396392dfe (julio de 2013, git 1.8.4+),
Eso significa que la
-X
estrategia ahora funciona con un rebase interactivo, así como un rebase simple, y su script inicial ahora podría funcionar mejor.fuente
Me encontraba con un problema más simple pero similar, en el que tenía 1) resolvió un conflicto de fusión en una sucursal local, 2) seguí trabajando agregando muchas más pequeñas confirmaciones, 3) quería rebase y golpear conflictos de fusión.
Para mí,
git rebase -p -i master
funcionó. Mantuvo el compromiso original de resolución de conflictos y me permitió aplastar a los demás en la parte superior.Espero que ayude a alguien!
fuente