elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Curso de javascript por TickTack


+  Foro de elhacker.net
|-+  Programación
| |-+  Programación General
| | |-+  Repositorios remotos y flujos de colaboración (cuarta parte)
0 Usuarios y 1 Visitante están viendo este tema.
Páginas: [1] Ir Abajo Respuesta Imprimir
Autor Tema: Repositorios remotos y flujos de colaboración (cuarta parte)  (Leído 3,978 veces)
MinusFour
Moderador Global
***
Desconectado Desconectado

Mensajes: 5.529


I'm fourth.


Ver Perfil WWW
Repositorios remotos y flujos de colaboración (cuarta parte)
« en: 23 Enero 2021, 18:48 pm »

Indice


Prefacio

En este tema veremos como trabajar con repositorios remotos y estrategias para poder colaborar con otras personas usando  estos repositorios remotos. Es una continuación de este tema.

Como recordatorio, esta es una guía informal a git.

Repositorios remotos

Un repositorio remoto es básicamente cualquier otro repositorio que no sea ese mismo repositorio. Un repositorio B es considerado un remoto de A así como A puede ser considerado un repositorio remoto de B. En pocas palabras, el que sea remoto simplemente nos indica una relación entre estos repositorios. Dependiendo de los permisos de acceso al repositorio remoto, uno podrá agregar u obtener información sobre el repositorio.

git necesita poder acceder al repositorio remoto de alguna manera. Los métodos de acceso varían, podemos acceder al repositorio remoto a través de nuestro sistema de archivos, usando SSH, HTTP(S) o el protocolo de git. Cada uno tiene sus ventajas y desventajas. Los métodos de acceso más populares hoy en día son SSH y HTTPS. Para efectos de esta guía usaremos nuestro sistema de archivos y sus permisos correspondientes en Linux porque explicar como montar tanto los servidores SSH como HTTPS merecen su propia guía aparte.

Creación de un repositorio remoto

Antes de empezar repasemos el estado actual del repositorio en nuestro sistema de archivos:

Código:
 code
└── proyecto
    ├── barfoo.txt
    ├── bar.txt
    ├── baz.txt
    ├── foobar.txt
    ├── foobaz.txt
    ├── foo.txt
    ├── .git
    ├── importante.txt
    ├── prueba.txt
    └── README.md

Dentro de mi carpeta code tengo la carpeta proyecto que alberga nuestro repositorio. Mi carpeta code está dentro de mi HOME (~). En este ejercicio queremos crear una copia de nuestro repositorio en proyecto a una carpeta que también residirá en code (una carpeta al mismo nivel que proyecto).

Para esto, podemos simplemente copiar nuestro repositorio. Desde una interfaz gráfica es simplemente copiar y pegar. En la terminal podemos usar cp. Sin embargo, también tenemos otra alternativa: git clone.

El comando, es bastante sencillo:

Código
  1. git clone proyecto proyecto-clon

Esto nos entregaría una copia exacta de nuestro repositorio dentro de la carpeta proyecto-clon y añadiría el repositorio proyecto como un repositorio remoto de proyecto-clon.

Sin embargo, hay algunas desventajas de realizar esta operación. Si bien es cierto que puedes interactuar con cualquier repositorio externo, realizar operaciones sobre el repositorio como está puede resultar desastroso. Tanto proyecto como proyecto-clon contienen un directorio de trabajo. Realizar operaciones de escritura sobre la misma rama tanto de proyecto a proyecto-clon puede resultar en un estado ambiguo.

Para esto tenemos los repositorios "simples". Un repositorio simple no contiene un directorio de trabajo, simplemente contiene la estructura del directorio .git. Para crear un repositorio simple podemos hacer:

Código
  1. git init --bare

Y si tenemos un repositorio existente podemos también clonarlo con git clone y convertirlo a un repositorio simple:

Código
  1. git clone --bare proyecto proyecto-simple

En este caso, voy a crear un nuevo repositorio vacío pero será un repositorio simple.



Este repositorio no contiene ningún dato de momento. Lo que haremos ahora es agregar un nuevo remoto a nuestro repositorio en la carpeta proyecto. Para esto vamos a usar los comandos bajo git remote.

El comando para agregar un remoto es:

Código
  1. git remote add nombre url

Por convención, el remoto con el cual interactuamos más lo llamamos origin (usualmente un repositorio central). Pero esta es una convención que no está enforzada por git y le podemos dar cualquier otro nombre. Para este ejemplo yo le daré el nombre de origin.



Podemos listar los repositorios remotos con el comando:

Código
  1. git remote

Y si queremos ver mas información acerca de los remotos podemos usar:

Código
  1. git remote -v



Enviando información al repositorio remoto

Nuestro interés es tener una copia de nuestro repositorio en este nuevo repositorio remoto así que tenemos que enviar nuestros commits a este repositorio. Para hacer esto tenemos que usar el comando git push.

El comando se usa así:

Código
  1. git push repositorio refspec

Donde respositorio es el nombre dado al repositorio remoto y refspec es lo que queremos actualizar/agregar.

refspect sigue un formato en especifico, fuente:destino. Donde fuente puede ser cualquier objeto o rama, mientras que el destino tiene que ser una referencia (rama o etiqueta). Se puede especificar solo la fuente y git buscará que referencia actualizar. De modo que podemos especificar solo master, git entenderá que quieres hacer master:master. Para ser más específicos el refspec completo sería refs/heads/master:refs/heads/master pero git te permite abreviarlo. La ventaja de usar un refspec completo es que podríamos especificar otros lugares fuera de los convencionales para almacenar información (por ejemplo, una llave pública GPG). Pero este ya es un caso más avanzado y realmente no es un caso de uso habitual.

