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.
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?.)
rebase -p
todos modos)