6 puntos por GN⁺ 2024-05-27 | 1 comentarios | Compartir por WhatsApp
  • Un PDF que organiza las tareas en las que uno suele atorarse en Git para poder consultar rápido creación de commits, cambio de ramas, comparación de cambios, sincronización remota y comandos para deshacer cambios
  • El flujo de trabajo local se centra en git status, git add, git commit y git diff, y muestra cómo revisar el estado antes y después del staging para crear commits
  • El trabajo con ramas usa git switch, git checkout, git branch, git merge, git rebase y git cherry-pick para crear, moverse, fusionar y reubicar cambios
  • La exploración del historial y la recuperación incluyen git log, git blame, git show, git reflog, git reset y git restore, y también se pueden usar ramas, tags, HEAD y ramas remotas como referencias de commits
  • Las operaciones con repositorios remotos cubren git remote add, git fetch, git pull, git push, git push --force-with-lease y git push --tags para traer y enviar cambios

Iniciar un repositorio y revisar el estado actual

  • Iniciar un repositorio nuevo:
    • git init
  • Clonar un repositorio existente:
    • git clone $URL
  • Revisar la ubicación y el estado actual:
    • git status

Preparar y crear commits

  • Agregar archivos no rastreados:
    • git add $FILE
  • Agregar todos los archivos no rastreados y los cambios sin staging:
    • git add .
  • Hacer staging solo de algunas partes de los cambios de un archivo:
    • git add -p
  • Eliminar o mover un archivo:
    • git rm $FILE
    • git mv $OLD $NEW
  • Excluir un archivo del seguimiento de Git sin borrarlo:
    • git rm --cached $FILE
  • Cancelar todo el staging:
    • git reset HEAD
  • Crear un commit:
    • git commit
    • abre un editor de texto para escribir el mensaje
  • Hacer un commit indicando el mensaje directamente:
    • git commit -m 'message'
  • Hacer un commit con mensaje incluyendo también los cambios sin staging:
    • git commit -am 'message'

Moverse entre ramas y administrarlas

  • Cambiar de rama:
    • git checkout $NAME
    • git switch $NAME
  • Crear una rama:
    • git checkout -b $NAME
    • git switch -c $NAME
  • Ver la lista de ramas:
    • git branch
  • Ordenar ramas según el commit más reciente:
    • git branch --sort=-committerdate
  • Eliminar una rama:
    • git branch -d $NAME
  • Forzar la eliminación de una rama:
    • git branch -D $NAME

Explorar el historial y rastrear código

  • Ver el historial de una rama:
    • git log main
  • Ver en gráfico la relación entre dos ramas:
    • git log --graph a b
  • Ver el log en una sola línea:
    • git log --oneline
  • Ver quién modificó por última vez cada línea de un archivo:
    • git blame $FILENAME
  • Ver todos los commits que modificaron un archivo:
    • git log $FILENAME
  • Buscar commits que agregaron o eliminaron un texto específico:
    • git log -S banana

Comparar cambios

  • Ver la diferencia entre un commit y su commit padre:
    • git show $COMMIT_ID
  • Ver la diferencia entre un merge commit y los padres que fusionó:
    • git show --remerge-diff $COMMIT_ID
  • Ver un resumen del diff:
    • git diff $COMMIT_ID --stat
    • git show $COMMIT_ID --stat
  • Comparar dos commits:
    • git diff $COMMIT_ID $COMMIT_ID
  • Comparar solo un archivo con un commit específico:
    • git diff $COMMIT_ID $FILENAME
  • Comparar todos los cambios en staging y sin staging:
    • git diff HEAD
  • Comparar solo los cambios en staging:
    • git diff --staged
  • Comparar solo los cambios sin staging:
    • git diff

Archivos de configuración y opciones

  • Archivo de configuración local de Git:
    • .git/config
  • Archivo de configuración global de Git:
    • ~/.gitconfig
  • Lista de archivos a ignorar:
    • .gitignore
  • Definir una opción de configuración:
    • git config user.name 'Julia'
  • Definir una opción global:
    • git config --global ...
  • Agregar un alias:
    • git config alias.st status
  • Ver las opciones de configuración disponibles:
    • man git-config

Modificar historial y restaurar archivos

  • Deshacer el commit más reciente y mantener intacto el directorio de trabajo:
    • git reset HEAD^
  • Deshacer un rebase fallido:
    • git reflog BRANCHNAME
    • git reset --hard $COMMIT_ID
  • Combinar los últimos 5 commits en uno solo:
    • git rebase -i HEAD^^^^^^
    • cambia pick por fixup en los commits que quieras combinar
  • Traer la versión de un archivo desde otra rama o commit:
    • git checkout $COMMIT_ID $FILE
    • git restore $FILE --source $COMMIT_ID
  • Cambiar el mensaje de un commit o agregar un archivo que faltó:
    • git commit --amend

