Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:git_control_codigo_fuente_programadores:git_basico_linea_comandos

Git básico - Línea de comandos

Módulo perteneciente al curso Git: Control de código fuente para programadores

Hasta ahora hemos visto los fundamentos de los sistemas de control de versiones, sus tipos, pros y contras, así como la terminología básica de Git. Además hemos aprendido a instalar Git.

En este módulo aprenderemos el flujo básico de trabajo con Git, que es el mínimo que se necesita para sacar partido a este VCS y en el que se basa cualquier otro flujo de trabajo de los que veremos más adelante.

Aprenderemos primero a manejar Git contra un repositorio local y con la línea de comandos. Aunque en el día a día luego lo utilices con algún cliente visual, es muy importante conocer también las instrucciones de línea de comandos pues muchas veces nos ayudarán a salir de problemas.

Puede parecer supérfluo una vez lo veas y empieces a usar herramientas visuales, pero estudiarlo ahora evitará que te pase como a los de esta famosísima tira cómica de los geniales XKCD.

Para ello, de momento, aprenderemos a crear un repositorio local y trabajar con éste para confirmar cambios y enviarlos.

Un repositorio en Git es un espacio en el que se almacena toda la información sobre tu código y su histórico. En el repositorio (muy comúnmente llamado simplemente “repo”, quédate con esta palabra) se guardan datos precisos sobre los cambios que se han realizado en cada archivo controlado: cuáles han sido, quién los ha realizado, cuándo, etc… Es en donde reside la esencia del control de código.

Existen esencialmente dos tipos de repositorios: locales y remotos. Tanto los repos locales como los remotos, una vez sincronizados, guardan exactamente la misma información en el caso de Git, que como hemos visto es un VCS de tipo distribuido.

El modo básico de trabajar es el mismo siempre y se ilustra en la siguiente figura:

Este proceso consiste básicamente en:

  • Crear o clonar un repositorio, para tener un área de trabajo donde gestionar cambios. Cuando clonas un repo remoto te traes una copia exacta a tu sistema y es con la que trabajas en local.
  • Añadir archivos y hacer cambios en ellos.
  • Agregar los archivos al área de preparación, como ya hemos visto en la introducción, listos para confirmar los cambios; algo que haremos cuando estemos seguros de que queremos guardar una instantánea de cómo están los archivos.
  • Confirmarlos, es el proceso que genera una “instantánea” (commit) de nuestro código. Es el punto en el tiempo que queremos guardar.
  • Obtener posibles cambios del repositorio o repositorios remotos (puede haber más de uno en realidad). Para verificar primero (fetch) si otras personas han añadido otras instantáneas con cambios en los archivos, o bien, añadido o quitado archivos y obtener esos cambios (instantáneas o commits) del histórico (pull).
  • Enviar nuestros cambios al repositorio o repositorios remotos, para que otros programadores puedan obtener nuestros cambios (push).

Esto es lo que vamos a estudiar en este módulo, aunque de momento sólo con repositorios locales. Empezaremos con un repaso general teórico de todo lo que conlleva para luego verlo en la práctica con un ejemplo sencillo. Al principio es posible que te cueste un poco entender bien todo lo que está ocurriendo, pero lo irás viendo mucho más claro a medida que avancemos en el curso y hagas uso de Git.

De momento todo lo que vamos a estudiar lo realizaremos directamente en la línea de comandos. Aunque luego uses un cliente visual, es muy importante conocer los comandos básicos de Git, pues te ayudarán a entender mejor cómo funciona y a salir de problemas. Más adelante en el curso veremos los clientes visuales más populares y cómo utilizarlos, pero es necesario que antes conozcas bien el funcionamiento “manual” desde la terminal.

Creación de un repositorio

Lo primero que debes aprender para usar Git es a crear un repositorio, ya que sin uno no podrás ejecutar el resto de comandos para trabajar.

Para crear un repositorio de Git tendrás que crear una carpeta en el sistema de archivos, y una vez dentro de ella ejecutar la siguiente instrucción desde la línea de comandos de tu sistema operativo (sea Windows, Linux o Mac):

git init

Nota: asegúrate de ejecutar este comando dentro de una carpeta sobre la que tengas derechos de acceso y escritura. Puede parecer una tontería pero ya te acordarás de este aviso cuando te pase ;-)

Una vez ejecutemos este comando, Git creará una carpeta llamada .git en la que se encontrará toda la configuración del repositorio.

Dentro de dicha carpeta se crearán diferentes elementos entre los que caben destacar dos archivos: config y description que guardan la principal configuración modificable por el usuario. Los estudiaremos en futuras lecciones.

Recuerda que esta carpeta está oculta, por lo que es probable que no la veas. Y por una buena razón: por regla general no deberíamos tocar directamente dentro de ella nunca. Pero es importante que sepas que existe.

Clonación de un repositorio

Ya has aprendido a crear un repositorio desde cero, pero la verdad es que lo que harás normalmente será clonar otros repositorios (propios o de otras personas o empresas). Un repo local no vale de mucho si no tiene un respaldo en algún lugar remoto o no hacemos copias de seguridad. Tampoco vale para colaborar con otros desarrolladores. Por ello, lo más habitual es trabajar contra un repositorio remoto en el que se almacena la información del repositorio de manera centralizada y nos aseguramos de que hay copias de seguridad y podremos colaborar si es necesario.

Para clonar un repositorio remoto existe el comando git-clone que podremos configurar dependiendo de si queremos clonar un repositorio local o un repositorio que esté en la nube. Veamos los fundamentos de dicho comando.

Para clonar un repositorio local:

