Fusión: Hg / Git vs. SVN


144

A menudo leo que Hg (y Git y ...) son mejores para fusionarse que SVN, pero nunca he visto ejemplos prácticos de dónde Hg / Git puede fusionar algo donde SVN falla (o donde SVN necesita intervención manual). ¿Podría publicar algunas listas paso a paso de ramas / modificar / confirmar / ... operaciones que muestren dónde SVN fallaría mientras Hg / Git felizmente avanza? Casos prácticos, no muy excepcionales, por favor ...

Algunos antecedentes: tenemos unas pocas docenas de desarrolladores trabajando en proyectos usando SVN, con cada proyecto (o grupo de proyectos similares) en su propio repositorio. Sabemos cómo aplicar versiones de lanzamiento y características para no tener problemas muy a menudo (es decir, hemos estado allí, pero hemos aprendido a superar los problemas de Joel de "un programador que causa trauma a todo el equipo") o "necesita seis desarrolladores durante dos semanas para reintegrar una sucursal"). Tenemos ramas de lanzamiento que son muy estables y solo se utilizan para aplicar correcciones de errores. Tenemos troncales que deberían ser lo suficientemente estables como para poder crear una versión en una semana. Y tenemos ramas de características en las que pueden trabajar los desarrolladores individuales o grupos de desarrolladores. Sí, se eliminan después de la reintegración para que no abarroten el repositorio. ;)

Así que todavía estoy tratando de encontrar las ventajas de Hg / Git sobre SVN. Me encantaría tener algo de experiencia práctica, pero todavía no hay proyectos más grandes que podamos mover a Hg / Git, así que estoy atascado con jugar con pequeños proyectos artificiales que solo contienen algunos archivos inventados. Y estoy buscando algunos casos en los que pueda sentir el impresionante poder de Hg / Git, ya que hasta ahora he leído sobre ellos pero no he podido encontrarlos.


2
Creo que debería prestar atención a los duplicados exactos: stackoverflow.com/questions/43995/… stackoverflow.com/questions/459891/…
P Shved

11
Ya había leído el primero, el otro era nuevo. Pero ya tienen entre 1 y 2 años y parecen estar relacionados principalmente con problemas anteriores a svn-1.5 (donde svn aún no tenía seguimiento de fusión).
stmax

2
Solo un comentario que también puede agrupar Bazaar con git / hg como otro DVCS que manejará los siguientes problemas correctamente. Y como mencionó tratar de encontrar ventajas: una ventaja logística simple de git / hg / bzr es que las ramas no son globales como lo son con svn. No tiene que ver 67 sucursales, cuando solo un par se aplica a usted. Todos hacen su trabajo en sucursales "privadas" y luego usan la excelente capacidad de fusión para volver a fusionarse sin preocuparse de si la fusión funcionará en el 99% de los casos.
wadesworld

55
@wade: ¿ves sucursales "privadas" como una ventaja en un entorno corporativo? Estoy preocupado por las copias de seguridad. a menudo tengo ramas características que viven durante 1-2 meses antes de la reintegración ..
stmax

9
@stmax: una preocupación válida. Sin embargo, lo que se encuentra en muchos entornos corporativos con subversión es que las personas demoran en registrarse hasta que su código sea perfecto, y usted tiene la misma exposición allí.
wadesworld

Respuestas:


91