Fusionar ramas

  • Fast-forward merge para actualizar la rama actual con otra rama:
    • git switch main
    • git merge banana
  • Copiar un commit a otra rama:
    • git cherry-pick $COMMIT_ID
  • Unir ramas divergentes con rebase:
    • git switch banana
    • git rebase main
  • Unir ramas divergentes con merge:
    • git switch main
    • git merge banana
    • git commit
  • Unir ramas divergentes con squash merge:
    • git switch main
    • git merge --squash banana
    • git commit

Repositorios remotos y sincronización

  • Agregar un repositorio remoto:
    • git remote add $NAME $URL
  • Hacer push de la rama main al remoto origin:
    • git push origin main
  • Subir por primera vez una rama a origin y configurar la rama de seguimiento:
    • git push -u origin $NAME
  • Hacer push de la rama actual a su rama remota de seguimiento:
    • git push
  • Force push:
    • git push --force-with-lease
  • Hacer push de tags:
    • git push --tags
  • Traer cambios sin modificar la rama local:
    • git fetch origin main
  • Hacer fetch de todas las ramas:
    • git fetch --all
  • Hacer rebase sobre la rama actual después de fetch:
    • git pull --rebase
  • Hacer merge sobre la rama actual después de fetch:
    • git pull origin main
    • git pull

Formas de referirse a un commit

  • En lugar de $COMMIT_ID, se pueden usar varias referencias:
    • rama: main
    • tag: v0.1
    • ID de commit: 3e887ab
    • rama remota: origin/main
    • commit actual: HEAD
    • commit de hace 3 commits: HEAD^^^
    • commit de hace 3 commits: HEAD~3

Descartar cambios

  • Eliminar de un archivo tanto los cambios en staging como los cambios sin staging:
    • git checkout HEAD $FILE
  • Eliminar de un archivo los cambios sin staging:
    • git checkout $FILE
  • Eliminar todos los cambios en staging y sin staging:
    • git reset --hard
  • Guardar en stash los cambios en staging y sin staging:
    • git stash
  • Eliminar archivos no rastreados:
    • git clean