git clone /ruta/al/repositorio/local

Los repositorios remotos normalmente son accesibles a través de una conexión web segura (HTTPS) o a través del protocolo SSH.

Para clonar un repositorio remoto se utiliza el comando de la misma manera, pero cambiando la ruta local por un URL remoto:

git clone https://servidor-git.com/empresa/proyecto.git

Esto creará automáticamente, dentro de la carpeta en la que ejecutes el comando, una subcarpeta con el nombre del repositorio (por ejemplo ./proyecto), y copiará dentro de ésta todo el repositorio, lo cual incluye no solo la carpeta .git, sino todos los archivos y carpetas del proyecto.

Nota: una vez más, no te olvides de repasar los derechos que tengas sobre la carpeta en la que ejecutes el comando git-clone. Recuerda que todos los comandos de Git que mencionemos se deben ejecutar sobre la carpeta raíz de nuestro repositorio local, en el que reside toda la información del repositorio.

Si quieres especificar un nombre concreto para la carpeta puedes usar un parámetro adicional tras la dirección del repo remoto:

git clone https://servidor-git.com/empresa/proyecto.git MiProyecto

que creará el repositorio local dentro de la carpeta ./MiProyecto.

Existen otras muchas opciones para el comando git-clone, pero no vamos a entrar en ellas de momento y generalmente te llegará con estas. Conclusión

Ya sabemos cómo inicializar nuestro entorno de trabajo, pero ahora nos falta empezar a trabajar con él de verdad. En la próxima lección veremos el primer flujo de trabajo que utilizaremos con Git. Será un flujo básico que ya se introdujo en el anterior módulo, pero no por ello menos importante ya que a partir de él nacerán el resto de flujos.

Esto de momento te puede resultar algo árido porque no lo hemos practicado. Pero, aguanta un poco conmigo que enseguida lo veremos en la práctica y te quedará claro.

Flujo de trabajo - Registra tus cambios

En la anterior lección hemos aprendido a crear un repositorio con el que poder empezar a trabajar. Ahora nos toca trabajar en él, para lo cual usaremos el siguiente flujo que ya hemos visto anteriormente:

  1. En primer lugar realizas “x” modificaciones en tu directorio de trabajo.
  2. A continuación añades los archivos al área de preparación.
  3. Almacenas los archivos que están en el área de preparación creando una instantánea en tu repositorio de Git haciendo un commit.

Vayamos paso por paso.

Gestionar las modificaciones

A medida que hemos ido trabajando en el código se han hecho modificaciones en los archivos gestionados en el repositorio. La pregunta es: ¿cuáles son estos cambios?. Por defecto no hay una forma visual de saberlo en Git, pero podemos utilizar el siguiente comando:

git status

Después de ejecutar git-status Git nos presentará algo como lo siguiente:

Como puedes ver en la sección de Untracked files aparece el archivo documento_nuevo.txt, el cual aún no se ha añadido a la zona de preparación.

Nota: en el caso de haber también modificaciones de archivos de los cuales ya había histórico indicará Changes not staged for commit.

Algunas opciones interesantes para usar con este comando son:

Opción Descripción
-s Muestra un estado simplificado
-vv Aparte de mostrar los archivos, muestra las modificaciones que tienen. Es un sustituto del comando git diff --cached, que haría lo mismo
-uno Muestra sólo los archivos que están en la zona de preparación

Preparar los cambios para registrar (git-add)

Para añadir un documento a la zona de preparación (staging), previo a poder confirmar los cambios y crear una versión, debemos usar el comando git-add:

git add nombre_archivo.ext

Si tuviésemos varios nuevos archivos y quisiésemos incorporar sólo algunos de ellos, podemos usar un comodín para lograrlo:

