Explicación de la horquilla de Github y cómo almacenan los files

Me pregunto qué pasará cuando termine un fork en Github.

Por ejemplo, cuando doy un proyecto, ¿se hace una copy en el server github de todo ese código, o simplemente se crea un enlace al mismo?

Entonces, otra pregunta: en git, ya que hash todos los files si le agregas el mismo file, no es necesario almacenar el contenido del file nuevamente porque el hash ya estará en el sistema, ¿correcto?

Es github así? Entonces, si subo exactamente el mismo fragment de código que otro usuario, cuando github gits lo hace, básicamente solo crea un enlace a ese file, ya que tendría el mismo hash, ¿o saveá todos sus contenidos de nuevo por separado?

Cualquier iluminación sería genial, ¡gracias!

github.com es exactamente la misma semántica que git, pero con una interfaz gráfica de usuario basada en la web.

Almacenamiento : "Git almacena cada revisión de un file como un único object blob"
Por lo tanto, cada file se almacena de forma única, pero usa un algorithm SHA-1 para determinar los cambios de un file a otro.

En cuanto a github, un tenedor es esencialmente un clon. Esto significa que una nueva horquilla es una nueva área de almacenamiento en sus serveres, con una reference a su ORIGEN. De ninguna manera establecería enlaces entre los dos, porque git por naturaleza puede rastrear los controles remotos. Cada tenedor sabe el upstream.

Cuando dice "si subo la misma pieza de código que otro usuario", el término "cargar" es un poco vago en el sentido "git". Si está trabajando en el mismo repository y git incluso le permite enviar el mismo file, eso significa que fue diferente y se verificó en esa revisión. Pero si te refieres a trabajar en un clon / tenedor de otro repository, sería la misma situación, pero tampoco se realizarían enlaces en el sistema de files al otro repository.

No puedo afirmar que tengo ningún conocimiento íntimo de las optimizaciones que Github podría estar haciendo bajo el capó, en su sistema interno. Posiblemente podrían estar haciendo operaciones personalizadas intermedias para ahorrar espacio en el disco. Pero cualquier cosa que harían sería transparente para usted y no importaría mucho, ya que efectivamente debería funcionar siempre bajo la semántica git esperada.

Un desarrollador de github escribió una publicación de blog sobre cómo hacen internamente su propio flujo de trabajo de git. Si bien no se relaciona con su pregunta sobre cómo administran el flujo de trabajo real del service, creo que esta cita de la conclusión es bastante informativa:

Git en sí mismo es bastante complejo de comprender, por lo que el flujo de trabajo que utilizas con él es más complejo de lo necesario, simplemente agrega más gastos generales mentales para todos los días. Siempre recomendaría utilizar el sistema más simple posible que funcionará para su equipo y hacerlo hasta que no funcione más y luego agregar complejidad solo cuando sea absolutamente necesario.

Lo que resta de eso, es que reconocen lo complejo que es el git en sí mismo, así que lo más probable es que tomen el toque más ligero posible para envolverlo para proporcionar el service, y dejar que git haga lo que mejor hace de forma nativa.

No sé exactamente cómo lo hace GitHub, pero aquí hay una manera posible. Requiere cierto conocimiento de la forma en que git almacena sus datos.

La respuesta corta es que los repos pueden compartir la database de objects , pero cada uno tiene sus propias references.
Incluso podemos simularlo localmente para una testing de concepto.

En el directory de un repository desnudo (o en el .git/ subdir si no está vacío) hay tres cosas que son el mínimo para que un repository funcione:

  • el subdirectory objects/ , que almacena todos los objects (commits, trees, blobs …). Se almacenan individualmente como files con nombres iguales al hash del object o en files .pack .
  • el subdirectory refs/ , que almacena files simples como refs/heads/master cuyo contenido es el hash del object al que hace reference.
  • el file HEAD , que dice cuál es el compromiso actual. Su valor es un hash en bruto (que corresponde a un encabezado separado, es decir, no estamos en ninguna twig con nombre) o un enlace textual a un ref donde se puede encontrar el hash real (por ejemplo, ref: refs/heads/master . significa que estamos en la twig master )

Supongamos que alguien crea su repos original (no bifurcando) en Github.
Para simular, localmente hacemos

 $ git init --bare github_orig 

Imaginamos que lo anterior sucede en los serveres de Github. Ahora hay un repository github vacío. Entonces imaginamos que desde nuestra propia PC clonamos el repo de github:

 $ git clone github_orig local_orig 

Por supuesto, en la vida real en lugar de github_orig utilizaremos https://github... Ahora hemos clonado el repository local_orig en local_orig .

 $ cd local_orig/ $ echo zzz > file $ git add file $ git commit -m initial $ git push $ cd .. 

Después de este github_orig de object github_orig contendrá nuestro object de confirmación github_orig , un object de blob para file y un object de tree. El file refs/heads/master contendrá el hash de confirmación.

Ahora imaginemos qué podría estar pasando cuando alguien presione el button Fork . Crearemos un repository git pero a mano :

 $ mkdir github_fork $ cd github_fork/ $ cp ../github_orig/HEAD . $ cp -r ../github_orig/refs . $ ln -s ../github_orig/objects $ cd .. 

Tenga en count que copymos HEAD y refs pero hacemos un enlace simbólico para los objects . Como podemos ver, hacer un tenedor es muy barato. Incluso si tenemos decenas de twigs, cada una de ellas es simplemente un file en el directory refs/heads que contiene un hash hexadecimal simple (40 bytes). Para los objects solo vinculamos al directory de objects original, ¡no copymos nada!

Ahora simulamos que el usuario que hace la horquilla, clona el repository bifurcado localmente:

 $ git clone github_fork local_fork $ cd local_fork $ # ls .git/ file 

Podemos ver que hemos clonado con éxito aunque el repository desde el que clonamos no tiene sus propios objects sino enlaces a los del repository original.
Ahora el usuario de bifurcación puede hacer bifurcaciones, confirmaciones y luego empujarlas a github_fork . Los objects se github_orig en el directory de objects , que es lo mismo para github_orig . Pero los refs y HEAD se modificarán y ya no coincidirán con los de github_orig .

Entonces, la conclusión es que todos los repos que pertenecen al mismo tree de bifurcación comparten un grupo de objects común, mientras que cada repository contiene sus propias references. Cualquiera que empuje commits a su propio repository bifurcado modifica sus propias references pero coloca los objects en un grupo compartido.

Por supuesto, para ser realmente útil, hay que ocuparse de otras cosas más, lo más importante es que no se debe invocar el recolector de basura git a less que el repository en el que se invoca tenga conocimiento de todas las references, no solo de las suyas. De lo contrario, podría descartar objects en el grupo compartido que no son accesibles desde sus references pero que podrían ser accesibles desde otros reposs refs.

Según https://enterprise.github.com/releases/2.2.0/notes, GitHub Enterprise (y supongo que GitHub) de alguna manera comparte objects entre horquillas para networkingucir el uso de espacio en disco:

Esta versión cambia la forma en que GitHub Enterprise almacena repositorys, lo que networkinguce el uso del disco al compartir objects Git entre las horquillas y mejora el performance del almacenamiento en caching al leer datos del repository.

También hay más detalles sobre cómo lo hacen en https://githubengineering.com/counting-objects .