Por ahora, me interesa copiar solo master de mi repositorio en proyecto a proyecto-simple.



git ha comprimido nuestros objetos y los ha enviado a nuestro otro repositorio. Al final nos dice la cantidad de objetos agregados y también nos dice que hemos creado una nueva rama master sobre este repositorio.

Podemos verificarlo en el otro repositorio:



Regresamos al repositorio original y revisemos nuestras ramas que tenemos.



En esta ocasión he usado el argumento -a para mostrar todas las ramas existentes. Y podemos ver las ramas que habíamos creado anteriormente, la rama en la que estamos ahora mismo en verde y una nueva rama que no habíamos visto antes en rojo, remotes/origin/master.

Esta rama es considerada como una rama remota. Esta rama mantiene el estado de una rama en el repositorio remoto. Para mostrar solo las ramas remotas podemos usar:

Código
  1. git branch -r

Cuando hacemos uso de git log este también nos dice en rojo acerca de la rama remota.



Aquí podemos ver que la rama master del repositorio origin esta apuntando al mismo commit que nuestra rama local master que también es HEAD (la rama en la que estamos trabajando). En pocas palabras, nuestras ramas están al corriente.

Rastreando ramas remotas

Algo que podemos hacer también es configurar nuestras ramas para que rastreen el estado de una rama en otro repositorio remoto. Esto tiene la ventaja de que git status y git branch nos pueden decir más acerca del estado de nuestras ramas con respecto a sus contrapartes en los repositorios remotos.

El comando para hacer esto es:

Código
  1. git branch -u remoto/rama rama

Donde remoto/rama es el nombre de la rama remota y la rama es la rama local.

Vamos a decirle a git que rastree los cambios en la rama remota origin/master sobre nuestra rama local master:



Ahora, si usamos git status:



Tenemos una nueva linea que nos dice que nuestra rama está al corriente con la rama que estamos rastreando. Cuando hagamos un commit nuevo sobre nuestra rama local este nos dirá que estamos adelante de la rama remota por un commit. Cuando el repositorio remoto este adelante de nuestra rama local nos dirá que nuestra rama local está atrasada por un número de commits.

Generalmente, este comando no es utilizado mucho. La razón de esto es porque normalmente configuramos está opción cuando creamos la rama. En este caso, cuando hicimos git push este creo la rama remota pero no le hemos indicado como rastrear la nueva rama que creamos. Es preferible utilizar el argumento -u a la hora de hacer push sobre una rama nueva.

Código
  1. git push -u repositorio refspec

De esta manera podemos configurar la rama que hemos enviado para que rastree la nueva rama creada por git push. No habrá necesidad de usar git branch para configurar la rama.

Recibiendo información de un repositorio remoto

No solo es importante poder enviar nuestra información sino también recibir información de otros posibles repositorios.

Para este ejemplo, vamos a clonar nuestro repositorio simple. Es muy común utilizar un repositorio simple como punto de colaboración entre repositorios. Es por eso que estamos clonando del repositorio simple, porque es lo más habitual. Más acerca de esto adelante.



Aquí he clonado el repositorio de la carpeta proyecto-simple a una nueva carpeta proyecto-clon. Este es un repositorio con un directorio de trabajo y al hacer git clone también ha agregado el repositorio remoto origin el cual apunta a proyecto-simple. También ha configurado la rama master para que rastree los cambios sobre origin/master.

En pocas palabras ha hecho prácticamente todo lo que nosotros hicimos en nuestra sección anterior. De modo que este repositorio es muy parecido al que tenemos en la carpeta proyecto. Con la excepción de que nuestro repositorio en proyecto contiene otras ramas que no hemos enviado a proyecto-simple. Esas ramas fueron previstas para ser locales y no tenemos intención de publicarlas a proyecto-simple. No son importantes pero es importante hacer la distinción del estado de estos dos repositorios.

En la imagen anterior también podemos ver una nueva rama remota, origin/HEAD. Es muy parecido al HEAD en nuestro repositorio excepto que los repositorios simples no tienen un directorio de trabajo, así que HEAD tiene otro uso para ellos. origin/HEAD representa la rama por defecto que utiliza un repositorio después de clonar. Al momento de clonar el repositorio simple, nuestro HEAD toma el valor de origin/HEAD. En pocas palabras, la rama que observamos por primera vez después de clonar es la estipulada por origin/HEAD. Esto es útil si la rama principal con la que trabajamos no es master. Existen formas de actualizar origin/HEAD de forma que personas que usen git clone sobre el repositorio tendrán esta nueva rama como la primera rama que ven.

Por lo pronto, lo que queremos hacer es hacer un commit desde nuestro clon y enviarlo a nuestro repositorio simple.



Aquí he agregado un commit con el mensaje: "Agrega archivo barbaz". Nuestro git log nos dice que nuestro master (que también es HEAD) está un commit adelante de origin/master y origin/HEAD. Nuestro git status nos lo repite mucho más explicitamente: Your branch is ahead of 'origin/master' by 1 commit.

Ahora lo enviare al repositorio simple:



Y ahora el commit está en nuestro repositorio simple pero no en el repositorio original en la carpeta proyecto. Lo que hare ahora es cambiarme al repositorio original y revisare el estado de mi repositorio.



Mi repositorio dice que está a la par con origin/master pero en mis registros no aparece el commit que agrega barbaz. ¿Que es lo que está pasando? Pues git no ha revisado el estado de la rama remota. La última vez que git reviso esa la rama en el repositorio remoto fue antes de que hiciera el commit. Necesito revisar nuevamente por posibles cambios en el repositorio remoto. Para esto utilizare git fetch.

Código
  1. git fetch repositorio refspec

El refspec es realmente lo mismo que con git push. Excepto que en está ocasión los roles están inversos. Recordamos que el formato del refspec es fuente:destino. En esta ocasión fuente es la rama del repositorio remoto que queremos obtener y destino es una rama remota local adicional que podemos crear. Personalmente, yo nunca utilizo un refspec completo aquí. Lo único que tenemos que decirle es que nos entregue una rama en especifico.



Aquí git fetch a obtenido el commit y git status nos dice que la rama master está detrás de origin/master por un commit. Es por eso que origin/master no aparece en el git log ya que este está mostrando todos los commits en master y el commit que está en origin/master no forma parte de la rama todavía. Si queremos ver las ramas remotas en nuestro git log podemos usar el argumento --remotes:



También podríamos simplemente agregado origin/master al final del comando.

Ahora, como he mencionado anteriormente, el nuevo commit no forma parte de la rama master dentro del repositorio en la carpeta proyecto. Tenemos dos ramas diferentes, ¿Cómo puedo incluir los cambios de la rama origin/master sobre la rama master? Pues eso es muy fácil, con git merge.



Y ha hecho un "Fast Forward Merge" lo que significa que solo ha actualizado la rama master. Para incluir los cambios también podemos usar git rebase pero en este caso, la tarea era para git merge.

Finalmente tenemos git pull.

Código
  1. git pull repositorio refspec

El comando es muy similar a git fetch. git pull haria lo mismo que git fetch solo que también incluirá los cambios sobre la rama en la que estamos trabajando. Es así de sencillo. Es un comando que lo hace todo, obtiene los cambios del repositorio remoto y los incluye en nuestra rama. Tampoco es muy habitual usar un refspec completo aquí pero lo menciono para ser completo.

Código
  1. git pull origin master

Haría git fetch origin master seguido por git merge origin/master sobre la rama master.

También tenemos la opción de usar:

Código
  1. git pull --rebase origin master

El cual hace un rebase antes de hacer el merge.

Crear y eliminar una rama en el repositorio remoto

Bueno, ya sabemos como crear una rama porque lo hemos visto anteriormente pero si aún no hemos caido en cuenta de como hacerlo:

Código
  1. git push repositorio rama

La rama tiene que existir localmente así que tendríamos que crearla primero si no existe:

Código
  1. git branch rama

Para eliminar la rama en el repositorio remoto:

Código
  1. git push --delete repositorio rama

Si alguién ha borrado la rama remota desde otro repositorio y seguimos teniendo la rama remota (e.g. origin/master) podemos borrar la rama remota con:

Código
  1. git branch -d -r repositorio/rama

Probablemente quieras eliminar la rama local si buscas remover la rama remota, así que como recordatorio:

Código
  1. git branch -d rama

Una nota acerca del estado de los repositorios

Por lo general, la manera en la que actualizamos una rama remota es publicando descendientes directos del commit al cual apunta la rama remota. Esto quiere decir que si en nuestro repositorio simple tenemos:

Código:
A -> B -> C -> D -> E (master)

Y en nuestro repositorio local tenemos:

Código:
A -> B -> C -> D -> F (master)

git no nos dejara actualizar la rama remota porque F no es un descendiente directo de E sino de D.

¿Porqué ocurriría esto? Podría ser por varias razones. Por ejemplo, podría ser que el commit E fue hecho por otro usuario y subido al repositorio simple antes de que pudieramos haber hecho git push con el commit F. Podría ser también que nosotros teníamos E en nuestro repositorio local pero hemos movido la cabeza de master a D y posteriormente creado nuevos commits sobre master.

La regla de oro de git es no reescribir la historia de una rama publicada. Esto quiere decir que si E ha sido publicado en la rama master cualquier cambio sobre master debe incluir a E también, para siempre. Publicar nuestros commits através de git push debe considerarse como una acción irreversible. Si has públicado un commit, este commit debería vivir en este repositorio público para siempre.

La razón de esto es muy sencillo, si alguien empezará a trabajar con la rama públicada y añadiera unos commits:

Código:
A -> B -> C -> D -> E -> G -> H -> I

G, H e I no serían parte de nuestra rama. Porque nosotros divergimos de D. La única forma de rescatar estos otros commits sería hacer un git rebase de la rama master en el repositorio remoto sobre nuestra rama master en nuestro repositorio local.

Ahora imagina que hay otros usuarios que también tienen estos commits en su rama y que también han añadido más commits. Tendrías que esperar a que ese usuario publique sus commits porque el seguiría trabajando con alguno de los commits publicados. Recordemos que git rebase no elimina commits, sino que escribe nuevos commits. Imagina ahora que pasaría si hubiera 5 o más usuarios trabajando con multiples commits.

Sin embargo, existe la opción para forzar a actualizar la cabeza de una rama a un commit que no es decedendiente directo de la punta actual. El argumento que se usa es --force.

