Herramientas de Git

Revision por Seleccion

Revision por Seleccion

Git te permite especificar commits o rangos de commits de varias formas. No son necesariamente obvias, pero vale la pena conocerlas.


checksum SHA-1 Corto

Git es lo suficientemente inteligente como para deducir el commit al que te refieres si le das los primeros caracteres del checksum SHA-1, siempre que sea lo suficientemente largo para ser unico (al menos 4 caracteres). Por ejemplo:

git log --abbrev-commit --pretty=oneline
734713b changed the version number
d921970 removed unnecessary test code
1c002dd first commit

La opcion --abbrev-commit muestra los valores cortos pero manteniendolos unicos. (por defecto muestra 7 caracteres)


Puedes hacer referencia a cualquiera de esos commits usando los primeros caracteres unicos de su checksum SHA-1. Por ejemplo:

# Los 3 comandos son equivalentes
git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
git show 1c002dd4b536e7479f
git show 1c002d

Referencias de Rama

La forma mas sencilla de referirse a un commit especifico es usar el nombre de una rama que apunte a el. Por ejemplo, si quieres ver a que objeto commit apunta la rama topic1:

git show topic1

Si quieres ver a que checksum SHA-1 apunta una rama, puedes usar el comando de fontaneria git rev-parse:

git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

Reflog

Mientras trabajas, Git registra silenciosamente donde han estado HEAD y tus ramas en los ultimos meses en el reflog. Puedes consultarlo con:

git reflog
734713b HEAD@{0}: commit: fixed refs handling
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by recursive.
1c002dd HEAD@{2}: commit: added some blame and merge stuff

Cada vez que la ultima referencia de tu rama es actualizada por cualquier motivo, Git guarda esa informacion en el historial temporal del reflog. Puedes usar la sintaxis @{n} para referirte a confirmaciones anteriores:

git show HEAD@{5}           # quinto HEAD anterior
git show master@{yesterday} # donde estaba master ayer

Es importante destacar que el reflog es estrictamente local. Solo existe en tu propio repositorio y refleja lo que tu has hecho. No tiene ninguna relacion con lo que otros hayan hecho en su copia del repositorio.


Seleccion por Ancestro

La otra forma principal de especificar un commit es a traves de su ancestro. Si colocas ^ al final de una referencia, Git lo resuelve como el padre de ese commit:

git show HEAD^     # padre de HEAD
git show HEAD^^    # abuelo de HEAD

En caso de que un commit tenga mas de 1 padre (en un merge commit por ejemplo), podriamos hacer algo como esto:

# Supongamos un merge topic1 -> dev
git show HEAD^ # El primer padre de HEAD (dev)
git show HEAD^2 # segundo padre de HEAD (topic1)

La otra sintaxis de ancestro es el tilde ~. HEAD~ es equivalente a HEAD^. La diferencia aparece cuando se combina con un numero: HEAD~3 significa el bisabuelo (primer padre, tres niveles atras), mientras que HEAD^3 seria el tercer padre de un merge commit.

git show HEAD~3 # 3 commits atras
git show HEAD^^^ # 3 commits atras

Rangos de Commits

Ahora que ya sabes como especificar commits individuales, veamos como especificar rangos. Supongamos un historial como el siguiente:

Commits alcanzables por master o experiment pero no por ambos

Doble punto

La sintaxis mas comun es el rango con doble punto .., que le dice a Git que resuelva todas los commits que son alcanzables desde una referencia pero no desde la otra:

git log master..experiment # Commits de experiment no alcanzados por master
D
C

Esto muestra todos los commits en experiment que aun no han sido fusionados en master. Si lo inviertes, ves los commits de master que no estan en experiment:

git log experiment..master # Commits de master no alcanzados por experiment
F
E

Esto es muy util para saber que vas a enviar a un remoto antes de hacer push:

git log origin/master..HEAD # Commits de HEAD no alcanzados por origin/master

Si HEAD no tiene nada que origin/master no tenga, el comando no devuelve nada.