Yo mismo no uso Subversion, pero según las notas de la versión para Subversion 1.5: Seguimiento de fusión (fundacional) parece que existen las siguientes diferencias con respecto a cómo funciona el seguimiento de fusión en sistemas de control de versiones DAG completos como Git o Mercurial.

  • Fusionar tronco a rama es diferente de fusionar rama a tronco: por alguna razón, la combinación de tronco a rama requiere la --reintegrateopción svn merge.

    En los sistemas de control de versiones distribuidas como Git o Mercurial no hay diferencia técnica entre troncal y rama: todas las ramas se crean de la misma manera ( aunque puede haber una diferencia social ). La fusión en cualquier dirección se realiza de la misma manera.

  • Debe proporcionar una nueva opción -g( --use-merge-history) svn logy svn blametener en cuenta el seguimiento de fusión.

    En Git y Mercurial, el seguimiento de fusión se tiene en cuenta automáticamente al mostrar el historial (registro) y la culpa. En Git puede solicitar seguir al primer padre solo con --first-parent(supongo que existe una opción similar también para Mercurial) para "descartar" la información de seguimiento de fusión git log.

  • Por lo que entiendo, la svn:mergeinfopropiedad almacena información por ruta sobre conflictos (Subversion está basada en el conjunto de cambios), mientras que en Git y Mercurial es simplemente confirmar objetos que pueden tener más de un padre.

  • La subsección "Problemas conocidos" para el seguimiento de fusión en Subversion sugiere que la fusión repetida / cíclica / reflexiva podría no funcionar correctamente. Significa que con las siguientes historias la segunda fusión podría no hacer lo correcto ('A' puede ser tronco o rama, y ​​'B' puede ser rama o tronco, respectivamente):

    * --- * --- x --- * --- y --- * --- * --- * --- M2 <- A
             \ \ /
              - * ---- M1 --- * --- * --- / <- B
    

    En el caso de que el arte ASCII anterior se rompa: la rama 'B' se crea (bifurca) a partir de la rama 'A' en la revisión 'x', luego la rama 'A' se fusiona en la revisión 'y' en la rama 'B' como fusionar 'M1', y finalmente la rama 'B' se fusiona en la rama 'A' como la combinación 'M2'.

    * --- * --- x --- * ----- M1 - * --- * --- M2 <- A
             \ / / 
              \ - * --- y --- * --- * --- / <- B
    

    En el caso de que el arte ASCII anterior se rompa: la rama 'B' se crea (bifurca) a partir de la rama 'A' en la revisión 'x', se fusiona en la rama 'A' en 'y' como 'M1', y más tarde se fusionó nuevamente en la rama 'A' como 'M2'.

  • Es posible que Subversion no admita casos avanzados de fusión entrecruzada .

    * --- b ----- B1 - M1 - * --- M3
         \ \ / /
          \ X /
           \ / \ /
            \ - B2 - M2 - *
    

    Git maneja esta situación muy bien en la práctica usando la estrategia de fusión "recursiva". No estoy seguro acerca de Mercurial.

  • En "Problemas conocidos" , se advierte que el seguimiento de fusión podría no funcionar con los cambios de nombre de archivos, por ejemplo, cuando un lado cambia el nombre del archivo (y tal vez lo modifica), y el segundo lado modifica el archivo sin cambiar el nombre (bajo el nombre anterior).

    Tanto Git como Mercurial manejan este caso muy bien en la práctica: Git con detección de cambio de nombre , Mercurial con seguimiento de cambio de nombre .

HTH


de alguna manera (¿error en el analizador Markdown?) la parte después del <pre>...</pre>bloque no está sangrada como debería ser ...
Jakub Narębski

1
+1 para los muchos ejemplos detallados. Todavía no entiendo por qué el ejemplo en el primer arte ascii podría causar problemas. parece la forma estándar de tratar las ramas de características: suponga que A es el tronco, B es una rama de características. se fusiona semanalmente de A a B y cuando haya terminado con la función, combina todo de B a A y luego elimina B. que siempre ha funcionado para mí. ¿He entendido mal el diagrama?
stmax

1
Tenga en cuenta que no sé (no he verificado) que los ejemplos dados anteriormente realmente dan problemas en Subversion . Los renombrados y la fusión entrecruzada son un problema real en SVN, creo.
Jakub Narębski

2
reintegrate merges es una opción especial para ayudarlo en el caso más común al fusionar: tampoco hay diferencias técnicas entre ramas y troncales en svn. Tiendo a nunca usarlo, y me quedo con la opción de combinación estándar. Aún así, el único problema con svn merge es que trata un movimiento / cambio de nombre como una eliminación + adición.
gbjbaanb

--reintegratees obsoleto.
naught101

120

Yo también he estado buscando un caso en el que, por ejemplo, Subversion no pueda fusionar una rama y Mercurial (y Git, Bazaar, ...) hace lo correcto.

El Libro SVN describe cómo se fusionan incorrectamente los archivos renombrados . ¡Esto se aplica a Subversion 1.5 , 1.6 , 1.7 y 1.8 ! He intentado recrear la situación a continuación:

cd / tmp
rm - rf svn - repo svn - pago y envío
svnadmin create svn - repo
archivo de pago de svn : /// tmp / svn - repo svn - pago
cd svn - pago y envío
ramas del tronco mkdir
echo '¡Adiós mundo!' > tronco / hola . TXT 
svn agregar ramas del tronco
svn commit - m 'Importación inicial'. 
svn copy '^ / trunk' '^ / sucursales / renombrar' - m 'Crear rama'. 
svn conmutador '^ / trunk' . 
echo '¡Hola, mundo!' > Hola . TXT    
svn commit - m 'Actualización en troncal'. 
svn cambia '^ / sucursales / renombrar' . 
svn renombrar hola . txt hola . en . TXT 
svn commit - m 'Renombrar en rama'. 
svn conmutador '^ / trunk' . 
svn merge - reintegrate '^ / sucursales / renombrar' 

Según el libro, la fusión debería finalizar limpiamente, pero con datos incorrectos en el archivo renombrado ya que trunkse olvida la actualización . En cambio, aparece un conflicto de árbol (esto es con Subversion 1.6.17, la versión más reciente de Debian en el momento de la escritura):

--- Combinando diferencias entre URL de repositorio en '.':
A hello.en.txt
   C hello.txt
Resumen de conflictos:
  Conflictos de árbol: 1

No debería haber ningún conflicto en absoluto: la actualización debe fusionarse con el nuevo nombre del archivo. Mientras Subversion falla, Mercurial maneja esto correctamente:

rm -rf /tmp/hg-repo
hg init /tmp/hg-repo
cd /tmp/hg-repo
echo 'Goodbye, World!' > hello.txt
hg add hello.txt
hg commit -m 'Initial import.'
echo 'Hello, World!' > hello.txt
hg commit -m 'Update.'
hg update 0
hg rename hello.txt hello.en.txt
hg commit -m 'Rename.'
hg merge

Antes de la fusión, el repositorio se ve así (desde hg glog):

@ conjunto de cambios: 2: 6502899164cc
El | etiqueta: consejo
El | padre: 0: d08bcebadd9e
El | usuario: Martin Geisler
El | fecha: Jue 01 Abr 12:29:19 2010 +0200
El | resumen: Cambiar nombre.
El |
El | o conjunto de cambios: 1: 9d06fa155634
| / usuario: Martin Geisler 
El | fecha: Jue 01 Abr 12:29:18 2010 +0200
El | resumen: Actualización.
El |
o conjunto de cambios: 0: d08bcebadd9e
   usuario: Martin Geisler 
   fecha: Jue 01 Abr 12:29:18 2010 +0200
   resumen: importación inicial.

El resultado de la fusión es:

fusionando hello.en.txt y hello.txt a hello.en.txt
0 archivos actualizados, 1 archivos fusionados, 0 archivos eliminados, 0 archivos sin resolver
(fusión de rama, no te olvides de cometer)

En otras palabras: Mercurial tomó el cambio de la revisión 1 y lo fusionó con el nuevo nombre de archivo de la revisión 2 ( hello.en.txt). Por supuesto, el manejo de este caso es esencial para admitir la refactorización y la refactorización es exactamente el tipo de cosas que querrá hacer en una sucursal.


+1 para un ejemplo detallado, uno puede tocar el teclado y ver por sí mismo lo que sucede. Como novato de Mercurial, me pregunto si la versión hg de este ejemplo se sigue de una manera obvia, ¿línea por línea?
DarenW

44
@DarenW: He agregado los comandos Mercurial correspondientes, ¡espero que aclare las cosas!
Martin Geisler

17

Sin hablar de las ventajas habituales (confirmaciones fuera de línea, proceso de publicación , ...) aquí hay un ejemplo de "fusión" que me gusta:

El escenario principal que sigo viendo es una rama en la que ... se desarrollan dos tareas no relacionadas
(comenzó desde una característica, pero condujo al desarrollo de esta otra característica.
O comenzó desde un parche, pero condujo a la desarrollo de otra característica).

¿Cómo fusionar solo una de las dos características en la rama principal?
¿O cómo aíslas las dos características en sus propias ramas?

Podría intentar generar algún tipo de parches, el problema es que ya no está seguro de las dependencias funcionales que podrían haber existido entre:

  • los commits (o revisiones para SVN) utilizados en sus parches
  • el otro confirma que no forma parte del parche

Git (y Mercurial también, supongo) proponen la opción rebase --onto para rebase (restablecer la raíz de la rama) parte de una rama:

De la publicación de Jefromi