Código
  1. git push --force repositorio refspec

En mi opinión esta es una herramienta que no debería utilizarse muy a menudo pero existen casos legitimos para usar --force. En general, --force debe usarse con mucho cuidado. Si la rama que queremos reescribir es una rama pública, cersiorarse de que nadie más este trabajando sobre ella en ese momento. Si alguien está trabajando sobre ella notificarle que la rama va a cambiar y que es probable que tenga que actualizar su repositorio.

Los culpables más comúnes de porque necesitamos usar --force:

git rebase: El comando tiene que buscar un commit en común entre las dos ramas y generalmente implica retroceder en la historia de la rama. Esto significa que la nueva rama no incluirá un número de commits que pudieron haber sido publicados anteriormente. La rama que se busca desplazar no debería recrear commits que ya han sido publicados anteriormente.

git reset: Si bien, git reset puede desplazar las cabezas hacía adelante, su caso más común es desplazar las cabezas de las ramas hacía un punto atrás en el historial. La regla es no hacer git reset sobre una rama de forma que commits publicados ya no están en su procedencia.

git amend: El comando revierte un commit desplazando la cabeza de la rama a su padre por lo que los siguientes commits creados ya no forman parte de la procedencia del commit al cual aplicamos el git ammend. La regla es no utilizar git amend sobre un commit publicado.

Como última advertencia: No busques "cambiar" commits públicos en los cuales otras personas pueden estar trabajando. No existe tal cosa como "cambiar" commits, los tienes que recrear. Si has llegado a usar --force debio haber sido por una muy buena razón (y por lo general, no es esta).

Resumiendo los comandos

git fetch: Obtiene los cambios nuevos de repositorios remotos.

git pull: Obtiene los cambios nuevos de repositorios remotos y los incluye en nuestras ramas locales.

git push: Envia nuestros cambios a otros repositorios remotos.

git clone: Clona nuestros repositorios y configura los clones para trabajar con los repositorios fuente (el repositorio a clonar) como repositorio remotos.

El argumento --bare para git clone y git init para crear un repositorio en su forma simple (sin directorio de trabajo).

El argumento -u en git push y git branch para configurar las ramas remotas que las ramas locales deben rastrear por cambios.

El argumento --force en git push nos permite enviar nuestras ramas y forzarlas a actualizarlas si es necesario (cuando los nuevos commits no sean ancestros directos del último commit en la rama).

Flujos de Colaboración

A continuación voy a mencionar las practicas mas comúnes (en mi opinión) para colaborar con varios individuos usando git.

Equipos/Proyectos Pequeños

Repo Central una sola rama

La forma más común (y más sencilla) para trabajar en proyectos pequeños consiste en mantener un repositorio central (un repositorio simple con --bare) en el cual todos los miembros involucrados tienen permisos de lectura y de escritura. Todos los cambios terminan integrados en una sola rama a través de todos los repositorios (cada usuario tiene su repositorio local). Su objetivo es el de tener un historial lineal.




Flujo de trabajo ideal para este escenario:

Un usuario nunca hace git push sin antes haber hecho git pull. Esto quiere decir que una vez que el usuario está listo para enviar su información, necesita primero revisar que nadie haya publicado nada en el repositorio remoto. No hay problema si alguien intenta hacer git push sin antes hacer git pull porque git avisará que no hay forma de incluir los cambios. git advertirá que la única forma posible de hacerlo es através del argumento --force. Es importante NO USAR --force en este caso sino hacer git pull. En esta situación también se recomienda utilizar git pull --rebase pero dependerá del estado de la rama remota.

En general, cuando vamos a enviar un nuevo commit al repositorio central nos encontraremos con dos situaciones. Puede ser que nadie haya subido nada al repositorio central y nuestro commit se envia sin más o puede ser que alguien haya puesto uno o varios commits en el repositorio central.

Si alguien ha compartido más commits desde la última vez que revisamos la rama remota esto significa que nuestro trabajo diverge del trabajo de los demás. Básicamente tenemos está situación:


Ambas ramas pueden tener uno o más commits divergentes, pero solo he dibujado un commit para ejemplificar la divergencia.

Este es el resultado de hacer git fetch sobre un repositorio remoto y la rama tiene un commit que diverge. Lo normal después de hacer un git fetch es hacer git merge (otra vez, esto es lo que hace git pull practicamente).

En esta situación git merge hará un "Three Way Merge" y creara un "merge commit". En pocas palabras esto quiere decir que para incorporar nuestros cambios tengo que crear un nuevo commit.


Después de esto podemos hacer git push y estaríamos enviando no solo el commit con nuestros cambios, sino también el merge commit. Al hacer esto tanto master como origin/master se volverían el mismo.

Ahora imagina que el usuario que puso código divergente tiene código que todavía no publica al repositorio central mientras que tu ya has publicado tu código con el merge commit. El repositorio del usuario antes de hacer el git pull:


Y después de hacer el git pull (git fetch y git merge):


Y si tu usuario ha hecho un commit antes de que el otro usuario haya hecho git push al repositorio.


Muy bonito para crear un helice DNA en 2D pero esto es git. Este es el problema de los merge commits. Creamos un commit extra, tenemos que unir ramas y al final no tenemos ni idea del historial. Se explico brevemente en el tema anterior pero vuelvo a repetirlo porque es de extrema importancia entender que herramientas utilizar para evitar tener miles de merge commits inútiles.

