Introducción al software de control de versiones Git. Una guía práctica para trabajar con Git desde la terminal y desde el entorno de desarrollo Eclipse IDE. También aprenderemos a utilizar repositorios remotos en GitHub y a sincronizarlos con nuestro equipo local mediante credenciales de acceso por token.
Git es uno de los sistemas de control de versiones más utilizados por los desarrolladores de software. Fue creado por Linus Torvalds y lanzado en 2005.
Un sistema de control de versiones es una herramienta que permite gestionar las distintas actualizaciones y modificaciones realizadas en el código. Su principal función es registrar los cambios en una línea de tiempo, lo que facilita volver a versiones anteriores si algo no funciona como se esperaba. Otra característica clave es la posibilidad de crear ramas secundarias del proyecto, en las que pueden trabajar diferentes miembros del equipo. Una vez que su trabajo está finalizado, estas ramas pueden fusionarse con la rama principal.
Al trabajar con Git, nuestro proyecto se encuentra, como mínimo, en dos ubicaciones: una copia local en el ordenador de uno de los desarrolladores y una copia remota en un servidor de repositorios. Algunos de los servicios más conocidos que alojan repositorios Git son GitHub, GitLab, Bitbucket y SourceForge.
El siguiente esquema ayuda a entender cómo funciona Git. Se representa una estructura local dividida en tres partes:
git add
).git commit
).En la parte derecha del esquema, se encuentra la zona del servidor, donde se suben los repositorios
(git push
) y desde donde también se pueden clonar
en local (git clone
).
Explicado de manera resumida, para empezar a trabajar con Git, debemos crear un nuevo repositorio
localmente (con los comandos git add
y git commit
) y, en el servidor (GitHub u otro), subir
los archivos con git push
.
Otra posibilidad es utilizar un repositorio que ya contiene contenido. En este caso, al clonarlo en
el espacio de trabajo con git clone
, se copiará
todo el árbol de archivos del proyecto en nuestro ordenador.
Hasta ahora, hemos hablado de un proyecto hipotético con un único desarrollador y una sola rama de trabajo. Sin embargo, lo habitual es que los repositorios tengan múltiples ramas. Un caso común es contar con una rama principal de desarrollo (generalmente llamada main) y otras ramas secundarias destinadas a desarrollo, pruebas, distintos equipos de trabajo, etc. Hay numerosos casos en los que se pueden aplicar ramas secundarias.
Cuando el contenido de una rama está finalizado y se desea integrar en la rama principal, se fusiona
con git merge
, pasando a formar parte de la rama
principal. De esta manera, se trabaja sobre copias del proyecto, protegiendo el código original.
Además de la línea de comandos, es posible utilizar clientes gráficos para Git, los cuales facilitan la visualización de la evolución de las ramas y permiten realizar muchas operaciones sin necesidad de memorizar comandos. Algunos ejemplos son SourceTree, GitHub Desktop, SmartGit y GitKraken.
Desde el entorno de desarrollo también es posible gestionar Git directamente desde los principales IDEs que lo integran en sus menús, como Eclipse, VS Code, IntelliJ IDEA o NetBeans, por citar algunos muy conocidos.
Desarrollar un ejemplo ayudará a entenderlo mejor. Vamos a crear un mini proyecto y hacer las operaciones más comunes con git.
Necesitamos crear una cuenta de usuario en un servidor de repositorios y a continuación crear un nuevo proyecto en blanco. En este pequeño tutorial usaré https://github.com para crear un repositorio llamado "demoRepositorio". Si no tienes cuenta de usuario en github puedes crearte una gratuitamente.
Pulsando el botón "Create Repository" se crea el repositorio vacío en el lado servidor. Para más sencillez a la hora de subir los archivos al repositorio, es recomendable no marcar aquí la opción de crear los archivos README.MD y .gitignore porque tendremos un error de conflicto de historial entre nuestro repo local y el remote. Esto ocurre porque Git detecta que la rama main en el remoto tiene historial de commits diferente al de tu rama local. No te preocupes si no entiendes ahora que es una rama ni un commit. Lo sabrás enseguida.
El error mostrado al subir los archivos con el comando push
podría ser como
este:
error: failed to push some refs to 'https://github.com/agriarte/demoRepositorio.git'
En este primer repositorio (y en la mayoría de los casos, es la opción más común), tomaremos como guía de referencia los comandos dentro del bloque create a new repository on the command line. Se trata de una serie de comandos de Git que inician un repositorio en la carpeta local y lo vinculan al repositorio en el servidor.
Si has llegado hasta aquí, ya tienes el repositorio nuevo y vacío https://github.com/agriarte/demoRepositorio, al que podrás subir un proyecto cuando habilites los permisos de repositorio correspondientes.
NOTA: También podemos usar las GitHub Apps y las OAuth Apps como métodos alternativos de seguridad, aunque están más orientados a proyectos de equipos de desarrollo profesionales. Para proyectos personales o con pocos desarrolladores, es suficiente utilizar Fine-grained Tokens. Te dejo el enlace a la documentación oficial para que consultes estos sistemas: Introducción a las Aplicaciones de GitHub.
Siguiendo los consejos de la lista anterior, he creado un Fine-grained token nombrado "Tutorial" para poder identificarlo fácilmente en mi lista de tokens creados:
Este token solo permite gestionar el repositorio del tutorial durante 1 mes:
Dentro del apartado "Repository permissions", le asigno permisos de lectura y escritura en Contents (código) y Pull Requests (gestión de merges):
Al pulsar el botón Generate Token, se mostrará el token una sola vez. Deberemos copiarlo y guardarlo en un lugar seguro. Si lo perdemos o caduca, tendremos que generar uno nuevo.
Desde https://git-scm.com, puedes obtener información sobre la instalación de Git en todos los sistemas y acceder a la documentación oficial.
En Windows, Git también instala una consola de comandos Bash para ejecutar sus comandos, ya que el terminal CMD no es completamente compatible. Si usas VS Code, puedes utilizar su terminal integrada, la cual funciona correctamente con Git. También se instala un programa con interfaz gráfica para usar Git, pero no lo veremos en este tutorial. Si te interesa, siéntete libre de explorarlo y usarlo si te resulta útil.
Una vez instalado Git, es recomendable , y en muchos casos necesario, configurar nuestro nombre y correo electrónico en el archivo de configuración. Aunque no es estrictamente obligatorio para usar Git, sí lo es para poder realizar commits correctamente, ya que esta información se usa para identificar a cada desarrollador en el historial de cambios del proyecto.
git config --global user.name "Nombre Apellido" #Configura tu nombre.
git config --global user.email "ejemplo@email.com" #Configura tu email.
git config --list #Muestra la configuración actual de Git.
A continuación, creamos una nueva carpeta que servirá como directorio de trabajo de Git. Aquí es
donde queremos crear o clonar el repositorio. Para abrir la consola en esta carpeta, haz clic
derecho dentro de ella y selecciona "Git Bash Here" en el menú contextual. Esto abrirá una
terminal
directamente en el directorio del proyecto. Si no lo hacemos así, al abrir la consola
manualmente,
será necesario navegar hasta el directorio usando los comandos cd
y ls
.
Al tratarse de un nuevo proyecto, podemos seguir las instrucciones de GitHub y ejecutar los comandos del bloque "or create a new repository on the command line" que vimos al crear el repositorio en GitHub.
echo
es un comando de impresión que, en este caso, redirige el texto a un archivo. Si el archivo no
existe, lo crea. Esta línea crea un archivo README.md
en el directorio actual, que
también es nuestro directorio de trabajo. En GitHub, este archivo servirá como la descripción
del
repositorio, donde podemos incluir instrucciones y cualquier otra información relevante sobre el
proyecto..git
, donde se almacenan los datos
de configuración del repositorio.git add
,
lo que los mueve a la zona de preparación (staging area). Si se trata de archivos de
texto,
es posible que aparezca un mensaje de advertencia sobre la conversión de saltos de línea y
retornos
de carro, debido a diferencias entre Unix y Windows. Este ajuste no afecta el funcionamiento,
por lo
que podemos continuar sin problemas.git commit
mueve los archivos
rastreados al repositorio local. El parámetro -m
permite añadir un mensaje descriptivo
sobre el cambio. Tras ejecutar el commit, Git informa sobre los archivos registrados y genera un
identificador único (hash) para esta versión. A medida que el proyecto avanza y
realizamos
nuevos commits, cada uno tendrá su propio hash, lo que nos permitirá movernos entre versiones.
git push
sube los archivos confirmados
(committed) al repositorio remoto. En este caso, la instrucción significa: "Sube los
archivos rastreados al servidor remoto en la rama main". Para Git, origin
representa el repositorio remoto. Además, el parámetro -u
establece la rama remota
de seguimiento, lo que facilita el uso de git push
en el futuro sin necesidad de
especificar la rama y el repositorio remoto. En adelante, si queremos subir archivos al mismo
destino ("origin"), podemos abreviar el comando a simplemente git push
.En este punto, ya tenemos un repositorio completamente creado. Podemos verificar que se ha subido correctamente accediendo a GitHub desde el navegador. En la página del repositorio, podremos ver todos los archivos, los commits realizados junto con sus identificadores (hashes), clonar el repositorio en otro ordenador y realizar muchas otras operaciones. Por ahora, no entraremos en demasiados detalles sobre GitHub.
También podemos utilizar comandos para obtener información sobre el historial del repositorio. Por
ejemplo, con git log --oneline
podemos ver un
resumen de los commits realizados, mostrando cada uno en una sola línea con su identificador
(hash) y su mensaje asociado.
A medida que trabajamos, iremos añadiendo los archivos con git add
y confirmando los cambios con git commit
en el repositorio local. Los archivos no
se subirán al servidor hasta que ejecutemos git push
. Vamos a verlo creando un archivo
index.htm
y observando las respuestas de los comandos git status
y git status -s
.
Vemos que Git detecta que hay un nuevo archivo index.htm
sin seguimiento, junto con otra
información sobre la rama en la que nos encontramos. Usa el color rojo y los símbolos ?? para indicar los archivos sin seguimiento. Ahora vamos a añadirlo al
área de preparación (staging area) para ver qué sucede. Podemos usar git add index.htm
o, de manera más corta, git add .
, que añade todos los archivos sin
seguimiento.
Los símbolos ?? se reemplazan por una A. La letra A proviene de "add".
Si modificamos el archivo index.htm
y ejecutamos de nuevo git status
, Git nos alertará de que hay cambios que
no se han añadido. Aparecerá una M roja, que indica que el archivo ha
sido modificado.
Para añadir los cambios, debemos ejecutar el comando git add .
A continuación, avanzamos al siguiente paso: realizaremos un commit de los cambios para actualizar el
repositorio local. El comando es: git commit -m "nuevo index"
Ahora tenemos 2 commits, los cuales podemos identificar por su hash y mensaje de commit. Para subir
estos cambios al servidor, repetimos el comando git push -u origin main
.
Y con esto, el repositorio ya está disponible en GitHub.
Cuando se está desarrollando código, puede ocurrir que estemos probando una parte nueva y no funcione como deseamos o, aún peor, que toda la aplicación deje de funcionar. En estas situaciones, usar Git puede salvarnos. Como vamos guardando el proyecto por fases mediante commits, siempre podemos volver a versiones anteriores o eliminar el commit más reciente para regresar al estado anterior.
Lo primero que debemos hacer es usar el comando git log --oneline
para ver el historial de
commits.
a3c9d3b (HEAD -> main) Tercer commit: añadimos archivo3.txt
7c9a1e2 Segundo commit: añadimos archivo2.txt
1f4c3b7 Primer commit: añadimos archivo1.txt
Si queremos retroceder a un punto anterior, por ejemplo, al segundo commit, podemos utilizar los
comandos
git checkout
o git reset
,
dependiendo de si queremos conservar o eliminar los commits posteriores.
Si solo queremos revisar o trabajar temporalmente en una versión anterior sin modificar el historial, utilizamos checkout:
git checkout 7c9a1e2 # Retrocede temporalmente al segundo commit (modo detached HEAD)
Al retroceder a un estado anterior, salimos de la rama main
. Debemos crear una nueva
rama si queremos desarrollar código a partir de este punto, y fusionarla luego con main
si es necesario.
Para volver a la rama main
en su último estado, ejecutamos:
git checkout main
En cambio, si queremos volver de forma permanente a un estado anterior y eliminar los commits siguientes, usamos reset --hard:
git reset --hard 7c9a1e2 # Borra los commits posteriores y deja el repositorio en ese punto
Estas operaciones afectan únicamente al repositorio local. Si intentamos hacer push
a
GitHub tras un reset
,
obtendremos un error, ya que por seguridad, GitHub no permite sobrescribir el historial remoto por
defecto.
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'https://github.com/...'
Para evitar este error, debemos utilizar el parámetro
--force
al realizar el push
:
git push origin main --force # Elimina los commits del historial remoto y lo reemplaza por el local
Ten en cuenta que esta operación puede eliminar cambios del repositorio remoto. Úsala con
precaución, especialmente si trabajas en equipo.
Otros ejemplos útiles sobre este tema:
git reset --soft HEAD~1 → Elimina el último commit. El parámetro --soft
hace
que
los archivos se borren del repositorio local, pero se mantengan en el directorio de trabajo. Si
usamos --hard
, también se borrarán los archivos del directorio de trabajo.
git reset --soft HEAD~2 → Elimina los dos últimos commits.
git reset --soft 3e4r2s → Elimina el commit con el ID especificado.
Si queremos eliminar archivos del stage area, podemos usar:
git rm --cached nombreArchivo → Borra un archivo específico del stage area, pero lo mantiene en el directorio de trabajo.
git reset → Borra todos los archivos del stage area, pero sin afectar su contenido en el directorio de trabajo.
Si solo queremos modificar el mensaje del último commit sin cambiar su contenido, utilizamos el
parámetro --amend
:
git commit --amend -m "Nuevo mensaje" → Reemplaza el comentario del último commit y genera un nuevo hash.
git commit --amend → Con esta instrucción también podemos modificar el mensaje de un commit existente. Sin embargo, la primera vez que se usa puede resultar poco intuitiva, ya que abrirá el archivo de configuración en el editor de Linux Vim.
Desde Vim, podemos editar el comentario del commit. Para guardar los cambios y salir, debemos seguir estos pasos:
:wq
→ Guarda los cambios y sale del editor.:q!
→ Sale sin guardar los cambios.:w
→ Guarda los cambios pero permanece en el editor.cursores Flecha
→ Movernos por las líneas del texto:dd
→ Borra una línea entera:i
→ el editor se pondrá en modo "INSERT" permitiendo añadir caracteres
Borrar y Supr
→ Eliminan caracteres de la línea actual. No borran los
saltos de líneaEn muchos proyectos, hay archivos que no queremos incluir en un repositorio. Puede ser por privacidad, como archivos que contienen credenciales de acceso a bases de datos, o porque no forman parte del código fuente, como las dependencias y librerías de algunos frameworks.
Para evitar que estos archivos sean rastreados por Git, se utiliza un archivo especial llamado
.gitignore
, que debe ubicarse en la raíz del
directorio de trabajo.
Nota para usuarios de Windows: En Windows, no es posible crear archivos que comiencen con
un punto desde el Explorador de archivos. Para solucionarlo, podemos usar el comando echo
en la terminal:
echo "" >> .gitignore
Tras ejecutar este comando, se creará el archivo .gitignore
. En él podemos listar los
archivos y directorios que queremos excluir del control de versiones. Cada línea representa un
elemento que Git ignorará.
Si se trata de un directorio, se añade una barra /
al final del nombre.
Ejemplo de .gitignore
:
CarpetaPrivada/
conexionDB.php
*.log
Importante: Si un archivo ya ha sido agregado al repositorio antes de incluirlo en .gitignore
, este no se eliminará automáticamente
del
control de versiones. Si ya hemos hecho git push
,
el archivo seguirá existiendo en el servidor, por lo que será necesario eliminarlo manualmente
del
historial si queremos que desaparezca.
Subir accidentalmente un archivo con contraseñas o información sensible a un repositorio público
como
Github
puede ser una situación grave. Simplemente eliminarlo del directorio local, hacer un git commit
y un git push
no es suficiente, ya que el archivo
seguirá
estando en los commits anteriores. Incluso eliminarlo directamente desde GitHub no lo borrará
del
historial.
En estos casos, la opción más sencilla puede ser eliminar el repositorio por completo y volver a
subir uno nuevo. Sin embargo, si queremos limpiar el historial sin eliminar el repositorio,
podemos
utilizar la herramienta BFG Repo-Cleaner o el comando git filter-branch
.
Supongamos que hemos subido por error el archivo pagina2.htm
y queremos eliminarlo por completo
del
repositorio. Usaremos el siguiente comando:
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch pagina2.htm' \ --prune-empty --tag-name-filter cat -- --all
Importante: Para evitar que el archivo vuelva a subirse accidentalmente en el futuro,
debemos asegurarnos de agregarlo a .gitignore
.
Si
aún no lo hemos hecho, lo editamos y realizamos un nuevo commit:
git commit -m "Añadir pagina2.htm a .gitignore"
Finalmente, ejecutamos los siguientes comandos para forzar la actualización del repositorio y limpiar cualquier rastro del archivo en el historial:
git push origin --force --all git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin git reflog expire --expire=now --all git gc --prune=now
Tras estos pasos, el archivo habrá desaparecido por completo de todos los commits del servidor.
Eliminar un archivo commiteado no sensible
Si lo que queremos eliminar es un archivo o directorio subido al repositorio que no tiene datos
peligrosos de ser compartidos, porque hasta este momento no estaba no en la lista del .gitignore
este método es mucho menos agresivo:
git rm -r --cached carpetaPrivada/
git commit -m "Eliminada carpetaPrivada del control de versiones"
Después de eso, .gitignore
se encargará de que Git
no la vuelva a seguir.
Hasta ahora hemos trabajado con un único flujo de desarrollo en una sola rama. Sin embargo, Git permite el uso de ramas (branches), una funcionalidad clave para gestionar proyectos de manera eficiente.
- ¿Qué es una rama en Git?
Una rama es una copia independiente de la rama principal, con su propio historial de commits
. Esto permite a los desarrolladores
trabajar
en nuevas funciones o corregir errores sin afectar el código original. Si los cambios son
útiles, se
pueden fusionar con la rama principal (main
o master
).
Imagina que estamos desarrollando una página web y queremos modificar el diseño CSS sin afectar la
versión en producción.
Para ello, podemos crear una rama llamada estilos
y trabajar allí:
Con este flujo, protegemos el código original y podemos decidir si queremos integrar las modificaciones en la versión principal. De manera gráfica, las operaciones realizadas se pueden representar así:
Para gestionar ramas en Git, utilizamos el comando git branch
. Aquí tienes algunos usos comunes:
git branch nuevaRama
git branch -d nuevaRama
git push origin --delete nuevaRama
git checkout nuevaRama
Tras ejecutar este comando, estarás trabajando en la rama indicada.
Para fusionar la rama actual con la rama que indiquemos:
git merge nuevaRama
Por ejemplo, para fusionar la rama nuevaRama
dentro de la rama
main
, podemos seguir estos pasos:
main
si no estamos ya:
git checkout main
git merge nuevaRama
Fusión con commit explícito (sin fast-forward):
Si queremos forzar la creación de un commit de merge para dejar constancia explícita en el
historial, usamos la opción --no-ff
con un mensaje:
git merge --no-ff nuevaRama -m "Fusionar nuevaRama a main"
Al utilizar el parámetro --no-ff
, se crea un commit de merge. El comando
git merge
sin opciones no deja evidencia de que una rama haya sido fusionada,
ya que no crea un commit de merge, lo que puede dificultar el seguimiento del historial en
git log
. Usar --no-ff
permite mantener un historial más claro y
visual, especialmente al trabajar con ramas de funcionalidades, ya que crea un commit
explícito que indica cuándo se realizó la fusión.
git branch
La rama actual aparecerá marcada con un asterisco (*).
git branch -r #modo abreviado
git branch --remote #modo largo
La rama actual aparecerá marcada con un asterisco (*).
git branch -m nombre_viejo nombre_nuevo #-m viene de move
git branch --move nombre_viejo nombre_nuevo #modo largo
En este ejemplo, mi intención es crear un archivo style.css
sobre la rama pruebas
con algunos cambios estéticos. Cuando
esté
satisfecho con el resultado, uniré las ramas. Actualmente, me encuentro en la rama pruebas
, ya que he ejecutado el comando git
checkout pruebas. Esto significa que cualquier cambio que realice solo afectará a esta
rama.
Es importante aclarar que en un caso real, no se hacen commits constantemente. Este es solo un ejercicio de práctica para ilustrar cómo se manejan las ramas en Git.
Voy a crear el archivo /css/estilos.css
, modificaré el archivo HTML para incluir un
archivo CSS externo, y luego realizaré los comandos git add y git commit que ya
conocemos. Además, haré más cambios en diferentes commits para que el ejemplo sea más completo.
Ahora, vamos a complicarlo un poco. Cambiaré a la rama main
con el comando git checkout main, y
allí
realizaré algunos cambios con sus respectivos commits.
En este ejemplo, he utilizado solo comandos que ya conocemos:
La rama actual aparecerá marcada con un asterisco (*).
Por último, para fusionar las ramas estando ubicado en la rama principal, ejecutaré:
git merge pruebas -m "merge pruebas"
En la captura anterior, podemos ver cómo el comando git merge nos informa sobre los cambios realizados en el proyecto. En este caso, la fusión se ha realizado sin conflictos. Sin embargo, los conflictos ocurren cuando el mismo archivo ha sido modificado en las mismas líneas en ambas ramas. ¿Cómo resuelve Git estos conflictos? Lo explicaremos en una futura actualización de este tutorial.
Desde un cliente gráfico de Git, las ramas quedarían representadas visualmente de la siguiente forma:
Aunque estas herramientas gráficas facilitan el trabajo y ofrecen una vista más intuitiva del repositorio, tener una base sólida en Git por línea de comandos es muy útil para comprender su funcionamiento, tener un mayor control de lo que está pasando y, también, permite su uso por terminal en servidores y entornos remotos.
Existen muchos escenarios en los que necesitaremos obtener o sincronizar un repositorio de GitHub:
A continuación, se presenta una lista de situaciones comunes que seguramente encontraremos al trabajar con repositorios remotos.
Una forma sencilla de obtener el código fuente de un proyecto alojado en GitHub —ya sea que tengamos o no permisos sobre el repositorio original— es descargarlo directamente desde la web de GitHub.
Para importar un repositorio desde GitHub a Eclipse sin usar la consola, lo primero que debemos hacer es descargar el archivo ".zip" desde la página del repositorio en GitHub y descomprimirlo en un directorio local, el cual será el directorio principal del proyecto en Eclipse.
Importante: Este método no descarga la configuración de Git del repositorio, por lo que no podremos consultar el historial de commits ni sincronizar cambios con el repositorio original mediante nuevos pushes.
Desde Eclipse, accedemos al menú File > Import > General > Projects from Folder or Archive.
A continuación, seleccionamos el directorio raíz del proyecto explorando la unidad al pulsar el botón [Directory]
Por último, hacemos clic en el botón [Finish] y el repositorio quedará importado en el disco como si fuera un proyecto creado desde cero en Eclipse. Si revisamos el directorio raíz, veremos que se ha creado el archivo .project, lo que indica que ahora es un proyecto Eclipse.
Si realizamos una modificación y consideramos conveniente crear un nuevo repositorio, podemos subirla, pero será como un repositorio propio en nuestra cuenta de GitHub, de manera completamente independiente al repositorio original.
Si tenemos permisos para acceder al repositorio o simplemente es público, una forma rápida y habitual de obtenerlo es clonándolo directamente desde la terminal.
En Windows, podemos usar Git Bash, la consola que se instala junto con Git y permite ejecutar comandos de Git cómodamente.
Lo primero que tenemos que hacer es copiar la URL del repositorio desde GitHub. Para ello, entramos en la página del repositorio, pulsamos el botón verde Code y copiamos la URL del tipo HTTPS (o SSH si tenemos configurada una clave).
Una vez copiada, abrimos Git Bash, navegamos hasta el directorio donde queremos clonar el proyecto, y escribimos el siguiente comando:
git clone https://github.com/usuario/repositorio.git
Esto descargará todo el contenido del repositorio y creará una carpeta con el mismo nombre que el proyecto. Además, a diferencia de importar desde un .zip, esta vez sí tendremos configurado el repositorio con su historial de commits y conexión al remoto, lo que nos permitirá hacer pull, push y trabajar de forma completa con Git.
Para importarlo dentro del IDE Eclipse, podemos utilizar la importación manual que vimos en el apartado anterior, accediendo al menú File > Import > General > Projects from Folder or Archive.
Cuando somos propietarios de un repositorio en GitHub o formamos parte del equipo de desarrollo con los permisos adecuados, podemos sincronizar el repositorio local con el remoto para actualizar el código, crear nuevas ramas o realizar cualquier otra operación de colaboración.
Una vez descargado el repositorio con el comando git clone https://github.com/agriarte/demoRepositorio.git
:
Pedro@DESKTOP-Q4VPLSR MINGW64 /g/JAVA/SpringToolSuite/DemoRepositorioLocal
$ git clone https://github.com/agriarte/demoRepositorio.git
Cloning into 'demoRepositorio'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (4/4), done.
Para comprobar que nuestra sesión local tiene los permisos configurados correctamente, podemos ejecutar el siguiente comando desde el directorio del repositorio:
$ git push
Everything up-to-date
Si todo está en orden, veremos un mensaje como el anterior, indicando que el repositorio remoto ya está actualizado con los últimos cambios locales.
Sin embargo, si no tenemos configurado un Fine-Grained Token o una Auth App, es posible que obtengamos un error como este:
$ git push
remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/agriarte/demoRepositorio/'
Este error indica que Git está intentando autenticarse mediante usuario y contraseña, un método que
GitHub ya no permite. En este caso, será necesario configurar correctamente el método de
autenticación con un token de acceso personal (PAT) para poder realizar operaciones
como push
o pull
.
Si ya existe, un token para sincronizar con el repositorio, hacemos un copiar/pegar en la ventana de Login que aparece y ya tendremos permisos de gestión del repositorio hasta que caduque
Si el token es válido, la credencial se almacena en el Administrador de credenciales de Windows y permanecerá allí mientras sea válida. Puede eliminarse en cualquier momento para revocar los privilegios.
Nota: Si seleccionamos la opción "Sign in with your browser", es posible que podamos sincronizar con el repositorio remoto desde la terminal. Esto se debe a que GitHub configura automáticamente un mecanismo de autenticación interno que puede utilizarse en este equipo, siempre que tengamos una sesión activa en www.github.com. La pantalla que aparecerá será similar a la siguiente:
Sin embargo, este método no será suficiente si intentamos sincronizar desde Eclipse IDE, ya que Eclipse no admite este tipo de autenticación de forma predeterminada. En este caso, necesitaremos usar un Token de Acceso Personal (PAT) para autenticar nuestras operaciones de Git dentro del IDE.
Cuando trabajamos en equipo y necesitamos actualizar nuestra versión local con la existente en remoto nos podemos encontrar con varias escenarios que requieren diferente forma de actuar para hacer la actualizacíon.
Por ejemplo: en el remoto del repositorio del tutorial tengo un archivo nuevo llamado "creado_en_github.txt" que no existe en local. Si intento hacer "push" de nuevos archivos, el mensaje de error me da la pista de como proceder. Dice que haga primero un "git pull" para traer el archivo a la rama local. Lo vemos:
Pedro@DESKTOP-Q4VPLSR MINGW64 /g/JAVA/proyectosnetbean/demoRepositorio (main)
$ git push
To https://github.com/agriarte/demoRepositorio.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/agriarte/demoRepositorio.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Este error viene porque github no deja actualizar la rama local si hay diferencias ajenas a
nuestro repositorio local. La solución está en hacer antes git pull
para
ponernos al día
con el remoto:
Pedro@DESKTOP-Q4VPLSR MINGW64 /g/JAVA/proyectosnetbean/demoRepositorio (main)
$ git pull origin main
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (4/4), 1.03 KiB | 1024 bytes/s, done.
From https://github.com/agriarte/demoRepositorio
* branch main -> FETCH_HEAD
ac2329f..60d4542 main -> origin/main
Updating ac2329f..60d4542
Fast-forward
public/creado_en_github.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 public/creado_en_github.txt
Este escenario ocurre cuando dos personas modifican el mismo archivo y Git no puede hacer un
merge automático. Simulemos ese caso creando diferencias en el contenido de
creado_en_github.txt
tanto en local como en GitHub para que tengan líneas con
diferencias en ambos lados.
Si ahora intentamos un git pull
, Git nos
devolverá un mensaje de error por
conflictos en la fusión (merge), ya que detecta que el mismo archivo fue modificado en ambas
ubicaciones y no puede decidir qué versión conservar.
$ git pull origin main
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (4/4), 1.01 KiB | 1024 bytes/s, done.
From https://github.com/agriarte/demoRepositorio
* branch main -> FETCH_HEAD
60d4542..9488b37 main -> origin/main
Auto-merging public/creado_en_github.txt
CONFLICT (content): Merge conflict in public/creado_en_github.txt
Automatic merge failed; fix conflicts and then commit the result.
Solución al conflicto manteniendo la versión remota:
Para esto, usamos el comando git checkout --theirs
para decirle a Git que
queremos la versión remota:
(main|MERGING)$ git checkout --theirs public/creado_en_github.txt
Updated 1 path from the index
(main|MERGING)$ git add public/creado_en_github.txt
(main|MERGING)$ git commit -m "Conflicto resuelto: se mantiene la versión remota"
[main 9f37bc7] Conflicto resuelto: se mantiene la versión remota
Con esto, descartamos los cambios locales y dejamos el archivo exactamente como está en el repositorio remoto (GitHub).
También existe la alternativa de mantener los cambios locales utilizando
git checkout --ours
, pero te dejo esa
opción como ejercicio para que la
explores por tu cuenta.
Alternativa: Si no te interesa conservar los cambios locales y deseas dejar tu repositorio exactamente igual que el remoto, puedes ejecutar los siguientes comandos:
$ git fetch origin
$ git reset --hard origin/main
git add
, no se eliminarán. El repositorio quedará exactamente como está en
GitHub, sin errores ni conflictos, y la rama local volverá a estar completamente alineada
con la remota.
Este escenario ocurre cuando dos personas modifican el mismo archivo, y Git no puede hacer un merge automático debido a que ambas versiones tienen cambios en las mismas líneas del archivo.
Para simular este caso, modificamos el archivo creado_en_github.txt tanto
en
local como en
GitHub, asegurándonos de que tengan diferencias en las mismas líneas. En el repositorio
local, también
debemos actualizar los cambios con los comandos git add .
y
git commit -m "colores"
para que los cambios sean reconocidos como parte del proyecto local.
Como era de esperar, al ejecutar git pull
se devuelve un error y además el promt
se modifica a (main|MERGING):
$ git pull origin main
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (4/4), 1.04 KiB | 1024 bytes/s, done.
From https://github.com/agriarte/demoRepositorio
* branch main -> FETCH_HEAD
6dc3c71..7ea1fed main -> origin/main
error: Your local changes to the following files would be overwritten by merge:
public/creado_en_github.txt
Please commit your changes or stash them before you merge.
Aborting
Updating 6dc3c71..7ea1fed
Cuando ocurre un conflicto de fusión, Git agrega marcas especiales en el archivo conflictivo para que podamos ver ambas versiones y decidir qué cambios conservar. Para resolverlo, desde Git Bash, se abre el editor "vim", que es el editor predeterminado en muchos entornos Linux y que Git instala en Windows para que lo uses desde GitBash. Aunque es un editor poco intuitivo y amigable, tiene una gran base de usuarios, por lo que es útil estar algo familiarizado con su uso.
En el ejemplo que he desarrollado para ilustrar este tutorial, tras introducir el comando:
$ vim public/creado_en_github.txt
Se abre el documento con las marcas en las líneas donde hay conflictos:
<<<<<<< HEAD
Azul es mi color favorito
=======
Mi color favorito es verde
>>>>>>> 7ea1fedf411481c8ce8bc46749a2cd2803fc2416
Contenido editado en Github
otro cambio github
La sección entre <<<<<<< HEAD
y =======
representa los cambios locales.
La sección entre =======
y >>>>>>>
representa
las diferencias en el lado remoto
Ahora solo se trata de eliminar las líneas con los símbolos de conflicto (el número de ID también se elimina), dejando la línea que nos interese, ya sea la local, la remota o, incluso, una tercera opción en la que creemos una versión que combine ambas.
Una vez guardado el archivo, los cambios ya se reflejan si lo volvemos a abrir desde
vim,
pero puede que aún no se vean en otros editores externos como Notepad. Además, es posible
que el prompt siga mostrando
(main|MERGING)
.
Esto se debe a que, aunque el archivo ha sido modificado, los cambios aún no han sido confirmados. En este punto, el archivo se encuentra en el stage area (zona de preparación), pero todavía no ha sido incorporado al repositorio.
Para completar correctamente el proceso de fusión, debemos ejecutar los siguientes comandos:
$ git add creado_en_github.txt
$ git commit -m "Conflicto resuelto manualmente con vim"
Una vez hecho esto, el prompt volverá a mostrar (main)
y la fusión habrá
concluido con éxito.
git stash
para guardar
temporalmente los cambios locales antes de
hacer un git pull
Imagina que estás trabajando en el archivo creado_en_github.txt
y haces algunas
modificaciones:
Contenido editado localmente sin confirmar
No has hecho git commit
, pero necesitas
hacer git pull
para obtener
cambios recientes del repositorio remoto.
Si intentas hacer git pull
directamente,
podrías obtener errores o conflictos.
Para evitar eso, puedes guardar tus cambios de forma temporal con git stash
:
$ git stash
Saved working directory and index state WIP on main: ac2329f primer commit
Esto guarda tus cambios en una "caja temporal" y deja tu repositorio limpio.
Ahora puedes hacer git pull
sin
problemas:
$ git pull origin main
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
...
Fast-forward
public/creado_en_github.txt | 1 +
Una vez que has actualizado la rama, puedes recuperar tus cambios guardados con:
$ git stash pop
Auto-merging public/creado_en_github.txt
Git intenta aplicar tus cambios encima del nuevo estado actualizado. Si no hay conflictos, todo irá bien. Si los hay, los resuelves como en cualquier otro conflicto.
En resumen, lo que hacen estos 3 comandos es:
git stash
→ Guarda los cambios sin
confirmarlosgit pull origin main
→ Trae los
últimos cambios del repositorio remotogit stash pop
→ Recupera y aplica tus
cambios guardadosEste método es ideal para trabajar de forma segura sin tener que hacer commits intermedios
innecesarios solo para poder hacer un pull
.
Una forma recomendada de trabajar con un proyecto de GitHub desde Eclipse es clonar el repositorio directamente, ya que esto conserva toda la configuración de Git, incluyendo el historial de commits, ramas y la posibilidad de sincronizar cambios con el repositorio remoto.
Para clonar un repositorio desde Eclipse, seguimos estos pasos:
main
o
master
) y hacemos clic en Next.
Una vez importado, Eclipse reconocerá el repositorio como un proyecto Git, permitiéndonos usar todas las funcionalidades de control de versiones desde el IDE: hacer commits, crear ramas, hacer pull o push y ver el historial de cambios.
Consejo: En Eclipse, en la parte superior derecha, tenemos un acceso directo a las vistas predeterminadas de las distintas funcionalidades del IDE. Si no aparece el botón "Git" para acceder a las ventanas comunes de trabajo con Git, podemos activarlo desde el menú: Window > Perspective > Open Perspective > Other y luego seleccionar Git.
A partir de este momento, en el panel Project Explorer, los archivos nuevos o modificados desde el último commit aparecerán con un pequeño icono de interrogación. Esto indica que aún no han sido añadidos al control de versiones. En cambio, los archivos que ya fueron guardados en la última versión mostrarán un símbolo diferente en su icono, lo que permite identificarlos fácilmente.
Para añadir archivos al local stage, hacer commits y Push debemos ir a la ventana "Git Staging" de la perspectiva "Git".
Para añadir archivos al control de versiones, simplemente arrastramos los que queramos desde la
ventana
"Unstaged Changes" hacia "Staged Changes".
Esta acción es equivalente al comando git add
.
Una vez tengamos los archivos en "Staged Changes", podemos realizar un commit. Para ello, escribimos un mensaje descriptivo en el campo correspondiente y pulsamos el botón Commit.
Finalmente, si queremos enviar los cambios al repositorio remoto (push), hacemos clic en el botón
Push Head. Esto realiza un git push
a la rama actual, justo después
del último commit.
Eliminar archivos
Puede suceder que necesitemos eliminar un archivo del repositorio. Para hacerlo, primero lo borramos desde el Project Explorer. A continuación, en la ventana Git Staging, veremos que el archivo eliminado aparece en la sección Unstaged Changes, marcado con un icono de una pequeña X.
Luego, arrastramos el archivo a la caja de Staged Changes. Al realizar el commit y posteriormente hacer un push, el archivo quedará eliminado del repositorio a partir de ese commit.
Recuerda que, como mencionamos anteriormente, eliminar un archivo en un commit reciente no lo elimina de los commits anteriores; estos seguirán guardando el historial del archivo eliminado.