git add /www/*.html

Que añadiría todos los archivos con extensión .html dentro de la carpeta www y sus subcarpetas.

Podemos incorporar todos los archivos que se hayan añadido, podemos usar directamente:

git add *

Lo que nos dejaría con la siguiente situación en nuestro pequeño repositorio, que podemos comprobar con git status:

Fíjate que ahora ha cambiado el texto y pone “Changes to be commited”, ya que este cambio está preparado para ser registrado, lo tenemos en lo que denominamos zona de preparación. Lo que nos quedaría ahora es simplemente hacer un commit.

Una forma alternativa de obtener un listado de todos los archivos que están en el área de staging es mediante el comando git diff --name-only --cached, por si lo necesitas para un script o similar.

Las diferentes variantes de git-add y una advertencia

Una forma muy habitual de agregar al área de preparación todos los cambios pendientes es utilizar un punto en lugar del asterisco:

git add .

Aparentemente hacen lo mismo. Sin embargo, hay una importante diferencia; el punto lo interpreta Git y no el sistema operativo (como pasa con el asterisco) y tiene un significado en particular: añadir todos los archivos en la carpeta actual y sus subcarpetas, incluyendo además aquellos archivos cuyo nombre empiece por un punto (que son muchos, algunos propios de Git).

Además, existe una diferencia importante al usar el asterisco en Windows y en Linux/macOS con el comando add. En Windows, como hemos visto, se añaden los archivos que cumplan con el criterio indicado en la carpeta actual y en todas las subcarpetas. En sistemas basados en UNIX, sin embargo, el asterisco sólo se expande sobre la carpeta actual, por lo que los comandos anteriores con asterisco no incluirían archivos contenidos en subcarpetas de la actual. Esto es así porque el asterisco no es un comando de Git sino algo perteneciente al shell subyacente (cmd o PowerShell en Windows, y bash o similar en UNIX). Para lograrlo en Linux/macOS basta con poner una barra inclinada delante del asterisco, por ejemplo así: git add www/\*.html y se añadirán todos los archivos HTML que estén bajo la carpeta www y sus subcarpetas.

Por ello suele ser más habitual y más recomendable usar el punto a la hora de añadir los cambios al área de preparación.

No obstante, git add . no tiene en cuenta los archivos eliminados. Es decir, que si teníamos un archivo bajo control de código (con una o más versiones ya almacenadas) y lo eliminamos, este comando no hará nada con él y esa eliminación no se verá reflejada en el repositorio. Hay que tenerlo en cuenta.

Para añadir todos los archivos, incluso los eliminados, al área de staging y poder confirmar los cambios el comando más recomendable es:

git add -A

que tendrá en cuenta todos los cambios.

Si queremos hacer caso omiso de los archivos nuevos pero pasar a preparación todos los demás, podemos utilizar:

git add -u

Este cuadro resume bien estas variantes y lo que hacen:

Comando Nuevos modificados Eliminados
git add .
git add -u
git add -A

Deshaciendo un add

Antes de hacer un commit y confirmar los cambios, vamos a ponernos en una situación bastante común: queremos deshacer un cambio que ya hemos mandado al área de confirmación. Esto por supuesto debe hacerse antes de crear la instantánea (o sea, antes de hacer el commit) ya que una vez que registras un cambio no hay marcha atrás (bueno, siempre hay formas, pero eso ya lo veremos ;-)). Veamos cómo se hace:

Podemos simplemente quitar el archivo de la zona de preparación y volver a la situación que teníamos antes de añadirlo, para lo cual tenemos dos opciones:

git rm --cached nombre_archivo

pudiendo usar un asterisco para recuperarlos todos. O bien:

git reset HEAD nombre_archivo

pudiendo usar también el asterisco para recuperarlos todos.

Lo anterior no nos elimina los cambios. Simplemente quita los archivos de la zona de preparación. Pero podemos, no sólo retirar de la zona de staging los archivos, sino también deshacer completamente los cambios sufridos por un archivo de modo que quede como si nunca hubiera pasado nada, utilizando:

git checkout nombre_archivo

Esto te deja el archivo tal y como lo tenías originalmente, antes de hacer cambio alguno sobre él, es decir, como estaba en el último commit.

Por supuesto puedes usar el asterisco, git checkout * para dejar todo como estaba y no solo un archivo o archivos determinados basados en su nombre.

Registramos los cambios

Vale, con todo lo anterior, pero fundamentalmente con el comando add, ya hemos decidido que hemos terminado de realizar los cambios que queremos registrar en este momento, los tenemos en el área de preparación y deseamos terminar el proceso y confirmarlos. Queremos hacer el famoso commit (¡por fin!). Para ello necesitamos ejecutar el siguiente comando en la carpeta de nuestro repo:

git commit -m "Mensaje del commit"

Con esto registraremos todas las modificaciones actuales de nuestra zona de preparación en el repositorio de forma local, especificando un mensaje que indique de qué se tratan dichos cambios.

Nota: ten en cuenta que Git es un VCS distribuido. Por eso los commit se almacenan en nuestro repositorio local, pero no se almacenan en los repositorios remotos, a disposición de los otros programadores, hasta que se lo indiquemos. Esto nos permite trabajar sin conexión, pero también almacenar ciertos commit solo localmente o en otro remoto diferente mientras no estemos listos para enviarlos al remoto principal. Al estudiar los remotos ya nos meteremos en este proceso. De momento trabajaremos sólo en local para comprender bien todos los conceptos básicos.

Algunas de las otras opciones interesantes que tiene este comando commit son:

Opción Descripción
--author=<name> Para cambiar el nombre del autor y poner uno distinto al nuestro por defecto al hacer el commit
--date Sobrescribe la fecha de creación
-a Esta opción añade todos los archivos de tu zona de trabajo al commit sin usar el comando git-add

Esta última opción es interesante ya que si quieres directamente hacer un commit de todos los cambios de tu directorio de trabajo actual te puedes ahorrar el paso de git-add escribiendo simplemente:

git commit -a -m "Mensaje descriptivo de los cambios"

Trabajar con el histórico de Git

En las lecciones anteriores hemos mostrado los comandos mínimos y necesarios para empezar a trabajar con Git de forma básica. Lo último imprescindible que requiere un trabajo diario con Git es, por supuesto, consultar el histórico de cambios. De poco nos serviría tener un control de versiones si no podemos realizar consultas de los cambios que se han hecho.

Visualización del histórico de cambios

Para ver un histórico con todos los commits realizados en el repositorio tenemos el comando git-log:

git log --graph --oneline --decorate --all

Lo que nos produce algo como la siguiente imagen:

En la que vemos que hay dos commits hechos y nos aparece alguna información sobre ellos, como por ejemplo su identificador único (hash) y los mensajes que hemos introducido al crearlos.

Importante: Git identifica cada commit, que es un conjunto de cambios, con un identificador único en forma de hash. Este identificador es la manera que tenemos para poder referirnos a ellos en caso de necesitarlo. Por ejemplo, si queremos volver a un punto anterior del histórico o comparar entre sí dos commits, nos referiremos a ellos generalmente a partir de su hash.

El hash es mucho más largo de lo que se ve en la figura anterior, pero por regla general llega con utilizar los 3 o 4 primeros caracteres para identificarlo, ya que las probabilidades de colisión son muy pequeñas. Es por eso que git-log muestra tan solo los 7 primeros caracteres de este identificador.

Ese comando es el que más uso personalmente y es uno de los comandos más útiles y más complejos que tiene Git. Algunas opciones que se pueden añadir a git log son:

Opción Descripción
--author=<nombre> Muestra sólo los commits de dicho autor
--oneline Comprime cada commit en una sola línea mostrando el hash y el mensaje del commit
--graph Dibuja el histórico como si fuera un gráfico
--decorate Muestra las referencias a los diferentes branches, tags, punteros y otros elementos de Git en el histórico
--all Hace que se muestren todas las referencias del repositorio
-nX Muestra los últimos X commits
--skip=X Se salta los últimos X commits

El comando git-log nos proporciona, de forma muy visual (aunque basada en texto), el estado de un repositorio de forma general.

Si tienes muchos commits en el histórico y usas git-log acabarás con un listado inicial que va mostrando nuevos commits cada vez que pulsas ENTER para poder ir viéndolos poco a poco. Para poder salir de este listado y terminar de verlos debes pulsar la tecla Q. Es la única manera de salir y si no lo sabes te puede desesperar.

Ver el detalle de un commit en concreto

Es muy común que, como gestores de un equipo de desarrolladores, queramos revisar los cambios que introduce cada commit. Para ello utilizamos git-show:

git show <hash_commit>

De esta forma podremos ver qué archivos han cambiado y qué cambios han sufrido. Adicionalmente, al igual que con el comando log, tenemos varias opciones para modificar la presentación:

Opción Descripción
--pretty=oneline Muestra el hash y el mensaje del commit en una sola línea
--pretty=fuller Muestra toda la información de un commit

Alias de comandos

Cuando trabajas a menudo con algunos comandos de Git complejos o con muchas opciones, una manera de ahorrar tiempo y equivocaciones es crear alias para los mismos. Estos alias son comandos “nuevos” que puedes añadir a tu instalación de Git de modo que puedas usarlos como un comando más pero de manera abreviada.

Un candidato habitual para usar alias es precisamente el comando git-log, ya que como ves, tiene bastantes parámetros que solemos usar.

Así, por ejemplo, podemos crear un nuevo alias llamado lg que nos sirva como sustituto de, por ejemplo, log --oneline --decorate --all --graph. Para ello usamos el comando de configuración de Git asi:

git config --global alias.lg "log --oneline --decorate --all --graph"

Fíjate en que el nombre del nuevo alias se pone a continuación del punto en alias. y el comando que quieres sustituir va entre comillas dobles y sin git delante. El modificador –global implica que creas el comando para que funcione en cualquier repositorio, no solo en el actual.

A partir de ahora si quieres sacar este listado sólo tienes que escribir:

git lg

y obtendrás el mismo resultado. ¡Mucho más rápido y fácil de usar!

Nota: en este caso le hemos puesto el nombre lg porque no podemos usar un nombre que ya exista, como log, y además es más corto, pero le puedes dar el nombre que prefieras, más descriptivo. Por ejemplo, como este comando saca también un pequeño gráfico con las ramas que haya, podrías llamarle lgg (de Log gráfico).

Conclusión

Con todos estos comandos ya conocemos lo básico para empezar a trabajar en un proyecto sencillo con Git. En próximos módulos iremos introduciendo flujos de trabajo más complejos gracias a conceptos como las ramas, que nos permitirán una tremenda flexibilidad a la hora, tanto de diseñar un repositorio, como de trabajar con él.

Vamos a ver un ejemplo práctico de uso de todos estos comandos que hemos visto.

Descartando archivos para el control de cambios: .gitignore

Aunque siempre querremos gestionar con Git todos nuestros archivos de código, existen muchos otros archivos que no nos interesará que aparezcan en el repositorio. Es más, habrá archivos que sería un grave error que estuviesen allí.

Por ejemplo, los archivos de preferencias de usuario para el proyecto que generan los diferentes editores de código son personales e intransferibles, y nunca deberían pasar a Git o sobrescribiríamos las preferencias de los demás usuarios con los que colaborásemos. Así, deberíamos excluir los archivos .suo que genera Visual Studio, la carpeta .vscode en el caso de Visual Stdio Code (aunque no siempre) o .idea/workspace.xml en el caso de IntelliJ, por poner ejemplos representativos.

Otros recursos típicos a excluir son:

  • Ejecutables y DLLs resultantes de compilar proyectos, así como archivos .class de Java o .pyc de Python.
  • Las carpetas obj, out, target o similares, con los objetos intermedios usados para la compilación
  • Carpetas con archivos auxiliares que se generan localmente por el programador, como por ejemplo la carpeta node_modules en proyectos Front-End o Node.js o la carpeta packages para paquetes NuGet en la plataforma .NET.
  • Archivos ocultos o temporales del sistema, como los típicos (y horribles) Thumbs.db de Windows para vista previa de iconos, o los horripilantes .DS_Store con los atributos personales de la carpeta.
  • Archivos de log o similares, generados en tiempo de ejecución.

Y, por supuesto, dentro del grupo de “sería una tragedia si fueran a parar a Git” están todos aquellos archivos con información privada o secreta, como claves de bases de datos o de cifrado, secretos para llamar a APIs y cosas así.

Una forma de lograrlo es, claro está, no incluirlos nunca dentro del área de staging para que no puedan formar parte del commit. Pero esto implica acordarse de cuáles no deben ir ahí para no meterlos, o quitarlos si se han incluido con git add. Obviamente esto no tiene ningún sentido pues sería demasiado laborioso y propenso a errores.

Por suerte, Git nos ofrece la posibilidad de hacer esto automáticamente de manera muy sencilla.

Lo único que tenemos que hacer es crear en la carpeta raíz de nuestro proyecto un archivo llamado .gitignore (sí, así, empezando por un punto, todo extensión).

Se trata de un simple archivo de texto en el que podemos incluir tantas líneas como necesitemos con nombres de archivos y carpetas o bien patrones de estos nombres. Todas las coincidencias que haya se excluirán automáticamente del control de código fuente.

Este es un ejemplo de un archivo .gitignore sencillo:

# Archivos de vista previa de imágenes
Thumbs.db

# Configuración personal de la carpeta
[Dd]esktop.ini

# Ejecutables y DLLs
*.exe
*.dll

# Archivos de log de nuestra aplicación
logs/**/*.log
!compilation.log