Entonces, ¿Que podemos hacer?

Volvamos al punto inicial en el que recogimos la información del repositorio remoto con git fetch.


Ahora si en lugar de hacer un git merge hacemos un git rebase:


En lugar de crear un merge commit aquí se ha desplazado el commit divergente. Como se explico git rebase en el tema anterior, este ha guardado el commit, ha cambiado la rama master para que esta y origin/master apunten al mismo commit y finalmente ha aplicado de nuevo el commit que ha guardado sobre la rama master. El resultado es como se aprecia en la gráfica. Hacer push ahora es muy simple.

El segundo usuario que tenía un commit sin públicar tendría un estado así si hubiera hecho git fetch en lugar de git pull:


Y después hecho un git rebase:


Y si tu usuario hubiese creado un commit antes de que el otro usuario haya publicado el suyo y haces git fetch:


Y finalmente git rebase:


Y como pueden ver, nuestro historial ahora es lineal y no está plagado de merge commits. La rama por consecuencia se puede leer fácilmente. No hace falta hacer git fetch seguido por git rebase. He puesto ese ejemplo para que puedan visualizar como se ven las ramas remotas en cada paso. Pueden utilizar git pull --rebase que usara git rebase en lugar de git merge.

Así que el flujo para publicar info es bastante sencillo:

Código
  1. git pull --rebase origin master
  2. git push origin master

Si han configurado su rama master para que rastree la rama remota origin/master entonces esto es todavía más sencillo:

Código
  1. git pull --rebase
  2. git push

Inclusive pueden configurar la rama para que siempre haga git rebase:

Código
  1. git config branch.master.rebase true

Y ahora pueden hacer:

Código
  1. git pull
  2. git push

Y si solo quieren incorporar los cambios de otros en sus repositorios locales:

Código
  1. git pull

Este es de lejos, la manera mas sencilla de trabajar. Prácticamente no hay ninguna otra rama fuera de master y lo único que tienen que hacer es git commit, git pull y git push. Si han clonado el repositorio ni siquiera tienen que configurar las ramas remotas ni los repositorios remotos.

Pero tiene sus desventajas. En primer lugar, la rama master se vuelve practicamente un entorno de trabajo en los que todos ponen su trabajo. Imagina que esto es el equivalente de N número de personas trabajando sobre un mismo escritorio y sobre los mismos documentos que estás trabajando. Tu quieres hacer algo, la otra persona quiere hacer otra cosa distinta, uno está deshaciendo lo que el otro hace y el otro deshaciendo lo que tu haces. Se necesita coordinar con el equipo para poder trabajar correctamente vaya. Quizás no haya tanto problema con dos personas o tres pero con grupos arriba de cinco se pueden poner las cosas díficiles.

Todos los individuos también tienen acceso de escritura sobre el repositorio para colocar sus cambios. Esto significa que por cada usuario que tenga acceso al repositorio se incrementa la posibilidad de equivocarse y provocar daños al repositorio. Imagina que uno de los usuarios se le ocurre subir sus cambios con un montón de commits que no hacen nada. Por ejemplo, modifica un archivo y hace commit. Se da cuenta que ese es un cambio malo, así que modifica de nuevo el archivo, lo deja en su estado inicial y vuelve hacer commit. Ahora hace git push y ha dejado 2 commits que practicamente no hacen nada. Este es un caso ligero pero habrá algún usuario que se le ocurra subir un desastre de historial, como el que habíamos comentado anteriormente. Inclusive pueden haber usuarios que hagan git push --force y no avisen a los demás usuarios.

Repo Central una sola rama por usuario

Este es un flujo de trabajo un poco más elaborado que personalmente no es de mi agrado. En este flujo de trabajo existe una rama master en la que eventualmente se termina incluyendo todos los cambios justo como en el ejemplo anterior. La gran diferencia es que también existe una rama para cada usuario diferente en el repositorio central. De esta manera cada usuario publica sus cambios sobre una misma rama. No hay necesidad de hacer git pull sobre esta rama si queremos enviar nuestros cambios porque en teoría solo el usuario puede escribir sobre esta rama. Así que solo es cuestión de hacer git push.

Cada usuario prepara su rama apartir de master:

Código
  1. git checkout -b miusuario master

Y realiza su trabajo en esta rama. Una vez que el usuario haya alcanzado una meta (termino una nueva abilidad del programa o similar) el usuario puede realizar un par de cosas:

1) Solicitar a los demas usuarios permiso para incorporar sus cambios sobre la rama master.
2) Incorporar sus cambios sin pedir permiso.

Para la primera opción usualmente se realiza una acción conocida como "Pull Request" (PR). Un PR es simplemente una petición para incorporar los cambios en una determinada rama (y en un determinado repositorio) sobre una rama en el repositorio. En este caso, el usuario solicitaría que se incluyera los cambios en la rama miusuario sobre la rama master.

Através del PR los demas miembros del proyecto pueden leer los cambios propuestos por el usuario y dar sus opiniones antes de incluir los cambios. Los PRs son practicamente externos a git (aunque git permite enviar parches por email) y dependerá de la accesibilidad de los cambios que los otros usuarios tengan. En esta ocasión, los demás usuarios pueden ver los cambios de dicho usuario publicado sobre la rama de este.

Para revisar la rama de otro usuario por primera vez:

Código
  1. git fetch origin miusuario
  2. git checkout miusuario

Una vez que hayamos terminado de revisar la rama y queremos incluir los cambios:

Código
  1. git checkout master
  2. # Si la rama master no esta actualizada
  3. # git pull
  4. git merge miusuario
  5. git push

Es importante que la rama master este al corriente tambien:

Si queremos actualizar la rama miusuario (para volver a revisar cambios) más adelante:

Código
  1. git checkout miusuario
  2. git pull

Los usuarios siguen usando sus ramas y cada vez que llegan a un punto en el desarrollo incluyen los cambios en la rama master. Su historial se puede ver así:


La ventaja es que cada usuario tiene su propia rama en la cual no tiene que estar coordinando con otros usuarios. Todo mundo sabe donde estan los cambios de todos los demas usuarios. También abrimos la posibilidad de poder revisar los cambios antes de que acaben integrados con los demas.

La desventaja de este flujo de trabajo es que su historial no es el de todo simple de seguir. Eventualmente los usuarios necesitarán incorporar los cambios de master dentro de sus ramas, así que todos los usuarios necesitan hacer git merge master desde sus ramas. Si los cambios en la rama no son los adecuados, es muy probable también necesiten usar git revert para crear commits que eliminen los otros commits o si es posible hacer git reset y git force --push.

Personalmente, yo no usaría este flujo de trabajo pero el siguiente es muy parecido y corrige la mayoría de las fallas.

Repo Central una sola rama por meta

Este flujo de trabajo es muy similar al anterior, la única diferencia es que en lugar de tener una rama por cada usuario, se crea una rama con el fín de realizar una acción en especifico y justo después que la rama se integra con master la rama no se útiliza nunca más.

Se crea la rama en la cual trabajar:

Código
  1. git checkout -b meta1 master

Una vez que llegamos a la meta en nuestra rama subimos el código al repositorio:

Código
  1. git push origin meta1

Y de aquí es cuando podemos evaluar los cambios o no.

Para revisar la rama se realizan los mismos procedimientos:

Código
  1. git fetch origin meta1
  2. git checkout meta1

Si se acuerda que los cambios deben ser integrados entonces algún usuario necesita obtener la rama e integrarla a master:

Código
  1. git checkout master
  2. # Si la rama master está desactualizada
  3. # git pull
  4. git merge meta1
  5. git push

La única diferencia aquí es que una vez que la rama haya sido integrada, no la volveremos a usar. Si queremos seguir trabajando en el repositorio tenemos que crear una nueva rama (empezando de master).

Entonces si el mismo usuario quiere seguir colaborando necesita hacer:

Código
  1. git checkout master
  2. git pull
  3. git checkout -b meta2

Ahora, tenemos algunas posibilidades aquí para manejara el historial de nuestro repositorio. Por un lado podemos tener un historial lineal, sin bifurcaciones. Esto es muy sencillo. Si las ramas empiezan y terminan una después de la otra cada merge que hagamos hará un "Fast-Forward Merge".

Es decir, si tenemos esto:


Hacer git merge haría esto:


Las dos ramas aquí apuntan al mismo commit. Si la siguiente rama divergiera de este punto el siguiente git merge tambien haría un "Fast Foward Merge". Sin embargo, lo que estamos diciendo aquí es que solo una rama podría salir de master lo que significa que solo se podría trabajar sobre una sola acción/meta hasta que esta termine para empezar la siguiente. Esto no es práctico. Lo ideal es poder trabajar en paralelo.

Digamos entonces que dos ramas diverge de master en el mismo commit. Si integro una de estas ramas, la siguiente rama ya no puede ser integrada con git merge si quiero tener un historial lineal. ¿Que podemos hacer? git rebase claro. Los pasos a seguir serian estos:

Código
  1. git rebase master meta2
  2. git checkout master
  3. git merge meta2
  4. git push

Es importante tener esta rama meta2 dentro de nuestro repositorio (y al corriente). Nuestra rama se moverá hacia adelante. Y así se integra cada una de las ramas que no divergan del último commit en master. De aquí podrías eliminar las ramas o la alternativa es actualizar las ramas con git push --force o borrar las ramas remotas primero. Nadie más debería tocar esta rama de aquí en adelante.

El resultado de esto sería un historial lineal.

Ahora, los historiales lineales son fáciles de leer pero no siempre es fácil encontrar donde termino el desarrollo de una rama y donde empieza el desarrollo de la siguiente rama. Aquí es donde los merge commits entran al rescate.

Las operaciones son exactamente las mismas, excepto que al hacer git merge usaremos el argumento --no-ff.

Código
  1. git merge --no-ff meta2

Esto siempre creara un merge commit. Lo que significa que siempre podemos ver donde una rama empieza y donde termina.

Digamos que tenemos un historial así:


Y digamos que nuestros compañeros han dado el visto bueno a las dos ramas. Ahora tenemos que integrarlas dentro de master.

Si integro primero la rama meta1 y después la rama meta2 sin hacer git rebase y sin usar --no-ff:


Si integro ambras ramas con --no-ff:


Y finalmente si las integro con --no-ff y git rebase:


La última opción es la que ya mencione la cual es usar git rebase solamente (historial lineal).

En mi opinión --no-ff es indispensable para este flujo de trabajo. git rebase no es estrictamente necesario a menos que la rama tenga una base muy antigua, la comprensión del historial no se ve muy afectada por una o dos ramas con la misma base.

Equipos/Proyectos Medianos-Grandes

Repositorios Intermedios