- x - x - x (v2) - x - x - x (v2.1)
           \
            x - x - x (v2-only) - x - x - x (wss)

puede desenredar esta situación en la que tiene parches para la v2, así como una nueva función de wss en:

- x - x - x (v2) - x - x - x (v2.1)
          |\
          |  x - x - x (v2-only)
           \
             x - x - x (wss)

, permitiéndole:

  • pruebe cada rama de forma aislada para verificar si todo compila / funciona según lo previsto
  • fusiona solo lo que quieres destacar.

La otra característica que me gusta (que influye en las fusiones) es la capacidad de aplastar las confirmaciones (en una rama que aún no se ha enviado a otro repositorio) para presentar:

  • una historia más limpia
  • commits que son más coherentes (en lugar de commit1 para function1, commit2 para function2, commit3 nuevamente para function1 ...)

Eso asegura fusiones que son mucho más fáciles, con menos conflictos.


svn no tiene confirmaciones fuera de línea? rofl? ¿Cómo puede alguien remotamente considerar usarlo si es así?
o0 '.

@Lohoris Cuando apareció SVN, no había DVCS de código abierto ampliamente utilizados; en este punto, creo que es principalmente la inercia que la gente todavía lo usa.
Max Nanasy

@MaxNanasy es un tipo de inercia muy malo ... aún así, elegirlo ahora sería simplemente estúpido.
o0 '.

Las confirmaciones de @Lohoris Online (más exactamente, centralizadas) no son tan importantes en un equipo pequeño donde el repositorio simplemente puede estar en un servidor local compartido. Los DVCS se inventaron principalmente para grandes equipos distribuidos geográficamente (tanto git como mercurial estaban destinados a administrar el código del kernel de Linux) y proyectos de código abierto (de ahí la popularidad de GitHub). La inercia también puede verse como una evaluación de los riesgos frente a los beneficios de cambiar una herramienta central para el flujo de trabajo de un equipo.
IMSoP

1
@Lohoris Creo que entendiste mal mi punto de vista sobre DB, firewall, etc.: no tiene sentido que pueda comprometerme en mi máquina doméstica si realmente no puedo ejecutar ese código primero. Yo podía trabajar ciego, pero el hecho de que no puedo cometer cosas en algún lugar no sería lo principal que me pone fuera.
IMSoP

8

Recientemente migramos de SVN a GIT, y enfrentamos esta misma incertidumbre. Hubo mucha evidencia anecdótica de que GIT era mejor, pero fue difícil encontrar algún ejemplo.

Sin embargo, puedo decirte que GIT es MUCHO MEJOR en la fusión que SVN. Esto es obviamente anecdótico, pero hay una tabla a seguir.

Estas son algunas de las cosas que encontramos:

  • SVN solía generar muchos conflictos de árboles en situaciones en las que parecía que no debería. Nunca llegamos al fondo de esto, pero no sucede en GIT.
  • Si bien es mejor, GIT es significativamente más complicado. Pasa algo de tiempo entrenando.
  • Estábamos acostumbrados a Tortoise SVN, que nos gustó. Tortoise GIT no es tan bueno y esto puede desanimarte. Sin embargo, ahora uso la línea de comando GIT, que prefiero a Tortoise SVN o cualquiera de las GUI de GIT.

Cuando estábamos evaluando GIT, realizamos las siguientes pruebas. Estos muestran a GIT como el ganador cuando se trata de fusionarse, pero no tanto. En la práctica, la diferencia es mucho mayor, pero supongo que no hemos logrado replicar las situaciones que SVN maneja mal.

Evaluación de fusión GIT vs SVN


5

Otros han cubierto los aspectos más teóricos de esto. Tal vez pueda prestar una perspectiva más práctica.

Actualmente estoy trabajando para una empresa que usa SVN en un modelo de desarrollo de "rama de características". Es decir:

  • No se puede trabajar en el baúl
  • Cada desarrollador puede crear sus propias sucursales
  • Las ramas deben durar la duración de la tarea realizada
  • Cada tarea debe tener su propia rama
  • Es necesario autorizar las fusiones de regreso a la troncal (normalmente a través de bugzilla)
  • En los momentos en que se necesitan altos niveles de control, un portero puede realizar las fusiones.

