Comparando flujos de trabajo
NOTA:Este artículo es una adaptación al español de la documentación oficial de Altassian: Comparing Workflows: Centralized,Featrue Branch,Gitflow,Forking Workflow
La lista de posible flujos de trabajo puede hacer difícil saber por dónde empezar cuando implementamos git
en nuestro propio trabajo. Este artículo proporciona un punto de partida echándole un vistazo a los flujos de trabajo en equipos de empresa más comunes en git
.
A medida que leamos debemos recordar que estos flujos de trabajo están diseñados para ser más una guía que unas reglas concretas. Simplemente se quieren mostrar las cosas que es posible hacer, así que se pueden mezclar y encajar aspectos de distintos flujos de trabajo para atender a las necesidades particulares de cada cuál.
Flujo de trabajo centralizado
La transición a un sistema de control distribuido puede parecer una tarea una tarea abrumadora, pero en realidad no hay motivo para cambiar de flujo de trabajo existente para sacar ventaja de Git. Nuestro equipo de trabajo puede desarrollar proyectos de la misma manera que lo haría con Subversion.
Sin embargo, usar Git para mejorar nuestro flujo de trabajo en el desarrollo presenta algunas ventajas frente a SVN. Primero, permite que cada desarrollador tenga su propia copia local del proyecto entero. Este entorno aislado permite que cada desarrollador trabaje independientemente de todos los demás cambios que se hagan en el proyecto —se pueden añadir commits
al repositorio local y olvidarse completamente de los cambios realizados en el repositorio principal hasta que sea conveniente.
Segundo, nos da acceso al robusto modelo de ramificación y fusión de Git. A diferencia de SVN, las ramas de Git están diseñadas como un mecanismo a prueba de fallos para la integración de código y el intercambio entre repositorios.
Cómo funciona
Igual que en Subversion, el Flujo de Trabajo Centralizado usa un repositorio central que sirve como único punto-de-entrada para todos los cambios efectuados en el proyecto. En lugar de trunk
, la rama de desarrollo por defecto se llama master
y todos los cambios se consignan en ella. Este flujo de trabajo no requiere más ramas a parte del master
.
Los desarrolladores empiezan clonando el repositorio central. En sus propias copias locales del proyecto, editan los archivos deseados y se consignan con un commit
, como se haría en SVN; sin embargo, esos nuevos commit
se almacenan localmente —permanecerán completamente aislados del repositorio central. Esto permite a los desarrolladores diferir la sincronía con el flujo principal hasta que se encuentren en un punto de giro.
Para publicar los cambios en el proyecto oficial, los desarrolladores realizan un push
desde la rama master
de su copia local al repositorio central. Este es el equivalente al commit
de SVN, salvo que en este caso se añaden todos los commit
locales que no estan todavía en la rama master
central.
Manejando conflictos
El repositorio central representa el proyecto oficial, por lo que su historia de commits debería ser sagrada e inmutable. Si el historial de commit
locales diverge del repositorio central, Git rechazará los cambios, puesto que sobreescribiría los commit
oficiales.
Antes de que el desarrollador pueda publicar su aportación, deberá ir a buscar (fetch
) los commit
actualizados del repositorio central y basar los cambios de su aportación en ellos (rebase
). Es como decir: “quiero añadir mis cambios a lo que se ha hecho hasta ahora”. El resultado de esto es un historial perfectamente lineal, precisamente como son los flujos de trabajo tradicionales de SVN.
Si los cambios locales entran directamente en conflicto con el historial de commit
de la versión principal, Git pausará el proceso de rebase
, dándonos la oportunidad de resolver esos confilictos a mano. Lo bueno de Git es que usa los mismos comandos git status
y git add
tanto para generar un commit
como para solucionar conflictos en la fusión de dos ramas. Esto facilita el manejo de sus propias fusiones a los nuevos desarrolladores. Además, si se meten en líos, Git hace muy fácil abortar el proceso de rebase
entero y volver a intentarlo (o buscar ayuda).
Ejemplo
Veamos ahora paso a paso cómo colaboraría un equipo pequeño empleando este flujo de trabajo. Veremos cómo dos desarrolladores, Juan y María, pueden trabajar en partes distintas de un proyecto y compartir sus contribuciones en un repositorio central.
- Alguien inicia el repositorio central:
Primero alguien tiene que crear el repositorio central en un servidor. Si es un proyecto nuevo, podemos iniciar un repositorio vacío. De otro modo, tendremos que importar un repositorio Git o SVN.
Los repositorios centrales deberían ser siempre repositorios bare
(no deberían tener un directorio de trabajo), que se pueden crear como sigue:
ssh usuario@servidor git init --bare /ruta/al/repositorio.git
Nos aseguraremos de dar un nombre válido de usuario
a SSH, el dominio o dirección ip de nuestro servidor
, y la localización donde tenemos el repositorio en la ruta/al/repositorio
. La extensión .git
se añade por convención al nombre del repositorio para indicar que es un repositorio desnudo (bare
).
- Todo el mundo clona el repositorio central:
A continuación, cada desarrollador crea una copia local del proyecto entero. Esto se consigue con el comando
git clone
:
git clone ssh://usuario@servidor/ruta/al/repositorio.git
Cuando clonamos un repositorio, Git automáticamente añade un acceso directo bajo el nombre de origin
reenvía información al repositorio “padre”, bajo la asunción de que querremos interactuar con él tarde o temprano.
- Juan trabaja en su contribución:
En su repositorio local, Juan puede desarrollar sus contribuciones usando los comandos ordinarios de los procesos de consigna de Git:
edit
,stage
ycommit
. Para quien no esté familiarizado con el area de preparación (staging area), se trata de un modo de preparar una consigna sin tener que incluir todos los cambios en el directorio de trabajo. Esto nos permite crear consignas extremadamente concisas, incluso cuando hemos realizado muchos cambios por el camino.
git status # Ver el estado del repositorio
git add <archivo*> # Colocar en el area de preparación
# (stage) el archivo
git commit # Consignar (commit) los archivos en
# el area de preparación
Recordemos que dado que estos comandos crean consignas locales, Juan puede repetir el proceso tantas veces como quiera sin preocuparse de lo que ocurra en el repositorio central. Esto puede resultar sumamente útil para cambios sustanciales que necesitan dividirse en otros más simples, fragmentos más atómicos.
-
María trabaja en su contribución:
Mientras tanto, María está trabajando en sus propias contribuciones en su repositorio local usando el mismo proceso de edición/preparación/consigna. Al igual que Juan, no tiene que preocuparse por lo que ocurra en el repositorio central, y realmente no le importa lo que esté haciendo Juan en su repositorio local, dado que todos los repositorios privados son privados.
-
Juan publica sus cambios:
Una vez Juan termina sus mejoras, debería publicar sus
commit
locales en el repositorio central, de modo que los miembros del equipo tengan acceso a ellos. Puede hacerlo con el comandogit push
, como se muestra a continuación:
git push origin master
Recordemos que ese origin
no es otra cosa que la conexión remota al repositorio central que Git creó cuando Juan lo clonó. El master
es el argumento que le dice a Git que debe intentar que la rama master
de origin
adopte la forma de la rama master
local. Dado que el repositorio central no se ha actualizado desde que Juan lo clonó, esto no supondrá conflicto alguno, y el push
funcionará según lo esperado.
- María trata de publicar sus cambios:
Veamos ahora qué pasa si María intenta subir sus cambios al repositorio central después de que Juan haya publicado exitosamente los suyos. Puede usar exactamente el mismo comando:
git push origin master
Pero, dado que su historial local diverge del repositorio central, Git rechazará la petición con un mensaje de error bastante verborreico:
error: failed to push some refs to 'ruta/al/repositorio.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Esto evita que María sobreescriba algún commit
oficial. Necesita bajarse (pull
) las actualizaciones de Juan a su repositorio, integrarlas con sus cambios locales, e intentarlo de nuevo.
- María rebasa sus cambios sobre los
commit
(s) de Juan:María puede usar
git pull
para incorporar los cambios del repositorio central a su repositorio local. Este comando es parecido al comandosvn update
—descarga todo elcommit
principal en el repositorio de María y trata de intagrarlo con suscommit
locales:
git pull --rebase origin master
La opción --rebase
le dice a Git que mueva todos los commits de María al final de la rama master
después de sincronizarlos con los cambios del repositorio central, como se muestra a continuación:
El pull
aún funcionará si olvidamos esta opción, pero entonces incordiaremos con un merge commit
superfluo cada vez que alguien necesite sincronizar con el repositorio central. Para este flujo de trabajo, es siempre mejor rebase
en lugar de generar un merge commit
.
- María resuelve sus conflictos en el
merge
:Rebasar trabajos transfiriendo cada
commit
local a la ramamaster
actualizada, uno a uno. Esto significa que tomamos los conflictos de la fusión conmerge
con un enfoquecommit
acommit
, en lugar de tratar de solventarlo todo con unmerge commit
masivo. Esto mantiene nuestroscommit
lo más focalizados que sea posible, y permite un historial limpio de versiones del proyecto. Uno a uno, es más fácil figurarse dónde se han introducido bugs y, si es preciso, deshacer los cambios con el impacto mínimo sobre el resto del proyecto.
Si María y Juan trabajan en cambios no relacionados, es improbable que el proceso de rebase
genere conflictos. Pero si lo hace, Git detendrá el proceso en el commit
actual y devolverá el siguiente mensaje, junto con algunas instrucciones relevantes:
CONFLICT (content): Merge conflict in <archivo>
Lo bueno de Git es que cualquiera puede resolver sus propios conflictos al combinar repositorios. En nuestro ejemplo, María simplemente ejecutaría git status
para ver dónde está el problema. Los archivos en conflicto aparecerán en la sección de Unmerged paths:
# Unmerged paths:
# (use "git reset HEAD <archivo>..." to unstage)
# (use "git add/rm <archivo>..." as appropriate to mark resolution)
#
# both modified: <archivo>
Entonces, editará los archivos a su gusto. Una vez esté conforme con el resultado, puede colocar los filtros en la zona de pereparación (stage) como hace habitualmente y dejar que git rebase
haga el resto.
git add <archivo>
git rebase --continue
Y eso es todo lo que hay que hacer. Git se moverá al siguiente commit
y repetirá el proceso para todos los otros commit
que generen conflictos.
Si llegados a este punto nos damos cuenta de que no tenemos ni idea de lo que estamos haciendo, que no cunda el pánico. Sólo tenemos que ejecutar el siguiente comando y volveremos al punto de partida antes de que ejecutáramos git pull --rebase
:
git rebase --abort
- María publica sus cambios con éxito:
Después de haber sincronizado su repositorio con el central, María podrá publicar sus cambios exitosamente:
git push origin master