Este sigue siendo un modelo en el que hay un repositorio central. La única diferencia aquí es que uno no estaría push sobre el repositorio central. Existen variaciones pero la idea es prácticamente la misma.


Este es un flujo de trabajo muy común. La idea es que existe un repositorio central en el cual se integra todo el código. Por lo general, solo una persona se encarga de integrar el código de otros repositorios y esta persona puede o no utilizar este repositorio como su repositorio para publicar su código u optar por tener un repositorio separado (no está en la imagen).

Esto quiere decir que los miembros del proyecto tienen su propio repositorio "público" y uno local. Los demás miembros tienen permisos de lectura a estos repositorios de manera que uno no puede publicar nada en estos repositorios pero si pueden obtener cambios publicados. La excepción es generalmente el individuo que se encarga de publicar al repositorio central. Su función es de administrador básicamente y tener acceso de escritura a los repositorios de los otros miembros le permitiría manejar cambios necesarios antes de integrar el código.

Una vez que un usuario alcanza una meta en el proyecto tiene que solicitar permiso al integrador para que su código forme parte del código central. El proceso por lo general involucra a los otros miembros del equipo y estos también pueden decir que cambios son necesarios o aceptables. Una vez que se determina la validez de los cambios propuestos, el integrador debe tomar los cambios e incluirlos en una rama publicada en el repositorio central.

Su uso normal es generalmente así:

1. El usuario, que se encarga de mantener el repositorio central, publica su repositorio en algún lado para que todos tengan acceso de lectura.
2. Cada usuario crea una copia del repositorio central y también publican este repositorio de manera que los demás también tengan acceso de lectura. Acceso de escritura para el usuario que integra los cambios es opcional pero recomendado.
3. Cada usuario clona la copia del repositorio central.
4. Cada usuario trabaja sobre su repositorio local.
5. Cuando un usuario da por terminado su trabajo, lo hace publico a través de la copia del repositorio central.
6. El usuario notifica al usuario integrador que sus cambios están listos para integrarse.
7. El usuario integrador descarga los cambios de la copia del repositorio central y los publica en una rama de su repositorio central.
8. El usuario descarga los cambios realizados sobre el repositorio central y los publica en la copia de su repositorio público (eventualmente).

Un proceso un tanto elaborado pero también muy sencillo. El verdadero problema de esto es encontrar un lugar donde poner tantos repositorios. Aquí es cuando la gente acude a servicios como Bitbucket, Github o Gitlab. Es muy probable que ya hayas escuchado de estas copias de los repositorios bajo otro nombre: "Forks" y estos servicios son reconocidos por preferir este modelo bajo "forks".

Ahora, para un determinado usuario, su proceso no es tan diferente como uno pudiera pensar.

Mi recomendación es tener dos repositorios remotos configurados. Por convención, "origin" es nuestra copia del repositorio (de aquí en adelante le llamare fork). Cuando hacemos git clone sobre enuestro fork, el remoto de origin se configura automaticamente. El segundo remoto que debemos agregar es el repositorio central. Al cual generalmente llamo upstream:

Código
  1. git remote add upstream urlalrepositorioremoto

Ahora, este flujo de trabajo favorece drásticamente al flujo de trabajo anterior. Se podría decir que este es todavía un paso extra sobre el flujo de trabajo anterior pero no es exactamente necesario.

Empezaremos trabajando sobre una rama con su punto de origen en master:

Código
  1. git checkout -b meta1 master

Una vez terminado con nuestra rama haremos push:

Código
  1. git push -u origin meta1

Es posible continuar trabajando sobre esta rama una vez publicada. Sin embargo, al momento de avisar al usuario integrador que el trabajo esta completo sería ideal no tocar la rama más o avisar al usuario integrador que el trabajo está incompleto.

Una vez que los miembros del proyecto y el usuario integrador validen los cambios le tocara al usuario integrador recoger estos datos de tu "fork" e integrarlos. Los servicios como Github, Bitbucket y Gitlab hacen está tarea trivial puesto que solo necesitan hacer uno o dos clicks para integrar tu código en una rama.

En el caso de que usuario integrador quiera usar la terminal este tendrá que agregar tu fork como repositorio remoto de su copia en local.

Código
  1. git remote add user1 urlaforkdeusuario1

Una vez agregado el repositorio remoto tendrá que obtener los cambios de la rama en especifico.

Código
  1. git fetch user1 meta1

Seguido por el git merge:

Código
  1. git checkout master
  2. git merge user1/meta1

Para finalmente colocar los cambios en el repositorio central con:

Código
  1. git push origin master
  2. # origin para el usuario integrador es el repositorio central

Las mismas estrategias para mantener el historial aplican aquí. El usuario integrador puede usar git rebase antes de hacer el git merge:

Código
  1. git checkout -b meta1 user1/meta1
  2. git rebase master

Y por supuesto usar el argumento --no-ff para git merge:

Código
  1. git merge --no-ff meta1
  2. # Sin rebase no necesitamos una rama local podemos usar la rama remota
  3. # git merge --no-ff user1/meta1

El usuario finalmente necesita obtener los cambios publicados:

Código
  1. git fetch upstream master
  2. git checkout master
  3. git merge upstream/master

Y también borrar las ramas locales y remotas que ya no son necesarias:

Código
  1. git push --delete origin meta1
  2. git branch -d meta1

Y el ciclo se repite ad infinitum.