En general, funciona. SVN se puede usar para un flujo como este, pero no es perfecto. Hay algunos aspectos de SVN que se interponen en el camino y dan forma al comportamiento humano. Eso le da algunos aspectos negativos.

  • Hemos tenido bastantes problemas con personas que se bifurcan desde puntos más bajos que ^/trunk. Esta hojarasca combina registros de información en todo el árbol y, finalmente, rompe el seguimiento de combinación. Comienzan a aparecer falsos conflictos y reina la confusión.
  • Recoger los cambios del tronco a una rama es relativamente sencillo. svn mergehace lo que quieres La fusión de sus cambios requiere (se nos dice) --reintegratedel comando de fusión. Nunca he entendido realmente este interruptor, pero significa que la rama no puede fusionarse nuevamente en el tronco. Esto significa que es una rama muerta y que debe crear una nueva para continuar trabajando. (Ver nota)
  • Todo el negocio de realizar operaciones en el servidor a través de URL al crear y eliminar sucursales realmente confunde y asusta a las personas. Entonces lo evitan.
  • Es fácil equivocarse entre las ramas, dejando parte de un árbol mirando la rama A, mientras que otra parte mirando la rama B. Por lo tanto, las personas prefieren hacer todo su trabajo en una rama.

Lo que suele suceder es que un ingeniero crea una sucursal el día 1. Comienza su trabajo y lo olvida. Algún tiempo después, aparece un jefe y le pregunta si puede liberar su trabajo a la cajuela. El ingeniero ha temido este día porque reintegrarse significa:

  • Fusionando su rama de larga vida nuevamente en el tronco y resolviendo todos los conflictos, y liberando código no relacionado que debería haber estado en una rama separada, pero no lo estaba.
  • Eliminar su rama
  • Crear una nueva sucursal
  • Cambiar su copia de trabajo a la nueva sucursal

... y debido a que el ingeniero hace esto tan poco como puede, no puede recordar el "encantamiento mágico" para hacer cada paso. Cambios y URL incorrectos suceden, y de repente están en un lío y van a buscar al "experto".

Con el tiempo, todo se calma y la gente aprende a lidiar con las deficiencias, pero cada principiante pasa por los mismos problemas. La realidad final (a diferencia de lo que expuse al comienzo) es:

  • No se realiza trabajo en el maletero
  • Cada desarrollador tiene una rama principal
  • Las ramas duran hasta que se necesite liberar el trabajo
  • Las correcciones de errores en los boletos tienden a tener su propia rama
  • Las fusiones de regreso al tronco se realizan cuando están autorizadas

...pero...

  • A veces el trabajo llega al tronco cuando no debería porque está en la misma rama que otra cosa.
  • Las personas evitan todas las fusiones (incluso cosas fáciles), por lo que a menudo trabajan en sus propias pequeñas burbujas
  • Las grandes fusiones tienden a ocurrir y causan una cantidad limitada de caos.

Afortunadamente, el equipo es lo suficientemente pequeño como para hacer frente, pero no escalaría. La cosa es que nada de esto es un problema con CVCS, pero más aún porque las fusiones no son tan importantes como en DVCS no son tan resbaladizas. Esa "fusión de fusión" provoca un comportamiento que significa que un modelo de "Rama de características" comienza a descomponerse. Las buenas fusiones deben ser una característica de todos los VCS, no solo DVCS.


De acuerdo con esto , ahora hay un --record-onlyinterruptor que podría usarse para resolver el --reintegrateproblema, y aparentemente v1.8 elige cuándo reintegrarse automáticamente, y no causa que la rama esté muerta después


Según tengo entendido, la opción --reintegrate le dice a svn que ya ha resuelto cambios conflictivos al fusionarse con la rama de características. Efectivamente, en lugar de tratarlo como un parche, sobrescribe archivos completos con la versión de bifurcación, ya que ha verificado en el historial de fusiones que todas las revisiones de troncales se han fusionado en la bifurcación.
IMSoP

@IMSoP: posiblemente, eso tiene algún sentido. Sin embargo, eso no me explica por qué era necesario o por qué hizo imposible nuevas fusiones de esa rama. Tampoco ayudó que la opción fuera en gran medida indocumentada.
Paul S

Solo lo he usado a través de TortoiseSVN, donde siempre se explicó de manera destacada en la interfaz de usuario de combinación. Creo que SVN 1.8 elige automáticamente la estrategia correcta y no necesita una opción separada, pero no sé si han solucionado el algoritmo de fusión normal para tratar correctamente con una rama que se ha restablecido de esta manera.
IMSoP