Multiples puntos

Si necesitas filtrar por mas de dos ramas, puedes usar ^ o --not delante de cualquier referencia de la que no quieras ver confirmaciones:

# Comandos equivalentes
git log refA..refB          # commits en refB que no esten en refA
git log ^refA refB          # commits en refB que no esten en refA
git log refB --not refA     # commits en refB que no esten en refA

Esto es muy potente porque permite hacer lo mismo con mas de dos referencias, algo que la sintaxis de doble punto no puede hacer:

git log refA refB ^refC # commits en refA o refB pero no en refC
git log refA refB --not refC # commits en refA o refB pero no en refC

Triple punto

La sintaxis de triple punto especifica todos los commits que son alcanzables por cualquiera de las dos referencias pero no por ambas a la vez (diferencia simetrica):

git log master...experiment
F
E
D
C

Esto te muestra los commits de cada rama que no estan en la otra. Para que la salida sea mas clara, puedes añadir --left-right, que muestra de que lado del rango viene cada commit:

git log --left-right master...experiment
< F
< E
> D
> C

Con estos tres tipos de sintaxis (checksum SHA-1 corto, referencias de rama, y rangos) tienes un control muy preciso sobre cualquier punto o tramo del historial de tu proyecto.


Herramientas de Git

Organizacion Interactiva

Organizacion Interactiva

Git viene con unos cuantos scripts que hacen que algunas tareas de linea de comando sean mas faciles de usar. Aqui veremos unos cuantos comandos interactivos que te ayudaran a preparar tus commits para incluir solo ciertas combinaciones y partes de los archivos.


Estas herramientas son muy utiles si modificas unos cuantos archivos y decides que esos cambios esten en varios commits enfocados, en lugar de en un gran commit problematico. Si ejecutas git add con -i o --interactive, Git entra en un modo de celda interactiva:

git add -i
           staged     unstaged path
  1:    unchanged        +0/-1 TODO
  2:    unchanged        +1/-1 index.html
  3:    unchanged        +5/-1 lib/simplegit.rb

*** Commands ***
  1: status   2: update   3: revert   4: add untracked
  5: patch    6: diff     7: quit     8: help
What now>

Puedes ver que este comando te muestra una vista muy diferente de tu area de ensayo: la misma informacion que git status pero mas informativa. Muestra los cambios que has realizado a la izquierda y los cambios que no has preparado a la derecha.


Organizar y Desorganizar Archivos

Si tecleas 2 o u en el prompt What now>, el modo interactivo te pregunta que archivos quieres organizar:

What now> 2
           staged     unstaged path
  1:    unchanged        +0/-1 TODO
  2:    unchanged        +1/-1 index.html
  3:    unchanged        +5/-1 lib/simplegit.rb
Update>>

Para organizar TODO e index.html, tecleas los numeros:

Update>> 1,2
           staged     unstaged path
* 1:    unchanged        +0/-1 TODO
* 2:    unchanged        +1/-1 index.html
  3:    unchanged        +5/-1 lib/simplegit.rb
Update>>

El * antes de cada archivo indica que esta seleccionado para ser organizado. Al pulsar Enter sin escribir nada, Git organiza todo lo seleccionado:

updated 2 paths
*** Commands ***
  1: status   2: update   3: revert   4: add untracked
  5: patch    6: diff     7: quit     8: help
What now> 1
           staged     unstaged path
  1:        +0/-1      nothing TODO
  2:        +1/-1      nothing index.html
  3:    unchanged        +5/-1 lib/simplegit.rb

Para desorganizar un archivo, usas la opcion 3 o r (revert). Para ver las diferencias de lo que has organizado, usas la opcion 6 o d (diff).

*** Commands ***
  1: status     2: update      3: revert     4: add untracked
  5: patch      6: diff        7: quit       8: help
What now> 6
           staged     unstaged path
  1:        +1/-1      nothing index.html
Review diff>> 1
diff --git a/index.html b/index.html
index 4d07108..4335f49 100644
--- a/index.html
+++ b/index.html
@@ -16,7 +16,7 @@ Date Finder

 <p id="out">...</p>

-<div id="footer">contact : support@github.com</div>
+<div id="footer">contact : email.support@github.com</div>

 <script type="text/javascript">

Parches de Organizacion

Lo que realmente es potente en Git es que puedes organizar partes de archivos en lugar de archivos enteros. Por ejemplo, si tienes dos cambios en lib/simplegit.rb y quieres organizar uno pero no el otro, es muy facil hacerlo.


Desde el prompt interactivo, tecleas 5 o p (patch). Git te preguntara que archivos quieres organizar parcialmente y, para cada seccion de los archivos seleccionados, mostrara bloques del archivo diferencial preguntandote si los quieres organizar uno por uno:

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index dd5ecc4..57399e0 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -22,7 +22,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log -o 25 #{treeish}")
+    command("git log -n 30 #{treeish}")
   end
...
Stage this hunk [y,n,a,d,/,j,J,g,e,?]?

ComandoAcciónDescripción detallada
yOrganizarOrganiza el bloque actual.
nNo organizarOmite el bloque actual sin organizarlo.
aOrganizar todoOrganiza este bloque y todos los bloques restantes de forma automática.
dNo organizar nada másOmite este bloque y todos los que quedan en el archivo.
gIr aTe permite seleccionar un bloque específico para saltar a él.
/BuscarBusca un bloque que coincida con una expresión regular (regex).
jSiguiente sin decidirDeja este bloque pendiente y salta al siguiente bloque que no hayas decidido.
JSiguiente bloqueDeja este bloque pendiente y salta al bloque inmediatamente posterior.
kAnterior sin decidirDeja este bloque pendiente y vuelve al anterior bloque que no hayas decidido.
KBloque anteriorDeja este bloque pendiente y vuelve al bloque inmediatamente anterior.
sDividirDivide el bloque actual en bloques más pequeños para un control más preciso.
eEditarPermite editar manualmente el contenido del bloque actual.

Generalmente, teclearas y o n para cada bloque. Cuando termines, Git crea un script de organizacion interactivo y ejecuta git commit para confirmar los archivos parcialmente organizados.


No necesitas estar en el modo interactivo para hacer la organizacion parcial de archivos. Puedes iniciar el mismo script con git add -p o git add --patch directamente en la linea de comando. Tambien puedes usar reset --patch para desorganizar parcialmente, checkout --patch para descartar partes de archivos, y stash save --patch para guardar solo partes.


Herramientas de Git

Guardado Rapido y Limpieza

Guardado Rapido (Stash)

Muchas veces, cuando has estado trabajando en una parte de tu proyecto, las cosas se encuentran desordenadas y quieres cambiar de ramas para trabajar en algo mas. El problema es que no quieres hacer un commit de un trabajo a medias. La respuesta a este problema es el comando git stash.


El guardado rapido toma el desorden de tu directorio de trabajo — es decir, tus archivos controlados por la version modificados y cambios almacenados en el area de ensayo — y lo guarda en una pila de cambios sin terminar que puedes volver a usar en cualquier momento.


Demo basica

Supongamos que estas trabajando en un par de archivos y posiblemente en tu etapa uno de cambios. Si ejecutas git status, puedes ver tu estado sucio:

git status
Changes to be committed:
  (use "git restore --staged..." to unstage)
        modified: index.html

Changes not staged for commit:
  (use "git add..." to update what will be committed)
  (use "git checkout --" to discard changes in working directory)
        modified: lib/simplegit.rb

Ahora quieres cambiar de rama, pero no quieres hacer commit del trabajo que has estado haciendo. Para crear un nuevo guardado rapido de tus archivos:

git stash
Saved working directory and index state \
  "WIP on master: 049d078 added the index file"
HEAD is now at 049d078 added the index file
(To restore them type "git stash apply")

Tu directorio de trabajo esta limpio:

git status
# On branch master
nothing to commit, working directory clean

En este punto, puedes facilmente cambiar de ramas y hacer trabajo en otro lugar. Tus cambios estan guardados en tus archivos. Para ver que guardados rapidos has almacenado, puedes usar git stash list:

git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log

Para volver a aplicar el que acabas de guardar, puedes ejecutar el comando que aparece en la salida del comando original git stash. Si quieres hacer entrada a uno de los guardados rapidos anteriores, puedes especificarlo poniendo su nombre de esta manera: git stash apply stash@{2}. Si no especificas un guardado, Git adopta el guardado mas reciente e intenta hacerlo entrada:

git stash apply
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#       modified:   index.html
#       modified:   lib/simplegit.rb

Puedes ver que Git vuelve a aplicar los cambios guardados en el stash directamente sobre tus archivos.


En este caso, tenias un directorio de trabajo limpio e intentaste aplicar el stash en la misma rama donde lo habias creado. Sin embargo, no es obligatorio que el directorio este limpio, ni tampoco que estes en la misma rama para poder recuperar tus cambios con exito.


De hecho, puedes crear un stash en una rama, cambiarte a otra y aplicar los cambios alli. Tambien puedes recuperar un stash aunque tengas cambios locales sin confirmar en tu directorio de trabajo, en ese caso, si los cambios chocan, Git simplemente generara un conflicto de fusion para que lo resuelvas.


Los cambios en tus archivos se han vuelto a aplicar, pero el estado del area de preparacion no se restauro. Para lograr eso, tienes que ejecutar el comando git stash apply con la opcion --index, la cual le indica a Git que intente reaplicar tambien los cambios que ya tenias listos para confirmar (staged). Si lo hubieras ejecutado de esta manera, habrias recuperado tu espacio de trabajo exactamente en su posicion original.


Ten en cuenta que usar la opcion --index no elimina el stash de tu lista, solo mantiene los cambios en tus archivos. Para remover ese stash de forma permanente, debes ejecutar el comando git stash drop seguido del identificador del guardado que deseas eliminar:

git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log
git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900edec309ff7f3be04aeeef2)

Guardado rapido creativo

Hay algunas variantes del guardado rapido que pueden ser utiles tambien. La primera opcion que es muy popular, --keep-index le dice a Git que no guarde nada que tu ya hayas almacenado con el comando git add:

git status -s
M  index.html       # index.html preparado
 M lib/simplegit.rb # simplegit.rb sin preparar
#
git stash --keep-index # guardado rapido de todo lo que no esta preparado
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file
#
git status -s
M  index.html       # index.html preparado

Esto puede ser util cuando ya has hecho un buen numero de cambios, pero solo quieres aplicar permanentemente algunos de ellos y luego regresar al resto de cambios en una siguiente ocasion.


Otra cosa comun que puede que quieras hacer con tus guardados es hacer un guardado rapido de los archivos que no estan bajo seguimiento (untracked) junto a los que estan bajo seguimiento (tracked). Por defecto, git stash solamente guardara archivos que ya esten en el area de preparacion. Si especificas --include-untracked o -u, Git tambien hara un guardado rapido de cualquier archivo que no este bajo seguimiento (untracked):

git status -s
M  index.html       # index.html preparado
 M lib/simplegit.rb # simplegit.rb sin preparar pero bajo seguimiento
?? new-file.txt     # nuevo archivo, no esta bajo seguimiento
#
git stash -u        # guardado rapido de absolutamente todo
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file
#
git status -s
# Todo quedo en el guardado rapido

Finalmente, si especificas la flag --patch, Git no hara guardado rapido instantaneamente, sino que te preguntara interactivamente cuales de los cambios guardar y cuales mantener en tu trabajo directamente:

git stash --patch   # Guardado rapido interactivo
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 66d332e..8bb5674 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -16,6 +16,10 @@ class SimpleGit
         return `#{git_cmd} 2>&1`.chomp
       end
     end
+
+    def show(treeish = 'master')
+      command("git show #{treeish}")
+    end
#
 end
 test
Stash this hunk [y,n,q,a,d,/,e,?]? y
#
Saved working directory and index state WIP on master: 1b65b17 added the index file

Creando una Rama desde un Guardado Rapido

Si almacenas algun trabajo, lo dejas ahi por un tiempo y continuas trabajando en la misma rama, puede que tengas problemas rehaciendo la entrada al trabajo: es posible tener conflictos de fusion y deberas resolverlos.


Si quieres una forma mas facil de probar los cambios guardados, puedes ejecutar git stash branch, el cual crea una nueva rama con el trabajo del guardado rapido para que puedas probarlo y luego realizar una fusion devuelta a la rama original:

git stash branch testchanges
Switched to a new branch 'testchanges'
# On branch testchanges
# Changes to be committed:
#   (use "git restore --staged..." to unstage)
#
#       modified:   index.html
#
# Changed but not updated:
#   (use "git add..." to update what will be committed)
#
#       modified:   lib/simplegit.rb
Dropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359)

Este es un buen metodo rapido para recuperar el trabajo guardado, trabajando en una nueva rama sin conflictos.


Limpiando tu Directorio de Trabajo

A veces no quieres guardar tu trabajo temporal en un stash, sino que simplemente deseas deshacerte de los archivos sobrantes en tu directorio de trabajo. El comando git clean sirve exactamente para eso.


Algunos escenarios comunes para usarlo son eliminar archivos residuales, o borrar archivos compilados y caches que se crean al ejecutar el proyecto.


Debes tener mucho cuidado con este comando, ya que esta diseñado para eliminar de forma permanente los archivos de tu directorio de trabajo que no estan bajo seguimiento por Git. Si cambias de opinion despues de usarlo, por lo general no hay forma de recuperar el contenido de esos archivos. Una opcion mucho mas segura es ejecutar git stash --all, lo cual limpia todo tu directorio pero respalda los archivos en un stash por si acaso.


Si estas seguro de que quieres eliminar la basura y limpiar tu directorio, puedes usar git clean. Para borrar todos los archivos y carpetas que no estan bajo seguimiento, debes ejecutar git clean -f -d. Aqui, el parametro -d incluye los subdirectorios y el parametro -f significa forzar (force), necesario porque Git bloquea este comando por defecto para evitar accidentes.


Si alguna vez quieres revisar que archivos se borrarian antes de ejecutar el comando real, puedes usar la opcion -n para hacer una simulacion:

git clean -d -n     # limpiar espacio de trabajo incluyendo carpetas en modo simulacion
Would remove test.o # test.o seria eliminado
Would remove tmp/   # carpeta tmp/ seria eliminada

Por defecto, el comando git clean solo removera archivos que no estan bajo seguimiento y que no estan ignorados. Cualquier archivo que empareje un patron de tu .gitignore u otros archivos ignorados no seran removidos. Si quieres eliminar esos archivos tambien, como para remover todos los .o generados por una version, para tener una version completamente limpia, puedes añadir un -x al comando:

git clean -n -d -x      # limpiar espacio de trabajo incluyendo carpetas y archivos ignorados en modo simulacion
Would remove build.TMP  # build.TMP seria eliminado
Would remove test.o     # test.o seria eliminado
Would remove tmp/       # carpeta tmp/ seria eliminada

Si no sabes lo que el comando git clean va a hacer, siempre ejecutalo con un -n primero para estar seguro antes de cambiar el -n (simluacion) al -f (forzar) y hacerlo de verdad. La otra forma en la que puedes ser cuidadoso con el proceso es ejecutarlo con el -i (interactivo):

git clean -x -i     # limpiar espacio de trabajo incluyendo archivos ignorados en modo interactivo
Would remove the following items:
  build.TMP  test.o
*** Commands ***
    1: clean    2: filter by pattern    3: select by numbers    4: ask each    5: quit
    6: help
What now>

De esta manera puedes decidir por cada archivo individualmente o especificar los patrones para la eliminacion de forma interactiva.


Recursos