tag - ¿Qué hace GIT PUSH exactamente?
git tag best practices (4)
Parece que no puedo encontrar una buena explicación de esto.
Sé lo que hace git pull :
1) una recuperación , es decir, todas las confirmaciones adicionales del servidor se copian en el repositorio local y el puntero de la rama de origen / maestro se mueve al final de la cadena de confirmación
2) una combinación de la rama de origen / maestro en la rama maestra , el puntero de la rama maestra se mueve hacia la confirmación recién creada, mientras que el puntero de origen / maestro permanece en posición.
Asumo que git push hace algo muy similar, pero no lo sé con seguridad. Creo que hace uno de estos, o algo similar, o algo más (?):
- copia todas las confirmaciones locales y hace una fusión allí (el reverso de lo que hace git pull ); pero en este caso, el servidor no tiene mi rama maestra local, por lo que no puedo ver qué es la fusión
O
- fusiona mi rama maestra en el origen / maestro , empujando el compromiso resultante al servidor y enlazándolo al lado del compromiso final existente, también moviendo el maestro del servidor; esto no parece correcto porque entonces mi origen / maestro local no está sincronizado con el del servidor.
Actualmente estoy usando git para operaciones básicas, así que estoy bien, pero quiero entender completamente estos aspectos internos.
La respuesta técnica, cargada de jerga del manual es la siguiente:
git push
"actualiza refs remotos usando refs locales, mientras que envía objetos necesarios para completar los refs dados".
Básicamente, está copiando información, para asegurarse de que su control remoto esté actualizado con su repositorio local. ¿Pero qué son las referencias y qué son los objetos? Parafraseando el manual:
Las entradas manuales de referencia son archivos que "almacenan el valor SHA-1 [de un objeto, como un commit] con un nombre simple para que pueda usar ese puntero en lugar del valor SHA-1 en bruto" [para encontrar el contenido asociado con él] . Puede verlos navegando a directorios como
.git/refs/heads/<branch name>
, o.git/refs/remotes/origin/<branch name>
en su repositorio.Los objetos ( entrada manual ) incluyen confirmaciones, árboles, blobs y etiquetas (las últimas no se insertan de forma predeterminada). Como ejemplo, citando a Mark Longair de otra respuesta SO , "una confirmación registra el contenido exacto del código fuente en ese momento con la fecha, el nombre del autor y las referencias a las confirmaciones principales".
Por lo tanto, cuando git push
, git usa referencias locales (creadas por usted al escribir git commit
) para actualizar archivos equivalentes en el control remoto, actualizando así los punteros a las confirmaciones más recientes, y luego cualquier contenido nuevo que haya creado se copia en el sistema de git como objetos, etiquetados con algunos metadatos y refs de SHA-1.
Como ilustración adicional de lo que es una referencia , aquí , en los documentos de la API de Github , muestran ejemplos de resultados JSON de llamadas a la API que solicitan referencias en un repositorio determinado. Podría ayudarlo a comprender cómo las diferentes piezas de información se relacionan entre sí.
Mi descripción más simple es, solo presiona lo siguiente: (asumiendo que haces git push master master )
- Copie las confirmaciones locales que no existen en el repositorio remoto al repositorio remoto
- Mueva el origen / maestro (tanto en su git local como en el git remoto) para apuntar a la misma confirmación local / maestra
- Empuje NO se fusiona
SIN EMBARGO , comprobará si su maestro / local se basa en el origen / maestro. Conceptualmente, significa que en el gráfico git, desde local / master puede volver directamente al origen / master (no al origen / maestro de su git local, sino al maestro en el repositorio remoto) simplemente moviendo "hacia abajo", lo que significa no La modificación se realizó en el repositorio remoto antes de su empuje. De lo contrario se rechazará el empuje.
Solo realiza una copia, no fusiona.
Más específicamente, copia las partes del almacén de objetos que se encuentran en el repositorio / rama local y faltan en el lado remoto. Esto incluye, cometer objetos, refs, árboles y blobs.
Las etiquetas son una excepción notable, requieren que se incluya la marca --tags.
En la siguiente publicación del blog, git es más simple de lo que crees que tiene más detalles.
Suponiendo que ya entiende el modelo de "objetos" de git (sus confirmaciones y archivos, etc., son simplemente "objetos en la base de datos de git", con objetos "sueltos", que no están empaquetados para ahorrar espacio, almacenados en .git/objects/12/34567...
y similares) ...
Usted tiene razón: git fetch
recupera los objetos que "ellos" ( origin
, en este caso) tienen que no tiene, y pega etiquetas en ellos: origin/master
y similares. Más específicamente, su git llama a los suyos en el teléfono de Internet (o en cualquier otro medio de transporte adecuado) y pregunta: ¿qué sucursales tiene y qué ID de compromiso son esas? Tienen master
y el ID es 1234567...
, por lo que su git solicita 1234567...
y cualquier otro objeto necesario que aún no tenga, y hace que su origin/master
punto origin/master
cometa el objeto 1234567...
La parte de git push
que es simétrica aquí es la siguiente: tu git llama a su git en el mismo teléfono de Internet como siempre, pero esta vez, en lugar de solo preguntarles sobre sus sucursales, tu git les cuenta sobre tus sucursales y tu git objetos del repositorio, y luego dice: "¿Qué tal si consigo que configures tu master
en 56789ab...
?"
Su git analiza los objetos que enviaste (el nuevo commit 56789ab...
y cualquier otro objeto que tengas que no tuvieran, que necesitarían para tomarlo). Su git considera la solicitud para establecer su master
en 56789ab...
Como Chris K ya respondió , no hay ninguna fusión aquí: tu git simplemente propone que su git sobrescriba a su master
con este nuevo ID de compromiso. Depende de su git decidir si permitir eso.
Si "ellos" (quienesquiera que sean) no han establecido ninguna regla especial, la regla predeterminada que usa git aquí es muy simple: se permite sobrescribir si el cambio es un "avance rápido". Tiene una característica adicional: la sobrescritura también se permite si el cambio se realiza con el indicador "forzar" establecido. Por lo general, no es una buena idea establecer el indicador de fuerza aquí, ya que la regla predeterminada, "solo hacia delante rápido", suele ser la regla correcta .
La pregunta obvia aquí es: ¿qué es exactamente un avance rápido? Llegaremos a eso en un momento; Primero necesito expandir un poco las etiquetas, o "referencias" para ser más formales.
Referencias de git
En git, una rama o una etiqueta, o incluso cosas como el alijo y HEAD
son referencias . La mayoría de ellos se encuentran en .git/refs/
, un subdirectorio del repositorio git. (Algunas referencias de nivel superior, incluida HEAD
, están en .git
mismo .git
.) Toda referencia es un archivo 1 que contiene una ID SHA-1 como 7452b4b5786778d5d87f5c90a94fab8936502e20
. Las ID de SHA-1 son incómodas e imposibles de recordar para las personas, por lo que usamos nombres, como v2.1.0
(una etiqueta en este caso, la versión 2.1.0 de git en sí) para guardarlas para nosotros.
Algunas referencias son, o al menos están destinadas a ser, totalmente estáticas. La etiqueta v2.1.0
nunca debe referirse a otra cosa que no sea la ID SHA-1 anterior. Pero algunas referencias son más dinámicas. Específicamente, sus propias sucursales locales, como el master
, son blancos móviles. Un caso especial, HEAD
, ni siquiera es un objetivo en sí mismo: generalmente contiene el nombre de la rama del objetivo en movimiento. Así que hay una excepción para las referencias "indirectas": HEAD
generalmente contiene la cadena ref: refs/heads/master
, o ref: refs/heads/branch
, o algo por el estilo; y git no (y no puede) imponer una regla de "nunca cambiar" para las referencias. Las ramas en particular cambian mucho.
¿Cómo sabes si una referencia debe cambiar? Bueno, mucho de esto es solo por convención: las sucursales se mueven y las etiquetas no. Pero debe preguntar: ¿cómo sabe si una referencia es una rama, una etiqueta o qué?
Nombre espacios de referencias: refs/heads/
, refs/tags/
, etc.
Aparte de las referencias especiales de nivel superior, todas las referencias de git se encuentran en las refs/
como ya hemos señalado anteriormente. Sin embargo, dentro del directorio refs/
(o "carpeta" si está en Windows o Mac), podemos tener una colección completa de subdirectorios. Git tiene, en este punto, cuatro subdirectorios bien definidos: refs/heads/
contiene todas sus sucursales, refs/tags/
contiene todas sus etiquetas, refs/remotes/
contiene todas sus "ramas de seguimiento remoto", y refs/notes/
contiene las "notas" de git (que ignoraré aquí porque se complican un poco).
Como todas sus sucursales están en refs/heads/
, git puede indicar que se debe permitir que cambien, y dado que todas sus etiquetas están en refs/tags/
, git puede indicar que estas no deberían.
Movimiento automático de ramas.
Cuando realice una nueva confirmación y se encuentre en una rama como master
, git moverá automáticamente la referencia. Su nuevo compromiso se crea con su "compromiso principal" siendo la punta de rama anterior, y una vez que su nuevo compromiso se guarda de manera segura, git cambia el master
para contener el ID del nuevo compromiso. En otras palabras, se asegura de que el nombre de la rama, la referencia en el subdirectorio heads
, siempre apunte a la confirmación de punta .
(De hecho, la rama, en el sentido de una colección de confirmaciones que forma parte del gráfico de confirmación almacenado en el repositorio, es una estructura de datos hecha de las confirmaciones en el repositorio. Su única conexión con el nombre de la sucursal es que La confirmación de punta de la rama en sí misma se almacena en la etiqueta de referencia con ese nombre. Esto es importante más adelante, siempre y cuando los nombres de las sucursales se modifiquen o borren a medida que el repositorio crezca muchas más confusiones. Por ahora, es solo algo a tener en cuenta: hay una diferencia entre la "punta de rama", que es donde apunta el "nombre de rama", y la rama-como-un-subconjunto-de-compromiso-DAG. Es un poco desafortunado que git tiende a agrupar estos diferentes conceptos bajo una sola nombre, "rama".)
¿Qué es exactamente un avance rápido?
Por lo general, se ve "avance rápido" en el contexto de la combinación, a menudo con la combinación realizada como el segundo paso en un git pull
. Pero, de hecho, el "envío rápido" es en realidad una propiedad de un movimiento de etiqueta .
Dibujemos un poco de un gráfico de compromiso. Los pequeños o
nodos representan confirmaciones, y cada uno tiene una flecha que apunta hacia la izquierda, hacia la izquierda y hacia arriba o hacia la izquierda y hacia abajo (o en un caso, dos flechas) hacia su padre (o padres). Para poder referirme a tres por nombre, les daré los nombres en mayúscula en lugar de o
. Además, esta obra de arte basada en personajes no tiene flechas, así que tienes que imaginarlas; solo recuerda que todos apuntan hacia la izquierda o hacia la izquierda, al igual que los tres nombres.
o - A <-- name1
/
o - o - o - o - B <-- name2
/ /
o - C <-- name3
Cuando le pide a git que cambie una referencia, simplemente le pide que pegue un nuevo ID de confirmación en la etiqueta. En este caso, estas etiquetas viven en refs/heads/
y, por lo tanto, son nombres de bifurcaciones, por lo que se supone que pueden asumir nuevos valores.
Si le decimos a git que ponga B
en name1
, obtenemos esto:
o - A
/
o - o - o - o - B <-- name1, name2
/ /
o - C <-- name3
Tenga en cuenta que la confirmación A
ahora no tiene nombre, y la o
a la izquierda se encuentra solo al encontrar A
... lo cual es difícil ya que A
no tiene nombre. A
ha abandonado el compromiso A
, y estos dos compromisos se han convertido en elegibles para la "recolección de basura". (En git, hay un "nombre de fantasma" dejado en el "reflog", que mantiene a la rama con A
alrededor de 30 días en general. Pero ese es un tema completamente diferente).
¿Qué hay de decirle a git que ponga a B
en name3
? Si hacemos eso a continuación, obtenemos esto:
o - A
/
o - o - o - o - B <-- name1, name2, name3
/ /
o - C
Aquí, la confirmación C
todavía tiene una forma de encontrarlo: comience en B
y trabaje hacia abajo y hacia la izquierda, hasta su otra (segunda) confirmación principal, y encontrará la confirmación C
Así que cometer C
no se abandona.
Actualizar name1
como este no es un avance rápido, pero actualizar name3
es .
Más específicamente, un cambio de referencia es un "avance rápido" si y solo si el objeto, generalmente un compromiso, que la referencia utilizada para apuntar a todavía es accesible comenzando desde el nuevo lugar y trabajando hacia atrás, a lo largo de todos los caminos posibles hacia atrás . En términos de gráficos, es un avance rápido si el nodo anterior es un antecesor del nuevo.
Hacer un push
ser un avance rápido, mediante la fusión
Los reenvíos rápidos de nombre de sucursal se producen cuando lo único que hace es agregar nuevos compromisos; pero también cuando, si ha agregado nuevos compromisos, también ha fusionado todos los nuevos compromisos agregados. Es decir, supongamos que su repo tiene esto dentro, después de haber hecho un nuevo commit:
o <-- master
/
...- o - o <-- origin/master
En este punto, mover el origin/master
"arriba a la derecha" sería un avance rápido. Sin embargo, alguien más aparece y actualiza el otro repositorio (de origin
), por lo que realiza una git fetch
y obtiene un nuevo commit de ellos. Su git mueve su origin/master
etiqueta origin/master
(en una operación de avance rápido en su repositorio, como sucede):
o <-- master
/
...- o - o - o <-- origin/master
En este punto, mover origin/master
a master
no sería un avance rápido, ya que abandonaría ese nuevo compromiso.
Sin embargo, usted puede hacer una operación git merge origin/master
para realizar un nuevo commit en su master
, con dos ID de commit principales. Vamos a etiquetar este M
(para fusionar):
o - M <-- master
/ /
...- o - o - o <-- origin/master
Ahora puede reenviar esto al origin
y pedirles que configuren su master
, al que llaman origin/master
equivalente a su (nuevo) M
, porque para ellos , ¡ahora es una operación de avance rápido!
Tenga en cuenta que también puede hacer una git rebase
, pero git rebase
para una publicación de diferente. :-)
1 De hecho, las referencias de git siempre comienzan como archivos individuales en varios subdirectorios, pero si una referencia no se actualiza durante mucho tiempo, tiende a "empaquetarse" (junto con todas las demás referencias en su mayoría estáticas) en un solo archivo lleno de referencias empaquetadas. Esta es solo una optimización que ahorra tiempo, y la clave aquí no es depender de la implementación exacta, sino más bien usar los comandos rev-parse
y update-ref
git para extraer el SHA-1 actual de una referencia, o actualizar una referencia para contener un nuevo SHA-1.