Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:control_version_git_avanzado:commits

Commits

Sección perteneciente al curso Control de versiones con Git Avanzado.

Introducción

Una de las unidades más importantes. Veremos cómo preparar un commit. Un commit no es un backup sino un momento concreto en la historia de cambios de nuestro proyecto. Una de las características más potentes de Git es que nos permite preparar el commit, qué ficheros van a ir a un commit y cuáles no.

También veremos cómo ver commits del pasado y cómo deshacerlos.

Preparación de un commit por etapas

Trabajando con el stage

Tanto para añadir nuevos archivos al repositorio (empezar a versionar) como para añadir modificaciones en archivos ya existentes, tendremos que “subirlos al stage”:

git add <RUTA>

Para inicializar un repositorio, es decir, que Git pueda empezar a trabajar con él, ejecutamos dentro del directorio el comando git init. A partir de entonces ya podremos usar git.

Cuando queremos “subir al stage” un archivo:

git add archivo

Si queremos añadir más de un archivo:

git add archivo1 archivo2 archivo3

Si queremos añadir un tipo de ficheros:

git add *.html

Para subir todos los archivos y modificaciones pendientes de pasar al stage y que estén en la carpeta actual (y todas las carpetas hijas):

git add .

Para ver información sobre las 3 zonas que usamos en Git, usamos el siguiente comando:

''git status''

Podemos ver un resumen de los ficheros que están en el stage y los que no están siendo controlados (versionados) por git. Los primeros aparecen en color verde y los segundos en color rojo. Otra forma de decirlo sería que lo que está en verde es lo que está en el stage y lo que está en rojo es lo que está en el working directory.

Poner captura con ejemplo de lo anterior

git status es un comando muy útil y es recomendable lanzar cada vez que ejecutemos algún comando de git. También ofrece “pistas”.

Si queremos quitar modificaciones del stage (por algún error):

git reset HEAD <RUTA_FICHERO>

Por ejemplo, si queremos quitar el fichero fichero1:

git reset HEAD archivo1

También se puede emplear git restore --staged <RUTA_FICHERO>

Si queremos quitar todos los cambios que haya en el stage:

git reset HEAD .

En realidad, el comando git reset sirve para otra cosa que se verá más adelante.

Para deshacer cambios en el working directory, es decir, para que Git deje los archivos modificados como estaban en el último commit:

git checkout -- <RUTA_FICHERO>

Por ejemplo, si queremos volver al estado anterior de todos los archivos del working directory:

git checkout -- .

Esta operación es irreversible, no se puede deshacer.

Finalmente, si queremos borrar un archivo tenemos dos opciones. Si el borrado lo hacemos mediante las operaciones del sistema operativo, Git lo entiende como una modificación del proyecto y podremos subirlo al stage para luego hacer un commit con ese borrado.

Si usamos el método Git:

git rm <RUTA_FICHERO>

Git lo borra del sistema de ficheros y añada esa modificación al stage, ahorrándonos un paso respecto a la versión usando el sistema operativo.

Si queremos quitar un archivo del repositorio sin borrarlo del working directory:

git rm --cached <RUTA_FICHERO>

Esto es útil cuando llegado un momento queremos empezar a ignorar un fichero que antes estábamos siguiendo (el .gitignore aquí no serviría).

Tras “desversionar” lo que queremos, ahora sí podremos modificar el .gitignore para añadir ese fichero o ficheros que no queremos que Git versione.

Realizando el commit

Un commit siempre necesita un mensaje descriptivo (es obligatorio):

git commit -m "Mensaje"

Si queremos que abra el editor por defecto para que luego añadamos el comentario:

git commit

El commit siempre se hace sobre lo que hay en el stage

Como recomendación, en los mensajes de commit habría que evitar genéricos como “Cambios”, “Nuevo archivo”, “Correcciones”, “CSS”, “Merge”… A la hora de escribir un mensaje, tenemos que explicar bien qué modificaciones sufre el proyecto y de forma muy breve. El propósito es que cuando se quiera ver el log, se pueda entender perfectamente los cambios que ha sufri oel proyecto:

git log --oneline --branches --graph

Si queremos modificar el último commit (solo el último) que hemos hecho:

git commit --amend

Esto lo haremos tras añadir cambios al stage.

Al ejecutar el comando anterior, se abrirá el mensaje del último commit (el que queremos modificar) dándonos la posibilidad de modificarlo, si queremos.

Si el commit ya se había subido al repositorio remoto, no se debe hacer --amend. Las correcciones en los commits hay que hacerlas en el repositorio local.

Otra cosa que suele pasar mucho es que hayamos hecho un commit con un mensaje incorrecto o incompleto. Si queremos corregir este mensaje, pero no tenemos más cambios, no hace falta subir nada al stage, basta con ejecutar el siguiente comando:

git commit --amend -m "Nuevo mensaje"

Si no le pasamos el mensaje por línea de comandos, git abrirá el editor por defecto.

Esta modificación cambia el hash del commit. Esto es normal porque este hash se genera a partir del contenido del título y otras cosas.

Ejemplos de commits por etapas

Añadir capturas de pantalla con las zonas de git y cómo se mueven los archivos cuando se preparan los commits.

Iniciamos un repositorio git:

git init
git status

Subiremos al stage el fichero despedidas-en.json:

git add despedidas-en.json
git status

Hacemos commit:

git commit -m "Commit inicial"

Es práctica habitual incluir el mensaje “Commit inicial” en el primer commit de un proyecto

git log --oneline --branches
git status

Subimos el fichero saludos-en.json al stage:

git add saludos-en.json

Hacemos el commit:

git commit -m "Añadidas traducciones de saludos"
git log --oneline --branches

Modificamos el fichero saludos-en.json. Ahora git reconocerá que hay un cambio en el working directory:

git status

Subimos el fichero al stage:

git add saludos-en.json

Hacemos el commit:

git commit -m "Añadida despedida"

Vemos el log:

git log --oneline --branches

Creamos un nuevo fichero en el working directory: lugares-en.json. También hacemos modificaciones en el fichero saludos-en.json. Git nos avisará de estos cambios.

git status

Añadiremos al stage solo dos ficheros:

git add lugares-en.json despedidas-en.json

Hacemos commit:

git commit -m "Añadido fichero de lugares y añadido saludo"

Vemos el log:

git log --oneline --branches

Subimos al stage saludos-en.json modificado.

git add saludos-en.json

Pero nos arrepentimos, y lo quitamos del stage:

git reset HEAD saludos-en.json

Git nos dirá que aún está pendiente el cambio de ese fichero en el working directory:

git status

Pero si queremos volver a la versión del último commit del fichero saludos-en.json:

git checkout -- .

Recordad que este último comando es irreversible

Partes de un commit

Hasta ahora hemos visto qué es un commit. Ahora veremos los datos que llevan asociados los commits:

  • Autor / “committer”: nombre y correo electrónico de quien realiza el commit.
  • Fecha y hora
  • Mensaje
  • Padre/s: un commit puede tener un padre o 2 (es raro que haya más).
  • Hash de 40 caracteres: se obtieen al aplicar SHA-1 al autor, fecha, mensaje y contenido del commit.
git log

Poner ejemplo de todo lo anterior

El log

El log es el listado de commits a lo largo del tiempo.

Comandos para trabajar con el log

Ver el histórico de commits:

git log [rama]

Si no se indica la rama, git log nos mostraría el nombre de la rama en la que nos encontremos.

Para ver el log de forma compacta:

git log --oneline

Así veremos un commit por línea. No mostrará autor ni fecha.

Ver el log de todas las ramas:

git log --branches

Ahora podemos mezclar los modificadores para ver el log en formato compacto y de todas las ramas:

git log --oneline --branches

Al trabajar con ramas, es útil tener una representación gráfica de las divergencias, cómo se ha bifurcado el código:

git log --graph --branches

Podemos crear alias para comandos largos que usemos habitualmente:

git config --global alias.milog "log --oneline --branches --graph"

Habremos creado un alias llamado milog, y para usarlo:

git milog

Para ver el contenido de un commit:

git show <HASH_COMMIT>

Poner ejemplo del comando anterior

Con el comando anterior podremos ver las diferencias con el commit anterior. El formato que es usa es el de los parches (patch)

Podemos referenciar un commit de varias maneras:

  • Por su identificador hash SHA-1 (lo que vimos anteriormente)
  • Su posición respecto al HEAD
  • Por el nombre de una rama
  • Por su posición en el listado de reflog: HEAD@{n}
git show HEAD

Commit anterior al commit apuntado por HEAD:

git show HEAD~1

Es lo mismo que

git show HEAD~

Dos commits antes:

git show HEAD~2

Es lo mismo que

git show HEAD~~

Esto es muy útil cuando queremos deshacer commits.

Si un commit tiene más de un padre, usamos la notación siguiente:

git show HEAD^1

Haremos referencia al primer padre.

git show HEAD^2

Haremos referencia al segundo padre.

Referenciando un commit por la rama que apunta:

git show nombre_rama

git reflog nos enseña una secuencia de movimientos de la referencia HEAD. Esto es útil si hemos “perdido” un commit.

git checkout HEAD@{1}

Nos habremos ido al commit al que apuntaba HEAD en cierto momento.

Poner gráfico con detached HEAD

Saltos entre commits, reset y reflog

Saltar a otro commit o rama:

git checkout <COMMIT o RAMA>

Para el nombre el commit podemos usar las referencias que vimos en el apartado anterior.

El checkout hará dos cosas: moverá el HEAD de donde esté al que estamos indicando ; la segunda cosa es que va a coger el working directory tal cual está y lo va a transformar al estado en el que estaba en el commit al que nos movemos.

Para “borrar” commits:

git reset <COMMIT>

Realmente no se borran, sencillamente desaparecen de nuestra vista. Esto permite que podamos recuperarlos en cualquier momento (si no ha pasado demasiado tiempo y ha pasado el recolector de basura de Git).

Por ejemplo, si quremos deshacer los últimos 3 commits:

git reset HEAD~3

El comando reset tiene 3 modificadores:

  • --soft: los cambios hechos en los commits que borramos, git los coloca en el stage. Este método se suele usar para comprimir commits.
  • --mixed: los cambios hechos en los commits que borramos, git los coloca en el working directorio. Es la opción por defecto.
  • --hard: los cambios hechos en los commits que borramos, desaparecen (de la vista).

Cuando desarrollamos en un equipo y queremos ver quién fue la última persona que hizo cada cambio:

git blame <RUTA>

Es útil para seguir una traza y poder encontrar quién hizo qué cosa.

Apartar cambios con stash

stash sirve para apartar cambios (en el working directory o en el stage) temporalmente.

Esto es muy útil cuando Git no nos deja cambiarnos de rama o commit (checkout) porque esos cambios machacarían nuestros ficheros.

stash es una pila, lo que entra queda en la parte superior.

git stash siempre va acompañado de un comando.

Para coger unos cambios y meterlos en la pila de “stashes”:

git stash [push -m "Mensaje"]

Listar todo lo que hay en el stash:

git stash list

Poner ejemplos del comando anterior

Si queremos ver el contenido de cierto stash:

git stash show [stash@{n}] [-p]

Para sacar los cambios del stash y colocarlos en el working directory (git stash siempre los colocará ahí):

git stash apply [stash@{n}]

El comando anterior no quita el stash de la pila. Si además queremos quitarlo de la pila, usaremos este otro comando:

git stash pop [stash@{n}]

Si queremos borrar de la pila un stash sin aplicarlo:

git stash drop [stash@{n}]

Cómo deshacer

Algo muy importante de Git es que casi todo se puede deshacer. Todo lo que vaya a un commit quedará para la historia (hasta que llegue el recolector de basura).

Repasemos varias maneras de deshacer cambios.

Cambios en el working directory

Deshacer cambios en el working directory (todos los cambios):

git checkout -- .

Recordemos que el anterior comando es de las pocas cosas en git que no se pueden deshacer.

Cambios en el stage

Deshacer lo que haya en el stage:

git reset HEAD .

Ahora sabemos que el uso principal de git reset es para volver a commits anteriores. Sin embargo, lo usamos también para quitar cambios del stage porque git reset HEAD . vuelve al commit actual (el mismo), pero como por defecto se aplica el modificador --mixed, esto hace que quite los cambios del stage y los coloque en el working directory.

Deshacer commits

git reset <COMMIT>

Deshacer un commit --amend:

git reflog

Así vemos los commits ocultos porque vemos los movimientos de HEAD. Cuando lo hayamos identificado, para recuperarlo:

git checkout <COMMIT>

Esto nos pondrá en el modo “detached HEAD”. Falta que la rama apunte también al mismo commit que HEAD. Para mover la referencia de una rama:

git branch -f master

Suponiendo que la rama master sea rama que nos interesa mover. De esta forma ya tendríamos tanto HEAD como master apuntando al commit que recuperamos. El último paso es hacer un checkout para que HEAD esté apuntando a master:

git checkout master

Podemos pensar que para recuperar un commit “oculto” lo que hacemos es meterlo en un itinerario existente, es decir, en una rama.

Deshacer un reset

Tras hacer un reset, los commits desaparecen de nuestra vista, pero podemos recuperarlos. Los podemos ver con:

git reflog

E identificamos el último commits de los que queremos recueprar.

Ahora nos llevaremos HEAD y master del momento actual a ese último commit que queremos recuperar:

git checkout <COMMIT>

Ahora llevaremos la rama:

git branch -f master

Para enlazar HEAD con master (y salir de “detached HEAD”):

git checkout master

Poner ejemplos de todo lo anterior

Resolver problemas derivados del detached HEAD

Cuando por ejemplo estamos haciendo commits en estado “detached HEAD”, no avanza la rama. Las ramas avanzan solo si HEAD la hace avanzar.

git log --oneline --graph

No ponemos --branches para poder ver HEAD.

Para solucionarlo, tenemos que mover la rama a donde esté HEAD:

git branch -f master

Nos quedaría la asociación de HEAD con master, es decir pasar de (HEAD, master) a (HEAD → master):

git checkout master

Poner ejemplos de todo lo anterior

Otra situación que se nos puede dar es que hagamos un commit en un estado anterior al commit en el que está la rama master.

Eso origina un nuevo itinerario, pero es un itinerario que está fuera de la rama master (no tiene ninguna referencia de rama asociada). Si nos cambiamos de rama, el itinerario anterior quedará oculto a la vista.

Para recuperar, nos ayudamos de:

git reflog

Identificamos el commit que nos interesa y

git checkout commit

Hemos vuelto al “itinerario sin nombre”. Para resolver esto, crearemos una referencia de rama:

git branch experimento

Ahora ya aparece el itinerario en los logs.

Seguimos en “detached HEAD”, así que para que se enlacen HEAD con experimento:

git checkout experimento

Ignorar archivos que ya están siendo versionados

Borrar del repositorio, pero no del working directory:

git rm --cached <RUTA_FICHERO>

El archivo dejará de estar versionado. Luego podremos añadirlo a un .gitignore para que de ahí en el futuro git deje de tenerlo en cuenta.

Conclusión

Hemos visto cómo se preparan los commits: en el stage. Al hacer un commit, “hacemos una foto” al stage.

Los commits no se hacen periódicamente, solo cuando es necesario que vaya a haber cambios en el log.

git reflog es muy útil para cuando queramos deshacer cambios o investigar los cambios que ha habido en nuestro repositorio.

Recursos

informatica/programacion/cursos/control_version_git_avanzado/commits.txt · Última modificación: por tempwin