Me han pillado en un lío. Y no puedo encontrar mis respuestas de otras preguntas SO o de leer la documentación de git. Realmente agradecería su ayuda aquí.
Después de fusionar mi sucursal en Github, elimino mi sucursal de la interfaz de usuario (remota). Periódicamente podo mis ramas locales:
git checkout master
git pull origin master
git fetch -p
git branch -d X
Error: error: The branch X is not fully merged. If you are sure you want to delete it, run 'git branch -D X'
La rama X se ha fusionado. Y puedo ver las confirmaciones de X cuando lo hago git log
en master
. ¿Por qué pasó esto? Quiero usar -d
y no -D
ya que esto podría conducir a la eliminación desastrosa de ramas que realmente no se fusionaron.
Hay varias partes difíciles diferentes aquí.
La pregunta clave para git branch -d
(borrar sin forzar1) no es “¿se fusionó la rama?”, Porque esa pregunta es literalmente imposible de responder. En cambio, la pregunta es “¿la rama se fusionó en
Esta prueba se cambió en la versión 1.7 de Git (lo que significa que todos deberían tenerla hoy, pero verifique su versión de Git), en cometer 99c419c91554e9f60940228006b7d39d42704da7 por Junio C Hamano:
rama -d: base la seguridad “ya fusionada” en la rama con la que se fusiona
Cuando una rama está marcada para fusionarse con otra referencia (por ejemplo, ‘siguiente’ local que se fusiona y retrocede al ‘siguiente’ del origen, con ‘branch.next.merge’ establecido en ‘refs/heads/next’), hace poco tiene sentido basar la seguridad “branch -d”, cuyo propósito es no perder confirmaciones que no se fusionan con otras ramas, en la rama actual. Es mucho más sensato verificar si está fusionado con la otra rama con la que se fusiona.
Por lo tanto, hay dos, o a veces tres, si observa la confirmación anterior, preguntas que usted (o Git) deben responder para ver si -d
esta permitido:
- ¿Cuál es el nombre ascendente de la rama que proponemos eliminar? (Si no hay ninguno, vea a continuación).
-
El nombre de la rama apunta a una confirmación específica (llamémosla T para propina). El nombre de la rama ascendente también apunta a una confirmación específica (llame a esta confirmación tu).
Es T un antepasado de tu?
Si la respuesta a la pregunta 2 es afirmativa (es decir, T ≤ T), se permite la eliminación.
¿Qué pasa si hay es sin aguas arriba? Bueno, ahí es donde entra “cambiado en 1.7”: la prueba original no fue T ≤ T sino más bien T ≤ CABEZA. esa prueba es todavía ahí. En este momento, si no hay aguas arriba, se usa la prueba HEAD en lugar de la prueba aguas arriba. Mientras tanto, durante un “período de transición” mientras todos se adaptan al comportamiento novedoso de Git 1.7, también puede recibir una advertencia o una explicación adicional cuando haya es un aguas arriba. Esta advertencia persiste incluso hoy, en Git 2.10 (casi 7 años después: ¡este es un período de transición muy largo!):
if ((head_rev != reference_rev) &&
in_merge_bases(rev, head_rev) != merged) {
if (merged)
warning("deleting branch ... not yet merged to HEAD.");
else
warning("not deleting branch ... even though it is merged to HEAD.");
}
(Recorté parte del código para mostrarlo aquí): básicamente, si HEAD
se resuelve en algún otro compromiso que no sea el compromiso que usamos en el T ≤ prueba, y obtendríamos un diferente resultado para T ≤ CABEZA de lo que tenemos para T ≤ T, agregamos el mensaje de advertencia adicional. (Tenga en cuenta que la primera parte de la prueba es redundante: si lo comparamos con HEAD debido a la compatibilidad anterior a la 1.7 y la falta de flujo ascendente, obtendremos el mismo resultado si volvemos a comparar con HEAD. Todo lo que realmente necesitamos es el in_merge_bases
prueba.)
¿Cómo sabes cuál es el upstream? Bueno, en realidad hay una forma fácil de averiguarlo desde la línea de comandos:
$ git rev-parse --abbrev-ref master@{u}
origin/master
$ git rev-parse --symbolic-full-name master@{u}
refs/remotes/origin/master
El --abbrev-ref
variant te da la versión abreviada típica de Git, mientras que --symbolic-full-name
le da el nombre de referencia completo. Ambos fallan si se ejecutan en una rama sin conjunto ascendente. Por supuesto, también puedes usar git branch -vv
para ver las aguas arriba abreviadas (para todos los ramales).
¿Cómo se prueba todo esto? T ≤ T ¿cosa? El git merge-base --is-ancestor
El comando hace eso para los scripts de shell, así que:
$ git merge-base --is-ancestor master origin/master && echo yes || echo no
te dire si master
es un antepasado de origin/master
(y para este propósito, “apunta a la misma confirmación” cuenta como “es un ancestro”, es decir, esta es la misma prueba ≤).
Cada vez que finalice este período de transición, la prueba “usar HEAD” podría desaparecer por completo o podría continuar utilizándose para ramas que no tienen un conjunto ascendente. Sin embargo, en cualquier caso, la pregunta siempre es “¿la rama que se eliminará se fusionó en ____? (llene el espacio en blanco)” y debe mirar lo que llena el espacio en blanco.
Las confirmaciones en el upstream que corresponden a las confirmaciones en la rama que propone eliminar deben ser (o al menos tener, en su historial) lo mismo commit, por ID de hash de confirmación, para que la prueba “se fusione en” tenga éxito. Si feature/X
se fusionó en origin/develop
a través de la llamada “fusión de squash” (que no es una fusión, aunque se hace mediante la fusión2), ese ID de compromiso no coincidirá y la prueba “se fusionó en” siempre fallará.
1Por cierto, a partir de Git 2.3, ahora puede agregar --force
a git branch -d
en lugar de tener que usar git branch -D
aunque claro -D
todavía funciona.
2La distinción aquí es entre “una fusión”: fusionar como sustantivo, lo que significa “un compromiso que es un compromiso de fusión” (que usa fusionar como adjetivo), y “fusionar”: fusionar como verbo, lo que significa la acción de combinando algunos conjuntos de cambios. El git merge
comando puede:
- haga un avance rápido, que ni se fusione como un verbo ni se fusione como un sustantivo, de modo que no haya fusión en absoluto; o
- hacer una fusión real, que fusiona (verbo) algunos cambios para producir una fusión (sustantivo); o
- haga una “fusión de squash”, que fusiona (verbo) pero produce una no fusión (que, por alguna razón inexplicable, debe confirmar manualmente).
Una “fusión de squash” ocurre solo si la solicita, mientras que una “no fusión de avance rápido” ocurre solo si puede y tu no prohibir él.
-
Toda esta es información útil, pero me gustaría que proporcionara una guía sobre cómo resolver (o al menos mejorar) el problema descrito en la pregunta.
– Alex Jonson
27 de noviembre de 2018 a las 18:58
-
@AlexJohnson: el problema es que no hay solución.
– torek
27 de noviembre de 2018 a las 19:13
-
Me parece bien. Simplemente me sorprende que no haya un mejor flujo de trabajo para la situación extremadamente común de eliminar una rama de características de forma local y remota después de fusionarse con el flujo ascendente.
– Alex Jonson
27 de noviembre de 2018 a las 19:18
-
@AlexJohnson: si usa una combinación real (incluido el botón “combinar” de GitHub en el modo de combinación real),
git branch -d
trabajará localmente (después degit fetch
-ing y actualización de sus otras sucursales locales). Si usa “reorganizar y fusionar” o “aplastar y fusionar” de GitHub, se verá obligado a forzar la eliminación. no es muy bonito– torek
27 de noviembre de 2018 a las 19:20
-
Aplasté antes de que se fusionara ese compromiso, por lo que tiene sentido. Supongo que tengo que elegir si es bonito cuando se fusiona (squash) o cuando se elimina (eliminación no forzada). Gracias por la explicación.
– Alex Jonson
27 de noviembre de 2018 a las 19:23
Esto sucede cuando el contenido de las confirmaciones difiere lo suficiente de la rama que intenta eliminar. Esto podría deberse a una confirmación de squash o una rama que se adjunta a una base anterior y luego cuando alguien más los fusionó en el control remoto.
Si está seguro de que los cambios se fusionaron de hecho (como dijo), entonces un branch -D
es seguro.
Por último,
eliminación desastrosa de sucursales
es falso Eliminar una rama no es un problema que se pueda deshacer. Todas las acciones se almacenan en el git reflog
y los compromisos que una rama indicó permanecer durante al menos 30 días. Si realmente está paranoico, podría cambiar el nombre de la rama. Pero en la práctica es más fácil simplemente eliminar la rama.
En otras palabras, una rama es solo un puntero. Eliminar el puntero no elimina el contenido. Y así puedes resucitar fácilmente la rama si lo necesitas.
-
Tenga cuidado con la eliminación, porque eliminar el nombre de una rama también elimina el registro de referencia de esa rama. El
HEAD
reflog aún puede tener entradas para las confirmaciones que el reflog de la rama protegería, pero eso es puede (como en poder), no necesariamente hace. En general, sin embargo, es cierto que los propios objetos tienden a persistir durante algún tiempo: esto sucede incluso si están desprotegidos. Tendrías que desprotegerlos y desencadenar (o ejecutar manualmente)git gc
ogit prune
para que se vayan.– torek
9 de noviembre de 2016 a las 21:47
Puedes usar el
-f
para forzar la eliminación local– panza
3 de julio de 2020 a las 18:11