3

Antes de la subversión 1.5 (si no me equivoco), la subversión tenía una desventaja significativa en que no recordaría el historial de fusión.

Veamos el caso descrito por VonC:

- x - x - x (v2) - x - x - x (v2.1)
          |\
          |  x - A - x (v2-only)
           \
             x - B - x (wss)

Observe las revisiones A y B. Digamos que fusionó los cambios de la revisión A en la rama "wss" a la rama "solo v2" en la revisión B (por cualquier razón), pero continuó usando ambas ramas. Si intentara fusionar las dos ramas nuevamente usando mercurial, solo fusionaría los cambios después de las revisiones A y B. Con la subversión, tendría que fusionar todo, como si no hubiera fusionado antes.

Este es un ejemplo de mi propia experiencia, donde la fusión de B a A llevó varias horas debido al volumen de código: eso habría sido un verdadero dolor que pasar por una vez más , lo que habría sido el caso de pre-1,5 subversión.

Otra diferencia, probablemente más relevante en el comportamiento de fusión de Hginit: Subversion Re-education :

Imagine que usted y yo estamos trabajando en algún código, y ramificamos ese código, y cada uno de nosotros salimos a nuestros espacios de trabajo separados y hacemos muchos cambios en ese código por separado, por lo que han divergido bastante.

Cuando tenemos que fusionarnos, Subversion intenta ver ambas revisiones, mi código modificado y su código modificado, y trata de adivinar cómo juntarlas en un gran desastre. Por lo general, falla, produciendo páginas y páginas de "conflictos de fusión" que no son realmente conflictos, simplemente lugares donde Subversion no pudo entender lo que hicimos.

Por el contrario, mientras trabajábamos por separado en Mercurial, Mercurial estaba ocupado manteniendo una serie de conjuntos de cambios. Y así, cuando queremos fusionar nuestro código, Mercurial en realidad tiene mucha más información: sabe lo que cada uno de nosotros cambió y puede volver a aplicar esos cambios, en lugar de solo mirar el producto final e intentar adivinar cómo ponerlo juntos.

En resumen, la forma en que Mercurial analiza las diferencias es (¿era?) Superior a la de la subversión.


55
He leído hginit. Lástima que no muestre ejemplos más prácticos de que hg está mejor que svn ... básicamente te dice que "confíes en Joel" que hg es simplemente mejor. los ejemplos simples que ha mostrado probablemente también podrían hacerse con svn ... en realidad es por eso que abrí esta pregunta.
stmax

1
Según cómo se dice esto, me viene a la mente la pregunta ingenua: ¿qué pasaría si el algoritmo de fusión de Mercurial se pusiera en Subversion? ¿Sería entonces svn tan bueno como hg? No, porque la ventaja de hg está en una organización de nivel superior, no en las matemáticas de texto de bajo nivel de combinar líneas de archivos. Esa es la idea novedosa que los usuarios de svn necesitamos asimilar.
DarenW

@stmax: puedo ver lo que quieres decir. Sin embargo, la opinión de Joel o de cualquier otra persona realmente no importa: una tecnología es mejor que la otra (para un conjunto de casos de uso) o no. @DarenW y @stmax: desde mi propia experiencia personal, Hg gana manos a mano debido a su operación distribuida (no estoy conectado todo el tiempo), rendimiento (muchas operaciones locales), ramificación extremadamente intuitiva potenciada por un algoritmo de fusión superior, hg rollback, salida de registro con plantilla, hg glog, carpeta única .hg ... Podría seguir y seguir ... cualquier cosa que no sea git y bazar se siente como una camisa de fuerza.
Tomislav Nakic-Alfirevic

El comentario de hg citado sobre "conjuntos de cambios" me parece bastante inexacto. SVN sabe perfectamente qué cambios está fusionando (un conjunto de cambios es básicamente la diferencia entre dos instantáneas, y viceversa, ¿verdad?), Y puede aplicar cada una de ellas si lo desea; ciertamente no tiene que "adivinar" nada. Si hace "un gran desorden", entonces ese es un problema de implementación, no algo fundamental para el diseño. El principal problema que es difícil de resolver además del diseño de la arquitectura actual es mover / copiar / renombrar archivos.
IMSoP
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.