La terminología confusa de Git
(jvns.ca)- Incluso usando Git durante mucho tiempo, hay mucha superposición y reutilización de términos que confunden, así que memorizar comandos no basta para predecir cómo va a comportarse realmente
- Palabras y símbolos como
HEAD,heads, detached HEAD,ours/theirs,../...cambian de significado según el contexto index,stagedycachedestán todos conectados con.git/index, peroreset,revertyrestore, pese a tener nombres parecidos, afectan de forma distinta al historial de commits, al árbol de trabajo y a los punteros de ramas- Que
mainhaga tracking de un remoto y una remote-tracking branch comoorigin/mainson cosas distintas, ycheckoutmete en un solo comando tanto el cambio de rama como la restauración de archivos - Para entender Git, más que mirar comandos individuales, hay que ver también el modelo interno en el que se acoplan ramas, referencias, commits, el índice y el estado de seguimiento remoto
HEAD, ramas y detached HEAD
- En Git, heads se refiere a las ramas, y a nivel interno las ramas se guardan en el directorio
.git/refs/heads- El glosario oficial de Git distingue entre branch como todos los commits sobre ella y head como el commit más reciente, pero puede verse como dos formas de mirar el mismo objeto
HEADapunta a la rama actual y se guarda en.git/HEAD- La frase “
heades una rama yHEADes la rama actual” muestra bastante bien lo confusa que puede ser la terminología de Git
- La frase “
- El estado detached HEAD es cuando
HEADno apunta a una rama sino directamente a un ID de commit- Si haces checkout de una etiqueta, como una etiqueta no es una rama, puedes terminar en detached HEAD
- En este estado
git pullno funciona, ygit pushtampoco salvo que se use de una forma especial git commit,git merge,git rebaseygit cherry-picksí se pueden usar, pero los nuevos commits pueden quedar desconectados de cualquier rama y volverse difíciles de encontrar- Puedes salir del estado detached HEAD creando una rama nueva o cambiando a una rama existente
ours y theirs cambian entre merge y rebase
- Al resolver conflictos,
git checkout --ours file.txtelige el archivo del lado “ours”, pero el punto de referencia en merge y rebase no es el mismo - En merge, la rama actual es
oursy la rama que se incorpora estheirs- Ejemplo: si ejecutas
git checkout merge-into-oursy luegogit merge from-theirs, la rama actual seráours
- Ejemplo: si ejecutas
- En rebase, la rama actual es
theirsy la rama objetivo del rebase esours- Ejemplo: si ejecutas
git checkout theirsy luegogit rebase ours, la rama objetivo seráours
- Ejemplo: si ejecutas
- Esta diferencia está relacionada con que
git rebase maininternamente va fusionando repetidamente los commits de la rama actual sobre una copia de la ramamain- Hay un ejemplo relacionado en este script de shell que implementa
git rebasecongit merge
- Hay un ejemplo relacionado en este script de shell que implementa
- ourstheirs explica brevemente la terminología
oursytheirs- En VSCode, “current change” e “incoming change” también pueden confundir de la misma manera
Mensajes de estado remoto y fast-forward
Your branch is up to date with 'origin/main'no significa quemainesté al día con el estado más reciente del remoto- Significa que tu rama local y
origin/mainson iguales según el últimogit fetchogit pull - Si el último fetch fue hace 5 días, entonces está “actualizada” según lo que sabías hace 5 días
- Significa que tu rama local y
- Git guarda en el reflog información sobre cuándo se hizo el fetch más reciente, pero el mensaje de estado no muestra el tiempo “según el último fetch”
can be fast-forwardedsignifica que la rama remota va por delante de la local y que en local no hay commits adicionales, así que solo hace falta mover el puntero hacia adelante- Ejemplo: si
mainesA-B-Cyorigin/mainesA-B-C-D-E, después degit pullambas quedan enA-B-C-D-E - En este caso no puede haber conflictos de merge
- Ejemplo: si
- Cuando no se puede hacer fast-forward es porque ambas ramas se han bifurcado
- Ejemplo: si
maintiene un commitXque no existe enorigin/main, yorigin/maintieneDyEque no están enmain, entonces no se puede hacer fast-forward - En ese caso Git muestra que las dos ramas están diverged y que cada una tiene una cantidad distinta de commits
- Ejemplo: si
Sintaxis para seleccionar commits: ^, ~, .., ...
HEAD^yHEAD~apuntan ambos al commit inmediatamente anteriorHEAD^^^,HEAD~~~yHEAD~3apuntan todos al commit de hace 3 pasos
- En commits de merge, donde hay varios padres, el significado de
HEAD^2yHEAD^3cambiaHEAD^es el primer padreHEAD^2es el segundo padreHEAD^3es el tercer padreHEAD~3significa padre del padre del padre, así que no es lo mismo queHEAD^3
git log main..testygit log main...testtambién funcionan de manera distintamain..testmuestra los commits que solo están entesttest..mainmuestra los commits que solo están enmainmain...testmuestra los commits exclusivos de ambos lados
- En
git diff,..y...se interpretan distinto que engit loggit diff test..maincompara incluyendo tanto los cambios del lado detestcomo los del lado demaingit diff test...maincompara el ancestro común con la punta demain, así que muestra un diff de un solo lado
- La confusión con la notación de puntos también se trata con más detalle en pain in dots
reference, refspec, tree-ish
- En Git, reference se usa con varios significados
- Ramas y etiquetas como
main,v0.2 HEAD, que apunta a la rama actual- Expresiones como
HEAD^^^, que se interpretan como IDs de commit
- Ramas y etiquetas como
- La documentación de Git llama estrictamente “revision parameters” a expresiones como
HEAD^^^, pero en el uso diario pueden tratarse como referencias - Una symbolic reference es una referencia que apunta a otra referencia, como
HEADHEADes un concepto central que influye mucho en cómo se comportan los comandos principales de Git
- La configuración del remoto en
.git/configincluye un refspec- Ejemplo:
+refs/heads/main:refs/remotes/origin/main - Muchas veces se usa tal cual el valor que crean por defecto
git cloneogit remote add
- Ejemplo:
tree-ishse refiere a lo que puede ir enTHINGdentro degit checkout THING .- Un ID de commit
- Una referencia a commit como
main,HEAD^^,v0.3.2 - Un subdirectorio dentro de un commit, como
main:./docs - En la práctica, muchas veces basta con entenderlo como “un commit o una referencia a commit”
index, staged, cached
index,stagedycachedestán todos relacionados con.git/index.git/indexes el archivo que se usa cuando haces stage de cambios congit add
- Hay varias expresiones para la misma cosa repartidas entre distintos comandos
git diff --cachedgit rm --cachedgit diff --staged.git/index
- Pero las banderas
--indexy--cachedno siempre significan lo mismo- Este texto del mantenedor principal de Git, Junio Hamano, explica las diferencias en detalle
- Por razones de rendimiento, el index también lista archivos untracked, pero por lo general staging area no se entiende como algo que incluya archivos untracked
reset, revert, restore
reset,revertyrestoreson palabras parecidas, pero en Git afectan cosas distintasgit revert COMMITcrea un commit nuevo en la rama actual que hace lo contrario deCOMMIT- Si
COMMITagregó 3 líneas, el nuevo commit elimina esas 3 líneas
- Si
git reset --hard COMMITfuerza a que la rama actual vuelva al estado deCOMMIT- Es una operación muy peligrosa que elimina los cambios nuevos posteriores a
COMMIT
- Es una operación muy peligrosa que elimina los cambios nuevos posteriores a
git restore --source=COMMIT PATHrestaura los archivos dePATHal estado que tenían enCOMMIT- No cambia otros archivos ni el historial de commits
git reset --hardygit restore .suelen comportarse de forma parecida si se usan solos, perogit reset --hard COMMITygit restore --source COMMIT .se comportan de manera completamente distinta
track, remote-tracking branch, untracked files
- Git usa la palabra track de tres maneras distintas
Untracked files:engit statusse refiere a archivos que Git no gestiona y que no están incluidos en un commit- Una remote-tracking branch como
origin/maines una referencia local que guarda el ID del commit al que apuntabamainen el remotooriginen el momento del últimogit pullogit fetch - Se puede configurar que una rama local haga track de una rama remota, como en “branch foo set up to track remote branch bar from origin”
- Decir que
maines una rama que hace track de un remoto y decir queorigin/maines una remote-tracking branch no es lo mismo maines una rama real, así que puedes crear commits y hacer merge sobre ella- Si en
.git/configestá configurada para hacer track delmainremoto,git pullygit pushse vuelven más cómodos de usar
- Si en
origin/main, pese al nombre “remote-tracking branch”, no es una rama normal- No puedes hacer commits directamente sobre ella
- Solo se actualiza cuando traes el estado más reciente de
maindesde el remoto congit pullogit fetch
checkout, reflog, merge/rebase/cherry-pick
git checkoutmete dos tareas distintas en un solo comandogit checkout BRANCHcambia de ramagit checkout file.txtdescarta los cambios sin stage defile.txt
- Para reducir esta confusión, Git separó esas funciones en
git switchygit restore, aunquecheckoutsigue pudiéndose usar reflogsignifica reference log, y es el registro de a qué apuntó una referencia en el pasado- reference es un término amplio que incluye ramas, etiquetas y
HEAD - El reflog puede ayudarte a salir de situaciones muy malas en Git, como borrar por accidente una rama importante
- reference es un término amplio que incluye ramas, etiquetas y
- merge, rebase y cherry-pick combinan commits de maneras diferentes
- merge crea un commit nuevo que une dos ramas
- rebase copia uno por uno los commits de la rama actual encima de la rama objetivo
- cherry-pick se parece a rebase, pero con otra sintaxis, y copia commits a la rama actual
git rebase --onto main otherbranch mybranchpuede usarse para mover solo algunos commits demybranchencima demain- En la estructura de ejemplo, serviría si quisieras rebasear solo
FyGsobremain - Es una sintaxis difícil de recordar porque incluye tres nombres de ramas
- En la estructura de ejemplo, serviría si quisieras rebasear solo
commit y otros términos confusos
commiten Git se usa tanto como verbo como como sustantivo- Verbo: “haz commit con frecuencia”
- Sustantivo: el commit más reciente de
main
- Un commit de Git puede verse de tres maneras
- Como una instantánea del estado actual de todos los archivos
- Como un diff respecto al commit padre
- Como el historial de todos los commits anteriores
- Cada comando trata el commit de forma distinta
git showtrata el commit como si fuera un diffgit logtrata el commit como historialgit restoretrata el commit como una instantánea
blob,tree,origin,upstream,downstream, la relación entrefetchypull,stash,worktree,subtreeysubmoduletambién forman parte de la terminología confusa de Git- Entre los términos de GitHub, “pull request”, “squash and merge” y “rebase and merge” se consideran casos confusos
Los tres ejes de la confusión repetida
- La terminología de Git que más confunde puede resumirse en tres puntos
heades una rama, yHEADes la rama actual- “remote tracking branch” y “branch that tracks a remote” son cosas diferentes
index,stagedycachedapuntan todos a la misma cosa
- the most confusing git terminology de 2012 también habla de cómo la terminología de Git se conecta con la de CVS y Subversion
- La confusión terminológica de Git no aparece solo en funciones raras, sino que se repite a lo largo de sus funciones centrales
- Sobre todo al hablar de ramas, “tracking” se usa de varias maneras y al principio es difícil notar las diferencias
- Algunas explicaciones incluyen rincones poco comunes de Git, así que queda la advertencia de que podría haber errores en algunos detalles
1 comentarios
Opiniones de Hacker News
Los nombres de ramas,
HEADy las etiquetas son todos punteros.HEADapunta al commit que estás viendo en este momento, y un nombre de rama comomy-featureapunta al commit más reciente de esa rama. Si hacesgit checkout -b my-featuredesdemain,main,my-featureyHEADterminan apuntando todos al commit más reciente demain.Cada vez que haces un commit en
my-branch,HEADymy-branchse mueven para apuntar al nuevo commit. detached HEAD significa que ningún branch apunta al commit al que apuntaHEAD. La diferencia entre una etiqueta y una rama es que la etiqueta apunta a un commit específico y no se mueve.Otra cosa que me confundía era que muchos comandos parecen inconsistentes porque Git asume argumentos predeterminados.
git checkout file.txtes lo mismo quegit checkout HEAD -- file.txt, y cuando estás enmy-branch,git rebase maines lo mismo quegit rebase main my-branch. Aunque esta última forma también se puede ejecutar desde otra rama.Por último, cuando todo se enreda, el comando que te permite salir de casi cualquier estado extraño es
git reflog. Muestra todos los commits a los queHEADapuntó. Aun así, me alegra que Git reconozca esta confusión y esté creando comandos con una interfaz menos sorpresiva y más simple.reflog.Otra cosa que muchas personas que usan Git no tienen muy presente es que las refs de ramas o etiquetas remotas que trajiste también son todas refs, así que puedes referenciarlas en cualquier lugar donde se pueda usar un nombre de ref.
Por ejemplo, si quieres decir “no me importa qué haya en esta rama; no hagas fast-forward, ni merge, ni rebase, simplemente sobrescribe la rama local con el contenido remoto”, puedes hacer esto:
git checkout foo,git reset --hard origin/foo.HEADno apunta directamente a un commit; normalmente apunta a un puntero de rama comomy-branch, y ese puntero de rama apunta al commit. El estado detached HEAD es la excepción.Cuando haces un commit en
my-branch, no es queHEADymy-branchse muevan ambos al nuevo commit;HEADsigue apuntando al punteromy-branch, y solomy-branchse mueve para apuntar al nuevo commit. Claro que, si siguesHEADa través demy-branchhasta el commit, llegas al nuevo commit.detached HEAD es el estado en el que
HEADapunta directamente a un commit en vez de a un puntero de rama. Incluso en detached HEAD,HEADy un puntero de rama pueden apuntar ambos al commit más reciente. Si mirasgit log --decorate, suele mostrarse como(HEAD, my-branch)en el commit más reciente, en lugar del habitual(HEAD -> my-branch).En cambio, las etiquetas anotadas pueden contener un mensaje, una marca de tiempo, un SHA, etc. Son objetos Git propiamente dichos y se comportan de forma bastante parecida a los objetos commit, aunque normalmente siguen referenciando otro objeto Git, por lo general un commit.
git rev-parse HEADantes de empezar un procedimiento delicado con muchos conflictos o varios movimientos, y copiar esa cadena en un lugar seguro.Te da la tranquilidad de haber marcado exactamente el último estado que sabes que estaba bien. Normalmente puedes volver al último estado correcto, no a algún punto varios pasos dentro del caos. Claro, siempre que no hayas usado sin cuidado cosas como
git gcogit filter-branch.Suena a broma, pero no lo es: no es del todo falso decir que para usar Git realmente necesitas un título en ciencias de la computación. Estoy de acuerdo en que los argumentos predeterminados de Git generan una experiencia de uso muy inconsistente.
git checkout .,git reset --hard/softygit cleanupparecen todos muy parecidos, pero en realidad son muy distintos. Aun así, sigo prefiriendo Git antes que Perforce o SVN. Antes me gustaba un poco más Mercurial, aunque no sé si seguiría siendo así hoy.git refloges el rey y muestra la verdad.Recuerdo haber leído que a Linus Torvalds le sorprendía que la gente empezara a usar Git directamente, sin ponerle encima una capa más amable. Si la industria fuera capaz de hacer autocrítica, admitiría que elegir Git fue un error y se habría pasado a otro sistema de control de versiones. Sin desperdiciar cantidades enormes de tiempo en una herramienta que, al final, solo guarda texto.
Claro que podría mejorar. Aun así, siempre me sorprende el odio que recibe Git. Git es una herramienta extraordinaria y está muy por delante de herramientas como Word o Google Docs, hechas para no desarrolladores.
Quizá sea porque tengo edad suficiente para recordar cuando Subversion era el rey.
Cada semana veo a compañeros confundirse por Git, veo publicaciones pidiendo ayuda en línea, y vuelvo a ver artículos como este intentando explicar otra vez algo que debería ser simple.
No recuerdo a gente desperdiciando horas revirtiendo los desastres que habían creado en SVN, TFVC, Perforce, etc., mientras programaban.
Las herramientas existen para hacer la vida más fácil. Si no lo logran, no valen el tiempo que exigen.
Todavía me gustaría que existiera esa diversidad. La monocultura de Git también significa que cualquier intento de reemplazar Git primero tiene que volver a implementar Git. Así que quedamos atados a Git casi para siempre, como con ASCII o la tecla Scroll Lock.
La preocupación central es que GitHub es tan bueno que, a menos que GitHub separe a Git del motor de gestión de texto y permita otros motores, quedaremos atados a Git.
https://trolololo.xyz/github
https://news.ycombinator.com/item?id=38098109
Son términos como abstracciones con fugas.
cache, que era el término más de ciencias de la computación.Luego parece que Junio lo cambió a
index, más específico para un sistema de control de versiones distribuido, y la mayoría de los usuarios lo llamaban staging area. Ahora la evolución de ese término quedó fosilizada en la UI y en las partes internas de Git.pull.origin/masterno es el master que está en origin, sino el commit al que apuntaba el últimoorigin/masterconocido. Este valor se actualiza cuando hacesfetch, ypullhacefetchautomáticamente.Si miras
ls -l .git/refs/remotes/origin/master, verás queorigin/masterno es más que un archivo en tu sistema. También puedes ver cuándo cambió. No se actualiza mágicamente.Si hubo cambios cuando ejecutaste
git fetch origin, cambiarán tanto la marca de tiempo como el contenido. Puedes comprobarlo concat .git/refs/remotes/origin/master.Los fundamentos de Git son muy simples, al punto de que puedes implementar sus estructuras de datos principales y unas cuantas operaciones en un día. Vale mucho la pena saberlo. De alguna manera, Git logró construir una interfaz de usuario muy compleja sobre un núcleo bastante simple.
master. Normalmente no puedo hacer push amaster, o de todos modos no quiero trabajar directamente enmaster. Entonces no hay razón para tener una rama localmastermodificable.Así que solo necesito actualizar
origin/master, y confetchbasta.git fetch && git pullEstoy esperando jubilarme dentro de 3 años y lavarme las manos de esta basura rota.
git status, y agregar la bandera--porcelainayudó muchísimo. Produce una salida más concisa y fácil de parsear programáticamente.Me dio curiosidad saber cuántos comandos más tenían salidas más legibles por máquina, y terminé llegando a la página “Git Internals - Plumbing and Porcelain” de git-scm. En resumen, Git no fue escrito desde el principio tanto como un sistema de control de versiones pulido, sino como una colección de herramientas para manejar control de versiones.
Muchas personas que usaron Git desde el principio aprendieron tareas de control de versiones con esos comandos de bajo nivel, y propagaron ese flujo de trabajo a otros. Esa es la capa de plumbing. Más tarde Git creó una capa más refinada, a la que llama porcelain.
Todavía no sé del todo qué comandos pertenecen a qué capa, pero me ayudó a entender los nuevos flujos de trabajo recomendados en los últimos años. También me permite juzgar mejor cuándo cambiar mi propio flujo de trabajo.
https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
Lamentablemente, para hacerlo más rápido, hardcodearon esa UI, pero no la mejoraron.
Mirando hacia atrás, al principio seguramente me resultó extremadamente confuso, pero creo que fingía que no para parecer cool.
https://news.ycombinator.com/item?id=28143078
“Your branch is up to date with ‘origin/main’” en realidad es un poco engañoso. Se puede interpretar como que la rama
mainestá actualizada, pero en realidad no es así.Aunque tampoco hace falta ponerse demasiado “técnicamente hablando” con esto. ¿No aplica aquí el problema de los dos generales? Si uno se pone realmente meticuloso, no se puede saber si está “actualizado” en este preciso instante. Lo mismo ocurre aunque hagas
fetchantes destatus.Entonces, ¿cuál sería una expectativa razonable? Algo así como que la ref en la base de datos de objetos de mi computadora era igual, la última vez que se comprobó, a la ref de otra base de datos de objetos allá lejos.
Una simple llamada a
statusno puede hacer unfetchde red ni recordarte cada vez los principios básicos de la herramienta que estás usando. Personalmente, así lo veo. Pero, sorprendentemente, muchos comentarios en la respuesta de StackOverflow consideran que hacen falta justamente esas dos cosas.Una frase como “is up to date with the origin’s main as of your last fetch 5 days ago” sería más razonable, porque solo te dice cuánto tiempo pasó desde tu último
fetch.[1] https://stackoverflow.com/questions/27828404/why-does-git-st...
Otra cosa: pensaba que
ORIG_HEAD, igual queFETCH_HEAD, tenía que ver con el “head de origin”. Pero no era así. Esta “pseudoref” parece usarse como punto de guardado antes de reescrituras más complejas como rebase, y se implementó antes de que existiera el reflog. Probablemente signifique “original head”.“Your branch is up to date with ‘origin/main’” es técnicamente correcto, pero la expresión up to date, leída por encima, sugiere dos cosas: que
maines igual aorigin/main, y queorigin/maintambién está actualizado; es decir, da la sensación de que se actualizó durante el comando, o que se mantiene actualizado automáticamente y que la sincronización reciente fue exitosa.Esto no es una señal legible por máquina que deba ser verdadera durante un ciclo de CPU, sino un mensaje de estado visible para el usuario. No hace falta llegar hasta la teoría de redes para que esa interpretación sea razonable.
“Up to date” se parece más a estar al día en el tiempo que a una cuestión de espacio. La posición de la rama local se acerca más al espacio de “a dónde apunta esta rama”, mientras que el estado de la ref remota se acerca más al tiempo de “cuándo actualicé por última vez la ref remota”. Claro que esto es muy subjetivo y ambos pueden entenderse en los dos sentidos.
Una mejor redacción podría ser algo como “Your branch matches origin/main”, “Your branch’s head is the same as origin/main” o “Your branch is pointing to the same commit as origin/main”. Es un compromiso entre claridad y verbosidad. También se podría agregar entre paréntesis, como sugiere el autor, “remote ref last updated 5 days ago”.
statushaga unfetchde red. Creo que las operaciones de red deben ser explícitas, no implícitas. Cuando hagogit status, quiero saber el estado de mi repositorio en el sistema de archivos.Si quiero saber qué está pasando en otro remoto, puedo hacer
fetchyo mismo y luego comparar.status. Puede haber una condición de carrera mientras se imprime la información en la terminal, pero es una ventana de tiempo muy pequeña.En general, eso significa que se quiere un
fetch. El problema de los dos generales solo aplica del lado remoto. Si el remoto no recibe confirmación de que el cliente recibió los datos, puede seguir retransmitiendo durante un tiempo. Ese no es problema del cliente.Si el cliente no recibe datos del servidor, el comportamiento correcto sería quedarse esperando y, tras un timeout, imprimir un error; probablemente eso es lo que ya hace
fetch.Esta herramienta se comparte con frecuencia y encaja muy bien aquí. Visualiza el modelo interno de Git y el efecto de varios comandos.
https://learngitbranching.js.org/
Aprendí más usando esta herramienta durante 10 minutos que en los casi 10 años anteriores de experiencia con Git. La recomiendo muchísimo.
“pull” es la acción de fusionar cambios remotos en un repositorio local. Lo que el usuario realmente está pidiendo es que el servidor fusione sus cambios remotos en alguna rama. El “merge request” de GitLab es el término correcto.
He visto a alguien hacer
fetchy que desaparecieran commits. Quedaron ocultos porque el repositorio retrocedió en el tiempo. Git oculta esos detalles enstatus, y si no usasgit-promptopowerline-shell, es como trabajar a oscuras.git-request-pull(1)era que todos tuviéramos nuestros propios repositorios Git en lugares como kernel.org/git o redhat.com/git, y pidiéramos por email que otros hicieran pull desde repositorios ubicados en servidores, organizaciones y dominios distintos.GitHub se inspiró en el comando
request-pullde Git, pero lo reinterpretó como fusionar de un repositorio de GitHub a otro repositorio de GitHub.Al menos yo siempre lo leí así.
En resumen, no es una solicitud al servidor, sino a otra persona. Es pedirle a la otra persona que haga pull de tu rama para revisar los cambios con los que quieres contribuir al proyecto.
[0] https://git-scm.com/book/en/v2/Distributed-Git-Contributing-...