De este contenido es fácil extraer algunas conclusiones:

  • Se pueden poner comentarios para saber por qué hemos incluido una determinada línea, para lo cual sólo hace falta ponerle una almohadilla (#) al principio.
  • Si pones simplemente el nombre de un archivo, este se excluirá del control de código. Distingue entre mayúsculas y minúsculas.
  • Es posible especificar conjuntos de letras, números o incluso rangos usando corchetes. Por ejemplo, en nuestro archivo, la expresión [Dd] sirve para indicar que ahí puede ir un “D” mayúscula o minúscula. Pero puedes indicar rangos separándolos con un guión: [a-zA-Z] o [0-9] como en las expresiones regulares (¡pero no lo son!).
  • Los asteriscos son expresiones de tipo Glob de Linux, aunque estés en Windows. En el enlace tienes una explicación detallada, pero es muy simple: los asteriscos * sustituyen a cualquier cosa, las interrogaciones ? a un carácter cualquiera y los dobles asteriscos con barra **/ a cualquier subcarpeta. Así, en nuestro ejemplo, logs/**/*.log quiere decir que se excluirá cualquier archivo con extensión .log que esté en cualquier subcarpeta de cualquier nivel de la carpeta logs que tenemos en la raíz. Sencillo, pero potente.
  • Podemos crear excepciones precediéndolas con un cierre de admiración: !. Esto quiere decir que en el caso anterior, si uno de los archivos de log se llama exactamente compilation.log sí que se incluirá en el control de código. Es muy útil para algunos casos.

Debes tener en cuenta que el orden de las líneas es importante, ya que si dos reglas para archivos se contradicen, la última gana. Así, en nuestro ejemplo, si movemos la línea con !compilation.log delante de la anterior en vez de a continuación, no tendría efecto. Por lo que, cuidado con esto.

La buena noticia es que, por regla general, no vas a tener que construir tus propios archivos .gitignore. Como mucho retocarlos para meter alguna cuestión inicial. Existen herramientas y colecciones de este tipo de archivos en las que podrás encontrar la que más te convenga:

  • Este repositorio de GitHub tiene infinidad de archivos .gitignore ya preparados y listos para usar con casi cualquier entorno de desarrollo o lenguaje que se te ocurra.
  • gitignore.io te permite buscar por palabras clave de sistema operativo, lenguaje, entorno… y obtener un archivo .gitignore apropiado.
  • Existen extensiones para los editores y entornos que te ayudan a generarlas. Por ejemplo, para Visual Studio Code tienes gitignore para añadir ayuda contextual mientras escribes este tipo de archivos, y Gitignore Generator te los genera automáticamente incluso mezclándolo con el que ya tengas para evitar conflictos.

Es posible tener más de un archivo .gitignore gobernando las diferentes subcarpetas, aunque es una mala idea salvo en casos excepcionales. Mejor usa solamente un archivo en la raíz y tendrás menos posibles problemas de conflictos y que se te queden archivos fuera por culpa de eso.

En una emergencia puedes forzar que un archivo que está excluido se incorpore a un commit utilizando la opción --force (o abreviada, -f) de git-add, así:

git add --force miArchivo.ext

y pasará al área de preparación, por lo que se incorporará al commit cuando lo hagas.

Si hay un archivo que querrías tener en Git y que no se incorpora a los commits puedes saber exactamente qué regla lo está bloqueando usando el siguiente comando:

git check-ignore -v miArchivo.ext

.

Los archivos .gitkeep

Una cuestión que no tiene clara todo el mundo, incluso cuando llevan bastante tiempo trabajando con la herramienta, es el hecho de que Git no es capaz de gestionar carpetas vacías.

Este hecho se debe al diseño del área de staging del índice de un repo Git, que solamente permite listar archivos. Esto significa que las carpetas no se añaden al control de código con Git: se añaden archivos únicamente.

Cuando añades una carpeta y todos sus contenidos a un commit para gestionarlos, lo que ocurre es que Git añade los archivos que encuentra dentro de esa carpeta y en sus metadatos va incluida la carpeta en la que están. Pero si la carpeta está vacía, ese comando no hará nada.

Es muy habitual, no obstante, que los programadores creen una estructura de carpetas vacías al inicio de un proyecto con el objetivo de ir organizando posteriormente los archivos dentro de ellas: controladores, vistas, modelos, bibliotecas auxiliares, controles, etc… Si el proyecto se comparte entre varias personas (lo habitual) es un fastidio que esa estructura inicial no llegue a todo el mundo hasta que se empiezan a añadir archivos dentro de las mismas.

Para saltarse la limitación de Git de no gestionar carpetas vacías y poder solucionar el problema de compartir estructuras de carpetas iniciales, una convención que se suele utilizar es la de crear dentro de cualquier carpeta vacía un archivo vacío llamado .gitkeep.

Estos archivos no ocupan nada, ya que están vacíos, por lo que no añaden apenas nada tampoco a la carga del remoto de Git ni a la transferencia de datos. Es como si no estuviesen. Pero como sí que están, Git los registra en el control de código y por tanto añade también la carpeta que los contiene.

Alguna gente añade con el mismo objetivo archivos .gitignore vacíos en lugar de .gitkeep. Personalmente no lo veo adecuado puesto que puede llevar a confusión y más de uno pensará que la idea es obviar esa carpeta. También hay quien crea un archivo simplemente con el nombre .keep, pero no me parece tan buena idea puesto que no estamos indicando de forma tan clara como con .gitkeep el hecho de que es un archivo que se refiere específicamente a Git. Que no te sorprendan de todos modos si los llegas a ver en alguna ocasión.

Puede que a pesar de que el directorio no está vacío siga teniendo ese archivo en su interior. Eso es que en algún momento estuvo vacío y que nadie lo ha borrado. Es también bastante típico.

Creando y clonando repositorios remotos

Hasta ahora, aparte del comando clone que apuntamos antes, sólo hemos estado trabajando de forma local. Pero ¿qué pasa si trabajas con un repositorio remoto como los ofrecidos por GitHub y servicios similares? ¿Cómo te sincronizas o subes tus cambios?

De hecho, esta es la forma normal de trabajar: usar un repositorio remoto con el que te sincronizas, de modo que tengas disponibilidad, seguridad, colaboración con otros desarrolladores…

Antes de nada vamos a ver qué es un repositorio remoto y lo básico sobre gestionarlos. De momento no crearemos repos remotos en ningún servicio en la nube, que será lo que harás habitualmente sino que, para no complicarnos, vamos a crear repositorios remotos locales (aunque suene a contradicción). Es decir, serán repositorios que actuarán como remotos pero estarán en nuestro equipo para poder hacer pruebas fácilmente sin ni siquiera estar conectado a Internet.

Crear un repositorio "desnudo" para simular un repo remoto

Un repositorio remoto es idéntico a cualquier repositorio local con el que trabajemos, con una excepción: no contiene archivos de trabajo.

Como has visto, un repositorio convencional con el que trabajamos, aparte de los archivos y carpetas de nuestro proyecto, contiene una carpeta especial llamada .git con la configuración del repositorio. Bien, pues un repositorio remoto es un repo algo especial llamado “desnudo” (o “bare”, en inglés) porque no va a gestionar archivos del proyecto, sino solo sus versiones. Así que en lugar de tener una subcarpeta .git, el contenido de ésta es ya lo que hay directamente en su raíz.

Los repositorios en la nube que utilizarás normalmente, utilizan repos de tipo “bare” para el remoto. Tú puedes crear un repo “bare” propio en una carpeta local o en una unidad de red (pero no en un disco cloud estilo Dropbox o OneDrive, ojo) y trabajar con él para simular un remoto, creando una carpeta vacía y desde dentro de ésta ejecutando esta instrucción:

git init --bare

Una vez que tienes un repositorio “bare” en alguna carpeta, puedes clonarlo a otra carpeta local diferente para poder tener un repositorio de trabajo. Para ello usarás el comando git-clone que ya vimos en la primera lección de este módulo (si lo necesitas, dale un repaso antes de continuar) ejecutándolo desde la carpeta en la que quieras crear el repositorio de trabajo:

git clone C:\temp\git\remoto repoLocal

En la que indicamos que vamos a clonar el repositorio remoto en una carpeta que se llamará repoLocal.

En este caso, como estoy bajo Windows, utilizo una ruta con la letra de la unidad y las barras inclinadas a la izquierda. En un sistema UNIX sería una ruta del estilo /ruta/al/remoto/ o similar. Por ejemplo, en este minivídeo te enseño a crear estos dos repositorios (el “remoto” simulado y la copia local para trabajar) en la carpeta de documentos de un sistema macOS:

AQUÍ habría un vídeo

En el vídeo ves también el contenido de los dos repositorios. El repolocal se ha clonado a partir del remoto, y como puedes observar tiene su subcarpeta .git con la información de los archivos y sus versiones (aunque al crearlo está vacío). El repositorio llamado remoto es un repositorio de tipo “bare” que contiene únicamente la información pero no va a trabajar nunca con archivos reales, por lo que toda la información está directamente en la raíz.

En el vídeo que veremos en breve haremos lo mismo pero en un entorno Bash como el que tiene Linux. Así lo ves en todos los sistemas, aunque las diferencias respecto a Git son nulas.

Configuración del usuario

Cada vez que haces un commit en Git se utiliza tu nombre y tu dirección de correo electrónico. Estos datos son especialmente importantes cuando se envían los commits al remoto, ya que determinan quién los ha hecho, a quién se le notifican los comentarios que se hagan en estas herramientas, etc..

Estos datos se suelen configurar la primera vez que te conectas a un remoto, pero puedes cambiarlos con el comando git-config, que sirve para configurar multitud de cosas en la herramienta.

Las configuraciones que nos interesan para esto son user-name y user.email.

Puedes ver el valor asignado a cualquiera de ellas escribiendo:

git config user.name

Y puedes modificarlos escribiendo el valor a continuación:

git config user.name "David García Valiñas"

Fíjate en que si el valor contiene espacios debe ir entre comillas dobles para que lo registre correctamente

Esto realizará el cambio solamente para el repositorio actual (la carpeta en la que estés, con subcarpeta .git). Si quieres grabarlo de manera global y que sea el valor por defecto para cualquier repositorio futuro que clones, puedes añadirle el switch --global:

git config --global user.name

Flujo de trabajo local-remoto con varios usuarios

Ahora que ya sabes crear y gestionar remotos, sólo te resta conocer los comandos necesarios para enviar y traer commits (o sea, versiones de tus archivos) entre tu repositorio de trabajo y el repositorio remoto.

Ahora es el momento de recordar el esquema general de trabajo con Git para que te ubiques de nuevo:

Lo que vamos a estudiar ahora son las operaciones de Pull y Push para sincronizarnos, y la operación de Fetch (que no está en la figura) que nos permite informarnos de los cambios sin aplicarlos.

Ten en cuenta que, aunque lo habitual es que tengas un único remoto contra el que trabajes, Git soporta la definición y uso de múltiples remotos, de modo que puedes enviar ciertos cambios a unos sí y a otros no, o hacer lo que consideres según el flujo de trabajo que hayáis definido en vuestro equipo.

Obtener cambios desde el remoto - git-pull

Lo primero que debes aprender es a obtener los cambios nuevos que se hagan en el repositorio remoto por parte de otros programadores. Para ello se lleva a cabo una acción que definiremos como hacer pull a partir de ahora.

Hacer pull es tan sencillo como ejecutar el comando git-pull:

git pull

Esto se trae todos los cambios que se hayan producido en el repositorio remoto, en la rama actual (ya veremos qué implica esto de las ramas), desde la última vez que nos hayamos sincronizado con él. Generalmente serán cambios que hayan hecho otros programadores con los que colaboramos o que hayamos efectuado nosotros mismos desde otro de nuestros ordenadores (por ejemplo, tocamos código en casa y luego seguimos trabajando en la oficina).

Algunas opciones con las que puedes configurar el comando son las de la siguiente tabla. De momento no hemos visto los conceptos de ramas (branches) ni etiquetas (tags) pero lo añado por completitud. Posteriormente, cuando conozcas estos conceptos, verás mejor su utilidad:

Opción Descripción
--all Actualiza todos los branches locales con los remotos
--not-tags No actualiza los tags

git-fetch

Otra opción para traerte cambios desde el repo remoto es usar el comando git-fetch:

git fetch --all

La diferencia entre fetch y pull es algo sutil pero importante. En realidad el comando git-pull no es más que un git-fecth seguido de un git-merge. ¿Qué quiere decir esto?

Cuando clonas o sincronizas un repositorio remoto a una carpeta local de trabajo, en la base de datos de Git (recuerda, en la carpeta .git) se copia el contenido del remoto y se crean versiones locales de los archivos que hay en el repositorio. De este modo, tienes el estado actual de los archivos en el remoto y puedes tocar los archivos locales para ir haciendo cambios. Es decir, la rama en la que estás trabajando tiene como dos versiones: la local, que contiene los archivos en los que trabajas y te permite hacer los cambios que necesites, y la misma rama remota que está en la base de datos local de Git y que tiene el estado exacto en el que se encentraban los archivos la última vez que te trajiste los cambios.

En terminología de Git se dice que tu rama local “rastrea” (track) tu rama remota, en el sentido de que, cuando las sincronizas son idénticas, y una vez empiezas a hacer cambios en la rama local, al sincronizar de nuevo con el remoto, los cambios se incorporan a la rama adecuada y siempre acaban siendo iguales. Leerás muy a menudo algo estilo “la rama local trackea a la rama remota”, que es una mala traducción pero es lo que se suele utilizar.

Bien, pues al hacer un fetch sólo se va a actualizar la copia local de tus ramas remotas, sin afectar en absoluto a los cambios que hayas hecho en la rama local, que es en la que trabajas. Sin embargo, al hacer pull, además de traerse los cambios remotos, actualiza el contenido de los archivos de tu rama local con los cambios que hay en la remota para intentar que sean idénticas. Es entonces cuando se pueden producir conflictos, si alguien ha tocado los mismos archivos que tú.

Es importante señalar, aunque lo veremos con detalle luego, que si alguno de tus cambios locales genera un conflicto al hacer un pull, Git no lo puede actualizar con los cambios que haya en el remoto. Es decir, si has hecho cambios en local en un archivo y otro colaborador lo ha modificado al mismo tiempo y las ha subido al remoto antes que tú, puede que surjan conflictos si se han tocado las mismas líneas. Git es muy bueno detectando y solucionando automáticamente estos conflictos, pero hay ocasiones en las que no se pueden resolver de manera autónoma y debes intervenir y resolverlos manualmente. Más adelante veremos cómo solventar esta situación.

El comando git-fetch es muy útil, por tanto, para comprobar simplemente si existen variaciones en los archivos del repo remoto, es decir, si han habido cambios adicionales en los archivos de tu proyecto desde la última vez que los has sincronizado. Habrá ocasiones en las que no te interese traer los cambios al branch, bien porque tienes tus propios cambios, bien para evitar conflictos… Por lo que en estos casos lo mejor es optar por el fetch en vez del pull: saber qué cambios hay pero no afectar para nada a los archivos con los que trabajas.

Nota: realmente no te preocupes si te has perdido con esto último. Cada uno de estos conceptos los veremos más adelante con detalle. Ahora quédate con que git-pull es el comando que vas a usar normalmente.

Subir tus cambios a un repositorio remoto - git-push

Con el comando anterior hacemos la sincronización en un sentido, trayéndonos a local los cambios que se hayan producido en el remoto. Pero si tenemos cambios que hemos realizado localmente, tenemos que poder sincronizarlos en el otro sentido y enviarlos al repositorio remoto para que otros (o nosotros mismos en otro momento) podamos recibirlos y, también, para salvaguardarlos fuera de nuestro equipo.

Para lograrlo el comando adecuado es git-push:

git push

Este comando subirá a nuestro repositorio remoto todos los commits que hayamos hecho en local y que no se hayan sincronizado previamente. Pero claro, para poder subir nuestros cambios, antes debemos haber hecho un pull y tener todo actualizado. En caso de que no lo hayamos hecho, Git nos daría un error para que obtengamos primero los cambios y resolvamos los posibles conflictos.

IMPORTANTE: antes de realizar un git-push para enviar nuestros commits al remoto, es necesario siempre hacer un git-pull para ver si hay modificaciones que puedan entrar en conflicto con los cambios que tenemos localmente y así poder resolverlos antes.

Dicho esto, puede parecer sencillo sincronizar repos locales con remotos, pues son tan solo dos simples comandos, pero el uso de git-push y git-pull será lo que más situaciones extrañas te generen en el repositorio si no tienes cuidado. Y en concreto el comando git-push tiene unas de las opciones más destructivas en el mundo de Git:

git push --force

Se puede usar la opción abreviada -f que es equivalente a --force pero más escueta, aunque hace que sea menos evidente lo que estamos haciendo.

Este comando es el sudo de Git, y lo que hace es sobrescribir TODO lo que haya en el remoto la rama actual con tu contenido sin importar si había cambios, commits o conflictos.

Por ello ten especial cuidado al hacer uso del comando git push -f. Asegúrate bien de lo que estás haciendo si lo vas a ejecutar en tu repositorio, sobre todo si estás sobre la rama master.

Resumen del flujo de trabajo

Todo lo que hemos visto hasta ahora puede parecer muy complicado, pero en realidad se puede resumir en muy pocas líneas. Básicamente el trabajo fundamental con Git implica los siguientes pasos una vez has clonado un repositorio a tu máquina local:

  1. Sincronizar los cambios con el repositorio remoto, usando git-pull.
  2. Trabajar con la copia local de nuestros archivos y hacer los cambios que necesitemos.
  3. Cuando estén listos, creamos una versión nueva de los cambios usando git-add y git-commit, o directamente git commit -a si queremos hacer versión de todos los cambios.
  4. Nos traemos de nuevo los cambios del remoto (git pull) por si han habido cambios mientras trabajábamos.
  5. Enviamos nuestros commits al remoto (git push) para archivarlos y ponerlos a disposición de los demás que estén trabajando en el remoto.

El 90% del tiempo que trabajas con Git estarás realizando esta secuencia.

Vamos a verla en un vídeo práctico. Sólo un par de detalles sobre el mismo:

  • Estoy utilizando Git Bash para ejecutar los comandos. Esto es un entorno Bash, como el que tiene Linux, que funciona siempre igual, independientemente del sistema operativo. Tú no tienes por qué utilizarlo y puedes recurrir a la línea de comandos de tu sistema para hacer exactamente lo mismo (cmd o PowerShell en Windows, el Terminal en macOs o bash, zsh, Powershell… en Linux).
  • En el vídeo utilizo el mítico editor de línea de comandos Vim para editar los archivos. Tú puedes usar el bloc de notas o cualquier editor de texto que prefieras, y crear archivos .html o .cs o del lenguaje que prefieras. Por cierto si usas Vim, para salir debes pulsar :q o :wq si quieres grabar.

Vamos allá.

Prácticas propuestas para el módulo

En este módulo hemos presentado los comandos esenciales con los que poder empezar a trabajar con Git. Por supuesto, aún nos quedan muchos comandos por ver en las próximas lecciones que nos permitirán llevar a cabo cuestiones más complejas, pero absolutamente todos hacen uso como mínimo del flujo de trabajo que acabas de estudiar.

Es esencial que entiendas bien el proceso de hacer un commit y te acostumbres a deshacer/revisar cambios a través de los comandos presentados en esas lecciones. Para ello te propongo que hagas los siguientes ejercicios:

1. Crea un repositorio local

2. Crea el archivo index.html en la raíz de tu repositorio con el contenido:

    <!DOCTYPE html>
    <html>
 
    <head>
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
        <title>Modulo 2</title>
    </head>
 
    <body>
 
    </body>
 
    </html>

3. Añade el archivo nuevo a la zona de preparación, pero NO hagas commit.

4. Vuelve a modificar el archivo añadiendo dentro del body lo siguiente:

<h1>Hola mundo</h1>

5. Comprueba el estado del repositorio y fíjate en qué estado se encuentra el archivo index.html.

6. Vuelve a añadir los cambios a la zona de preparación.

Nota: este comportamiento es el esperado. Tú preparas unas modificaciones para registrar, pero eso no significa que no puedas seguir trabajando y seguir añadiendo cambios al archivo hasta la hora de hacer el commit.

7. Registra el cambio en el repositorio local a través de un commit.

8. Repite el paso 4, 6 con el siguiente código, pero NO hagas commit:

    <h3>Este cambio no se debería subir</h3>

9. Deshaz tus cambios y deja el archivo limpio.

Nota: intenta hacer el checkout antes del reset y comprueba qué es lo que pasa.

10. Repite el paso 4, 6 y 7 con el siguiente código:

    <h3>Cambio registrado</h3>

11. Comprueba cómo ha quedado tu histórico del repositorio.

12. Repite las veces que creas necesarias estos pasos hasta que entiendas perfectamente el proceso.

En el próximo módulo veremos comandos más avanzados por lo que es esencial que entiendas correctamente estos pasos.

informatica/programacion/cursos/git_control_codigo_fuente_programadores/git_basico_linea_comandos.txt · Última modificación: por tempwin