1 comentarios

 
GN⁺ 2024-05-27
Comentarios de Hacker News
  • Hay varios comandos que son joyitas ocultas: git diff --staged muestra la diferencia entre el último commit y lo que tienes en staging ahora, así que sirve para verificar que solo entre lo que querías al subir archivos o hunks de forma detallada
    git log <file> muestra solo los commits en los que cambió ese archivo, así que reduce la necesidad de revisar todo el git log, y es especialmente útil en archivos que no cambian seguido
    Si usas git add --patch y quieres elegir con más detalle que los hunks en que Git divide los cambios, vale la pena probar la opción e. Te indica cómo incluir o excluir líneas individuales, así que no hace falta memorizarlo

    • Apenas ahora me enteré de que git diff --staged es un alias de git diff --cached. Llevo años usando cached, pero staged sí se siente mucho más intuitivo. Aunque no sé si podré borrar esa memoria muscular
    • Tener varios de estos comandos como alias ahorra bastante tiempo y también baja la barrera para mantener el trabajo mejor organizado
      Si te interesa, puedes ver o sugerir alias aquí: https://gitlab.com/Falimonda/gitrc
    • Si configuras git config --global commit.verbose true, Git agrega automáticamente la diferencia en staging en el área de comentarios del editor de mensajes de commit
  • Uno de mis comandos favoritos que no aparece en la lista es git log -LstartLine,endLine:fileName
    Es como un git blame extendido: muestra cómo cambiaron con el tiempo las líneas indicadas y quién las cambió. Hace seguimiento incluso si cambian los números de línea o si el archivo se mueve
    Responde preguntas como “¿quién hizo esto, cuándo y por qué?” o “¿desde cuándo está así?”

  • Me recordó la frase de Phil Karlton: “Solo hay dos cosas difíciles en ciencias de la computación: invalidación de caché y poner nombres.”
    Desde esa perspectiva, que un comando de Git se llame blame siempre me ha parecido bastante raro, y no soy el único que lo pensó
    What does 'git blame' do? https://stackoverflow.com/questions/31203001/what-does-git-blame-do
    Blame someone else for your bad code https://news.ycombinator.com/item?id=27963868
    Git blame should be called git credit https://dev.to/damcosset/git-blame-should-be-called-git-credit-27h5
    Does Git Blame sound too negative? https://www.reddit.com/r/ProgrammerHumor/comments/r5lzyo/does_git_blame_sound_too_negative_just_look_up/

    • Pensando en que la herramienta se llama git, blame suena como un nombre acorde a la marca
    • De acuerdo. Yo cambié blame a who en mi configuración. De hecho, como soy usuario hardcore de Vim, simplemente uso w
  • Me gustó la forma en que aprendió Git. Al principio empecé con Sourcetree, una GUI de Git de terceros, y era muy fácil de usar y entender
    Luego pasé a git gui, aunque esta etapa se puede saltar. Después me empezó a dar flojera abrir una UI y aprendí los comandos de terminal
    Lo recomiendo a quienes empiezan con Git, aunque claro, esa es solo mi experiencia n=1

    • Lo único que sé de Git es clone, pull, commit y push, y hasta ahora con eso me ha bastado. Git puede ser así de simple. Claro, si hace falta también puedes aprender usos más complejos
    • No quiero volver a los comandos de terminal. Los repositorios y flujos de trabajo de Git se entienden mucho mejor cuando los visualizas, así que Git es casi una herramienta que te pide usar GUI
      En el momento en que empecé a usar Magit, Git empezó a tener sentido para mí
    • Yo me cambié de Sourcetree a Fork. Es muy parecido, pero el rendimiento es muchísimo mejor y la UI se siente más fluida. Al menos, si Sourcetree no ha mejorado en estos últimos años
    • Seguí un camino parecido, pero usé GitKraken y Sublime Merge, y como facilitan incluso las tareas complejas, me quedé con Sublime Merge
      Solo tengo cosas buenas que decir de Sublime Merge
  • Se preguntan si hay una razón histórica por la que Git es innecesariamente complejo.
    Viendo esta chuleta, esa complejidad se siente todavía más real.

    • Sí. Porque Git fue una herramienta creada por Linus Torvalds para hacer posible y hacer avanzar el desarrollo del kernel de Linux.
      Que se haya usado tan ampliamente puede haber sido un “accidente feliz”, o quizá una desgracia. GitHub tuvo mucho mérito al popularizar Git como “hosting gratuito de Git”, pero no creo que Git sea el sistema de control de versiones ideal para muchos proyectos.
      Como fue hecho para Linus y para Linux, a veces la terminología y el flujo de trabajo se sienten invertidos frente a los sistemas de control de versiones más comunes. Incluso el nombre Pull Request viene de que, en el desarrollo de Linux, Linus o un mantenedor “jalan” los cambios hacia el árbol del kernel. En otros sistemas normalmente se le llama Merge Request, lo cual es mucho más lógico en el sentido de que integra al árbol cambios vengan de donde vengan.
      Git era prácticamente el único sistema de control de versiones distribuido de código abierto que podía manejar bases de código gigantes con rapidez, y por eso también ganó inercia en las grandes empresas.
      Para proyectos personales prefiero mucho más Mercurial y de hecho lo uso, pero si pruebas Hg en una base de código grande como Android AOSP, se pone terriblemente lento. Mercurial tiene una mejor interfaz de usuario y un mejor sistema de comandos, e incluso puede levantar un servidor web al vuelo para ver el árbol de cambios, pero al estar escrito en Python no es ideal en rendimiento ni escalabilidad.
    • Parte de la complejidad viene de que, en las primeras versiones, el costo de agregar nuevos subcomandos era bastante alto. Como resultado, se le siguieron pegando funciones a subcomandos existentes como git checkout, y eso volvió más confusa la experiencia de usuario.
      En el Git moderno, el costo de agregar subcomandos nuevos bajó, y empezaron a aparecer comandos más específicos como git switch.
    • En la mayoría de los flujos de trabajo basta con conocer unos cuantos comandos de Git como log, add, commit, pull y push. También hay herramientas con GUI.
      Sinceramente, no creo que Git sea tan complejo para el trabajo común. Más bien tiene muchas funciones potentes disponibles cuando las necesitas.
    • Git no es innecesariamente complejo. El flujo de trabajo que fue creado para soportar ya era especialmente complejo, y sus usuarios objetivo —los desarrolladores del kernel— eran muy técnicos, así que la complejidad no era un gran problema.
      Los desarrolladores del kernel tenían que comunicarse entre sí de forma descentralizada y volver a trabajar sobre el trabajo de otros. ¿Cómo trabajas en un proyecto donde 15,000 personas escriben código? Git fue creado para ese propósito.
      Es tan complejo como necesita serlo para soportar el flujo de trabajo para el que fue diseñado. Hacer una herramienta más simple es fácil, pero entonces no podría soportar flujos complejos como el desarrollo del kernel de Linux. Así que no es innecesariamente complejo, aunque sí es más complejo de lo necesario para usos simples.
      Si necesitas un frontend menos complejo, hay muchas herramientas que facilitan usar Git. Si no eres desarrollador de software de tiempo completo, no te recomendaría usar Git directamente. Pero si sí lo eres, vale la pena dedicar tiempo a entender los conceptos internos, y una vez que lo entiendes, Git es bastante elegante para lo que hace.
      No diría que conocer Git determina si calificas como desarrollador de software o no, pero si entender por tu cuenta cómo funciona este tipo de sistema básico no te produce nada de dopamina, quizá esta área no va contigo. Y no pasa nada: no todo es para todo el mundo.
      Para aprender a moverte en Git, recomiendo https://learngitbranching.js.org. Claro, también aplica https://xkcd.com/1597/.
    • Porque un buen diseño de UI es una disciplina aparte y requiere esfuerzo adicional, así que por lo general simplemente se ignora.
  • No conocía git add -p y de verdad es muy útil.
    -p, --patch te permite elegir de forma interactiva los hunks del parche entre el índice y el árbol de trabajo para agregarlos al índice. Te da la oportunidad de revisar las diferencias antes de añadir los cambios al índice.

    • Siempre que puedo, elijo directamente los hunks del trabajo actual para armar commits coherentes. Eso me ayuda a ordenar mentalmente lo que hice, y cuando tengo que explicárselo a alguien, puedo hacerlo siguiendo el flujo de commits.
  • Algo que quisiera agregar aquí es la combinación de git commit --fixup $COMMIT_ID y git rebase -i upstream/master --autosquash.
    Se volvió una pieza esencial de mi flujo de trabajo reciente. En el ejemplo de Julia usan HEAD^^^^^ para rebasear los 5 commits anteriores, pero yo hasta hace poco usaba HEAD~5. Ahora me enteré de que simplemente puedes rebasear todos los commits hasta el HEAD de upstream.

    • git commit --fixup $COMMIT_ID lo tengo que adoptar de inmediato. Yo siempre hacía pequeños commits de corrección tipo “oops GET users/me”, y luego en el siguiente rebase interactivo los movía manualmente para hacerles fixup, pero esta forma es mucho mejor.
    • Apenas aprendí esto hace poco, y va muy bien cuando trabajas con patches apilados en Gerrit.
  • “Combinar los últimos 5 commits en uno: git rebase -i HEAD^^^^^^” me parece que debería ser git rebase -i HEAD~5.
    Asumiendo, claro, que lo que quieres rebasear es solo un historial lineal.
    https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git

  • La revelación más grande fue entender que todos los commits siguen ahí dentro. Las etiquetas y las ramas solo apuntan a commits, y los commits a los que una rama apuntaba antes no desaparecen
    Muchos comandos, probablemente casi todos, son no destructivos, así que es fácil referenciar estados anteriores de una rama
    Había visto git reflog, pero casi siempre lo usaba solo para recordar “¿cuál era esa rama en la que había trabajado antes?”
    Desarrollé un miedo medio saludable a las operaciones que pueden meterte en medio de una resolución de conflictos no deseada. Normalmente los conflictos aparecían por referenciar el commit o la rama equivocados
    Ese miedo no ha desaparecido por completo, pero al menos ahora me siento bastante cómodo recuperando trabajo anterior entre operaciones como un rebase

    • Jujutsu eliminó por completo ese miedo
      Si no sé si algo va a generar conflictos después de cierta operación, simplemente lo intento y veo qué pasa. Si hay conflictos, los resuelvo, revierto la operación que los causó, o creo un commit nuevo en otro lugar y dejo el conflicto para después
      Si ya sé que habrá conflictos, duplico la rama relacionada y pruebo de antemano, o ejecuto la operación conflictiva y sigo el proceso de resolución. Si resolverlo se vuelve demasiado enredado, puedo deshacer solo una parte de la resolución y seguir intentando, dejarlo en pausa un rato para hacer otra cosa, o descartar todo el árbol
  • Dicho sin rodeos, hay que dejar Git y mirar JJ
    https://martinvonz.github.io/jj/v0.17.1/
    Hasta Linus probablemente pensaría que la UI de Git es pésima. JJ tiene un flujo de trabajo excelente

    • Me cuesta estar de acuerdo con lo de no tener índice. Puede que para algunos flujos de trabajo eso sea más simple, pero para bien o para mal ya estoy demasiado acostumbrado al índice de Git, así que siento que mi forma de trabajar cambiaría bastante
      Aun así, fuera de haberlo probado un poco, no lo he usado a fondo en trabajo real, así que no puedo afirmarlo con total certeza