Las ventajas de tener varios repositorios en los que actuán como repositorios de lectura para otros es que los usuarios pueden equivocarse sobre estos repositorios. Si alguién ha publicado código que no esta listo no afecta a los demás miembros del equipo porque los demás miembros del equipo basan su trabajo sobre el repositorio central no sobre el repositorio de un determinado miembro. Los demás miembros y el usuario integrador pueden decirle al usuario que necesita corregir el historial o el código y lo haría sobre fork.

En el peor de los casos en el que el usuario ha hecho daños en el cual ya no puede reparar su fork, lo único que el usuario tiene que hacer es eliminar por completo su fork y crear un nuevo fork del repositorio central. Una opción que no es posible si se trabaja directamente sobre el repositorio central.

Ramas auxiliares

Por lo general los cambios se integran sobre una rama (hasta ahora solo hemos usado master) pero también es muy común tener otras ramas para diferentes propósitos. El caso más común es tener una rama de desarrollo y otra de producción.

Por ejemplo, los cambios de los miembros del equipo se integran en la rama de desarrollo y una vez que el proyecto este listo para distribuirse la rama de desarrollo se integra en la rama master.

Un ejemplo de como se vería el historial:


Las ramas meta1 y meta2 son ramas en las que los usuarios trabajan. El usuario integrador las integra en la rama develop. Una vez que los cambios en develop sean suficientes para distribuirse devleop se integra en master.

El flujo de trabajo que popularizó multiples ramas auxiliares fue GitFlow. Quizás para proyectos más robustos sea necesario tener todas estas ramas auxiliares pero yo creo que para la gran mayoría de proyectos 1 o 2 ramas son suficientes.

La ventaja de trabajar en esta forma es que podemos realizar ciertas acciones cuando un commit llega a cualquiera de estas ramas. El ejemplo más común es desplegar un entorno de desarrollo cuando la rama se integra a la rama de desarrollo, correr un número de pruebas, etc. En la rama master podemos desplegar código inmediatamente sobre el entorno de producción. Realizar acciones en especifico merece su propia guía realmente por eso solo lo mencionare.

Epílogo

Esta es la última parte de lo que yo consideraría indispensable para cualquier persona que maneje git. El único tema faltante a considerar es como publicar nuestros repositorios y hacerlos públicos.

La siguiente parte (lo estoy considerando todavía) sería enseñar como usar un servicio como Github para realizar algunas operaciones administrativas sobre los repositorios publicos.


« Última modificación: 19 Mayo 2021, 16:23 pm por MinusFour » En línea

Becerra

Desconectado Desconectado

Mensajes: 97


Siempre aprendiendo


Ver Perfil
Re: Repositorios remotos y flujos de colaboración (cuarta parte)
« Respuesta #1 en: 23 Enero 2021, 22:21 pm »

Hola

¡¡Fantástico!! Empecé a leerlo pero ahora mismo no tengo tiempo, pero vi que puede ser lo que estoy buscando, te cuento:

Yo tengo varios proyectos en curso, alguno lo tengo en github, pero quiero tener una copia de todos mis proyectos en un pendrive, sobre todo los más pequeños y no estoy seguro de cómo debo hacer.

Si la forma de hacerlo es la que indicas en este post, lo leeré con detenimiento!!

Un saludo


En línea

Buenas preguntas obtienen mejores respuestas
MinusFour
Moderador Global
***
Desconectado Desconectado

Mensajes: 5.529


I'm fourth.


Ver Perfil WWW
Re: Repositorios remotos y flujos de colaboración (cuarta parte)
« Respuesta #2 en: 24 Enero 2021, 05:54 am »

Hola

¡¡Fantástico!! Empecé a leerlo pero ahora mismo no tengo tiempo, pero vi que puede ser lo que estoy buscando, te cuento:

Yo tengo varios proyectos en curso, alguno lo tengo en github, pero quiero tener una copia de todos mis proyectos en un pendrive, sobre todo los más pequeños y no estoy seguro de cómo debo hacer.

Si la forma de hacerlo es la que indicas en este post, lo leeré con detenimiento!!

Un saludo

git clone es suficiente siempre y cuando tu directorio de trabajo este limpio (e.g ningún cambio pendiente a agregar). Si tienes archivos ignorados y cambios pendientes por agregar entonces lo mejor es copiar y pegar (con cp o desde una interfaz gráfica).
En línea

Páginas: [1] Ir Arriba Respuesta Imprimir 

Ir a:  

Mensajes similares
Asunto Iniciado por Respuestas Vistas Último mensaje
Repositorios GNU/Linux [colaboracion]
GNU/Linux
jlinux 5 3,765 Último mensaje 30 Agosto 2010, 19:34 pm
por jlinux
No te creas todo lo que lees en Twitter: la cuarta parte es mentira
Noticias
wolfbcn 0 1,117 Último mensaje 4 Junio 2015, 01:54 am
por wolfbcn
Más de la cuarta parte de usuarios Firefox no usa complementos
Noticias
wolfbcn 0 1,137 Último mensaje 6 Enero 2016, 21:40 pm
por wolfbcn
Una cuarta parte de los españoles cree que robar propiedades en la “vida ....
Noticias
wolfbcn 0 1,236 Último mensaje 13 Febrero 2018, 01:59 am
por wolfbcn
Flujos de control en C/C++
Programación C/C++
sukokiin 0 1,524 Último mensaje 21 Agosto 2019, 04:56 am
por sukokiin
WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines