Mesclagem: Hg / Git vs. SVN


144

Costumo ler que Hg (e Git e ...) são melhores na fusão do que o SVN, mas nunca vi exemplos práticos de onde o Hg / Git pode mesclar algo em que o SVN falha (ou onde o SVN precisa de intervenção manual). Você poderia postar algumas listas passo a passo das operações de ramificação / modificação / confirmação /...- que mostram onde o SVN falharia enquanto o Hg / Git seguisse em frente? Casos práticos, não muito excepcionais, por favor ...

Alguns antecedentes: temos algumas dezenas de desenvolvedores trabalhando em projetos usando SVN, com cada projeto (ou grupo de projetos similares) em seu próprio repositório. Sabemos aplicar as ramificações de liberação e recurso para que não tenhamos problemas com muita frequência (ou seja, estivemos lá, mas aprendemos a superar os problemas de Joel de "um programador causando trauma a toda a equipe" ou "precisar de seis desenvolvedores por duas semanas para reintegrar uma filial"). Temos ramos de lançamento muito estáveis ​​e usados ​​apenas para aplicar correções. Temos troncos que devem ser estáveis ​​o suficiente para poder criar uma liberação dentro de uma semana. E temos ramos de recursos nos quais desenvolvedores individuais ou grupos de desenvolvedores podem trabalhar. Sim, eles são excluídos após a reintegração para não desorganizar o repositório. ;)

Então, ainda estou tentando encontrar as vantagens do Hg / Git sobre o SVN. Eu adoraria ter alguma experiência prática, mas ainda não existem projetos maiores que pudéssemos mudar para o Hg / Git, por isso estou envolvido em brincar com pequenos projetos artificiais que contêm apenas alguns arquivos inventados. E estou procurando alguns casos em que você possa sentir o poder impressionante do Hg / Git, pois até agora eu sempre li sobre eles, mas não consegui encontrá-los.


2
Eu acho que você deve prestar atenção às duplicatas exatas: stackoverflow.com/questions/43995/… stackoverflow.com/questions/459891/…
P Shved

11
Eu já tinha lido o primeiro, o outro era novo. Mas eles já têm de 1 a 2 anos e parecem estar principalmente relacionados a problemas anteriores ao svn-1.5 (onde o svn ainda não tinha rastreamento de mesclagem).
Stmax

2
Apenas um comentário em que você também pode agrupar o Bazaar com git / hg como outro DVCS que lidará com os problemas abaixo corretamente. E como você mencionou a tentativa de encontrar vantagens: uma vantagem logística simples do git / hg / bzr é que as ramificações não são globais como são com o svn. Você não precisa ver 67 ramos, quando apenas alguns se aplicam a você. Todo mundo faz seu trabalho em ramificações "particulares" e, em seguida, usa o excelente recurso de mesclagem para mesclar novamente sem suar se a mesclagem funcionará em 99% dos casos.
Wadesworld 03/04

5
@ Wade: você vê as agências "privadas" como vantagem em um ambiente corporativo? Estou preocupado com backups. Muitas vezes eu tenho ramos de recursos que ao vivo por 1-2 meses antes da reintegração ..
stmax

9
@stmax: uma preocupação válida. No entanto, o que você encontra em muitos ambientes corporativos com subversão é que as pessoas esperam o check-in até que seu código seja perfeito, e você tem a mesma exposição lá.
Wadeworld 04/04

Respostas:


91

Eu não uso o Subversion, mas a partir das notas de versão do Subversion 1.5: Rastreamento de mesclagem (fundamental) , parece que existem as seguintes diferenças de como o rastreamento de mesclagem funciona em sistemas de controle de versão completo do DAG , como Git ou Mercurial.

  • Mesclar tronco a ramo é diferente de mesclar ramo a tronco: por algum motivo, mesclar tronco a ramo exige a --reintegrateopção svn merge.

    Nos sistemas de controle de versão distribuído, como Git ou Mercurial, não há diferença técnica entre tronco e ramificação: todas as ramificações são criadas da mesma forma ( embora possa haver diferença social ). A fusão em qualquer direção é feita da mesma maneira.

  • Você precisa fornecer uma nova opção -g( --use-merge-history) svn loge svn blamelevar em consideração o rastreamento de mesclagem.

    No Git e no Mercurial, o rastreamento de mesclagem é levado automaticamente em consideração ao exibir histórico (log) e culpa. No Git, você pode solicitar que siga o primeiro pai apenas com --first-parent(acho que existe uma opção semelhante também para o Mercurial) para "descartar" as informações de rastreamento de mesclagem git log.

  • Pelo que entendi, a svn:mergeinfopropriedade armazena informações por caminho sobre conflitos (o Subversion é baseado em changeset), enquanto no Git e Mercurial é simplesmente confirmar objetos que podem ter mais de um pai.

  • A subseção "Problemas conhecidos" para rastreamento de mesclagem no Subversion sugere que a mesclagem repetida / cíclica / refletiva pode não funcionar corretamente. Isso significa que, com os seguintes históricos, a segunda mesclagem pode não fazer a coisa certa ('A' pode ser tronco ou ramificação e 'B' pode ser ramificação ou tronco, respectivamente):

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

    No caso de a arte ASCII acima ser interrompida: a ramificação 'B' é criada (bifurcada) a partir da ramificação 'A' na revisão 'x'; depois, a ramificação 'A' é mesclada na revisão 'y' na ramificação 'B' como mesclar 'M1' e, finalmente, a ramificação 'B' é mesclada na ramificação 'A' como mesclagem 'M2'.

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

    Caso a arte ASCII acima seja interrompida: a ramificação 'B' é criada (bifurcada) a partir da ramificação 'A' na revisão 'x', é mesclada na ramificação 'A' em 'y' como 'M1' e, posteriormente, mesclado novamente na ramificação 'A' como 'M2'.

  • O Subversion pode não suportar casos avançados de mesclagem cruzada .

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

    O Git lida com essa situação muito bem na prática, usando a estratégia de mesclagem "recursiva". Não tenho certeza sobre o Mercurial.

  • Em "Problemas conhecidos", há um aviso de que o rastreamento de mesclagem pode não funcionar com renomeação de arquivos, por exemplo, quando um lado renomeia o arquivo (e talvez o modifique), e o segundo lado modifica o arquivo sem renomear (sob o nome antigo).

    Tanto o Git quanto o Mercurial lidam com esse caso muito bem na prática: Git usando detecção de renomeação , Mercurial usando rastreamento de renomeação .

HTH


de alguma forma (? erro no analisador Markdown) a parte após <pre>...</pre>bloco não é recuado como deve ser ...
Jakub Narębski

1
+1 para muitos exemplos detalhados. Ainda não entendo por que o exemplo da primeira arte-científica pode causar problemas. parece a maneira padrão de tratar ramificações de recursos: assuma que A é o tronco, B é uma ramificação de recursos. você mescla semanalmente de A a B e, quando termina o recurso, mescla tudo de B a A e exclui B. que sempre funcionou para mim. eu entendi mal o diagrama?
Stmax

1
Note que eu não sei (não verifiquei) que os exemplos dados acima realmente causam problemas no Subversion . Renomear e mesclagem cruzada são problemas reais no SVN, eu acho.
Jakub Narębski 22/03/10

2
reintegrar mesclagens são uma opção especial para ajudá-lo no caso mais comum ao mesclar - também não há diferença técnica entre ramificações e tronco no svn. Eu costumo nunca usá-lo e ficar com a opção de mesclagem padrão. Ainda assim, o único problema com o svn merge é que ele trata uma mudança / renomeação como uma adição de exclusão +.
Gbjbaanb

--reintegrateestá obsoleto.
precisa saber é o seguinte

120

Eu também tenho procurado um caso em que, digamos, o Subversion não consiga mesclar um ramo e o Mercurial (e Git, Bazaar, ...) faça a coisa certa.

O SVN Book descreve como os arquivos renomeados são mesclados incorretamente . Isso se aplica ao Subversion 1.5 , 1.6 , 1.7 e 1.8 ! Tentei recriar a situação abaixo:

cd / tmp
rm - rf svn - repo svn - checkout
svnadmin create svn - repo
arquivo svn checkout : /// tmp / svn - repo svn - checkout
cd svn - checkout
ramos do tronco de mkdir
eco 'Adeus, mundo!' > tronco / olá . TXT 
svn add branches do tronco
svn commit - m 'Importação inicial.' 
svn copy '^ / trunk' '^ / branches / rename' - m 'Criar ramo.' 
svn alterna '^ / trunk' . 
eco 'Olá, mundo!' > Olá . TXT    
svn commit - m 'Atualização no tronco.' 
svn alterna '^ / branches / rename' . 
svn renomear olá . Olá txt . en . TXT 
svn commit - m 'Renomear na ramificação.' 
svn alterna '^ / trunk' . 
svn merge - reintegrar '^ / branches / rename' 

Segundo o livro, a mesclagem deve terminar de forma limpa, mas com dados incorretos no arquivo renomeado, pois a atualização trunké esquecida. Em vez disso, recebo um conflito de árvore (isso é com o Subversion 1.6.17, a versão mais recente do Debian no momento da redação):

--- Mesclando diferenças entre os URLs do repositório em '.':
Um hello.en.txt
   C hello.txt
Resumo dos conflitos:
  Conflitos em árvores: 1

Não deve haver nenhum conflito - a atualização deve ser mesclada com o novo nome do arquivo. Enquanto o Subversion falha, o Mercurial lida com isso corretamente:

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 da mesclagem, o repositório fica assim (de hg glog):

@ changeset: 2: 6502899164cc
| tag: tip
| pai: 0: d08bcebadd9e
| usuário: Martin Geisler
| data: quinta-feira 01 de abril 12:29:19 2010 +0200
| resumo: Renomear.
|
| o changeset: 1: 9d06fa155634
| / usuário: Martin Geisler 
| data: quinta-feira 01 de abril 12:29:18 2010 +0200
| resumo: Atualização.
|
o changeset: 0: d08bcebadd9e
   usuário: Martin Geisler 
   data: quinta-feira 01 de abril 12:29:18 2010 +0200
   resumo: Importação inicial.

A saída da mesclagem é:

mesclando hello.en.txt e hello.txt para hello.en.txt
0 arquivos atualizados, 1 arquivos mesclados, 0 arquivos removidos, 0 arquivos não resolvidos
(mesclagem de ramificação, não esqueça de confirmar)

Em outras palavras: Mercurial pegou a alteração da revisão 1 e a fundiu com o novo nome de arquivo da revisão 2 ( hello.en.txt). É claro que lidar com esse caso é essencial para oferecer suporte à refatoração e refatoração é exatamente o tipo de coisa que você deseja fazer em uma filial.


+1 para um exemplo detalhado, pode-se tocar no teclado e ver por si mesmo o que acontece. Como noob mercurial, gostaria de saber se a versão hg deste exemplo é seguida de maneira óbvia, linha por linha?
darenw

4
@ DarenW: Adicionei os comandos correspondentes do Mercurial, espero que isso torne as coisas mais claras!
Martin Geisler

17

Sem falar sobre as vantagens usuais (confirmações offline, processo de publicação , ...), aqui está um exemplo de "mesclagem" que eu gosto:

O cenário principal que eu continuo vendo é uma ramificação na qual ... duas tarefas não relacionadas são realmente desenvolvidas
(ele começou a partir de um recurso, mas levou ao desenvolvimento desse outro recurso.
Ou a partir de um patch, mas levou ao desenvolvimento de outro recurso).

Como você mescla apenas um dos dois recursos no ramo principal?
Ou como você isola os dois recursos em seus próprios ramos?

Você pode tentar gerar algum tipo de correção, o problema é que você não tem mais certeza das dependências funcionais que poderiam existir entre:

  • os commits (ou revisões para SVN) usados ​​em seus patches
  • o outro não faz parte do patch

Git (e Mercurial também, suponho) propõe a opção rebase --onto para rebase (redefinir a raiz do ramo) parte de um ramo:

Da publicação de Jefromi

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

você pode desembaraçar esta situação em que possui patches para a v2, bem como um novo recurso wss em:

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

, permitindo que você:

  • teste cada ramificação isoladamente para verificar se tudo compila / funciona como pretendido
  • mesclar apenas o que você deseja principal.

O outro recurso que eu gosto (que influencia a mesclagem) é a capacidade de compactar confirmações (em uma ramificação ainda não enviada para outro repositório) para apresentar:

  • uma história mais limpa
  • confirma que são mais coerentes (em vez de confirmar1 para a função1, confirmar2 para a função2, confirmar3 novamente para a função1 ...)

Isso garante fusões muito mais fáceis, com menos conflitos.


svn não tem confirmações offline? rofl? como alguém pode considerar remotamente usá-lo, se é assim?
o0 '.

@Lohoris Quando o SVN saiu, não havia DVCSs de código aberto amplamente usados; neste ponto, acho que é principalmente inércia que as pessoas ainda o usam.
Max Nanasy

@MaxNanasy é um tipo muito ruim de inércia ... ainda assim, escolhê-lo agora seria simplesmente estúpido.
o0 '.

As confirmações do @Lohoris Online (mais precisamente, centralizadas) não são tão importantes em uma equipe pequena, na qual o repositório pode simplesmente estar em um servidor local compartilhado. Os DVCSes foram inventados principalmente para grandes equipes geograficamente distribuídas (tanto git quanto mercurial destinavam-se a gerenciar o código do kernel Linux) e projetos de código aberto (daí a popularidade do GitHub). A inércia também pode ser vista como uma avaliação dos riscos versus benefícios da alteração de uma ferramenta central no fluxo de trabalho de uma equipe.
IMSoP

1
@ Lohoris: Eu acho que você não entendeu meu argumento sobre banco de dados, firewall, etc: não adianta ser capaz de confirmar em minha máquina doméstica se eu não conseguir executar esse código primeiro. Eu poderia trabalhar às cegas, mas o fato de não poder cometer coisas em algum lugar não seria a principal coisa que me deixava de lado.
IMSoP

8

Recentemente, migramos do SVN para o GIT e enfrentamos essa mesma incerteza. Havia muitas evidências anedóticas de que o GIT era melhor, mas era difícil encontrar exemplos.

Posso dizer, porém, que o GIT é MUITO MELHOR na fusão do que o SVN. Isso é obviamente anedótico, mas há uma tabela a seguir.

Aqui estão algumas das coisas que encontramos:

  • O SVN costumava criar muitos conflitos de árvore em situações em que parecia que não deveria. Nós nunca chegamos ao fundo disso, mas isso não acontece no GIT.
  • Embora melhor, o GIT é significativamente mais complicado. Passe algum tempo em treinamento.
  • Estávamos acostumados com o Tortoise SVN, do que gostávamos. O GIT da tartaruga não é tão bom e isso pode desencorajar você. No entanto, agora uso a linha de comando GIT, que eu prefiro muito mais ao Tortoise SVN ou a qualquer uma das GUI do GIT.

Quando avaliamos o GIT, executamos os seguintes testes. Eles mostram o GIT como o vencedor quando se trata de fusão, mas não tanto assim. Na prática, a diferença é muito maior, mas acho que não conseguimos replicar as situações que o SVN lida mal.

Avaliação de mesclagem GIT vs SVN


5

Outros abordaram os aspectos mais teóricos disso. Talvez eu possa emprestar uma perspectiva mais prática.

Atualmente, estou trabalhando para uma empresa que usa SVN em um modelo de desenvolvimento "ramo de recursos". Isso é:

  • Nenhum trabalho pode ser feito no tronco
  • Cada desenvolvedor pode criar suas próprias ramificações
  • As ramificações devem durar a duração da tarefa realizada
  • Cada tarefa deve ter seu próprio ramo
  • Mesclagens de volta ao tronco precisam ser autorizadas (normalmente via bugzilla)
  • Às vezes, quando altos níveis de controle são necessários, as fusões podem ser feitas por um gatekeeper

Em geral, funciona. O SVN pode ser usado para um fluxo como esse, mas não é perfeito. Existem alguns aspectos do SVN que atrapalham e moldam o comportamento humano. Isso dá alguns aspectos negativos.

  • Tivemos alguns problemas com pessoas que ramificam de pontos inferiores a ^/trunk. Essas ninhadas mesclam registros de informações por toda a árvore e, eventualmente, interrompem o rastreamento de mesclagem. Conflitos falsos começam a aparecer e a confusão reina.
  • A captação de alterações do tronco em um galho é relativamente direta. svn mergefaz o que você quer. Para mesclar suas alterações, é necessário que nos digam --reintegrateo comando de mesclagem. Eu nunca entendi verdadeiramente essa opção, mas isso significa que o ramo não pode ser mesclado no tronco novamente. Isso significa que é um ramo morto e você precisa criar um novo para continuar trabalhando. (Veja a nota)
  • Todo o negócio de realizar operações no servidor via URLs ao criar e excluir ramificações realmente confunde e assusta as pessoas. Então eles evitam.
  • É fácil alternar entre galhos, deixando parte de uma árvore olhando para o galho A, enquanto deixa outra parte olhando para o galho B. Portanto, as pessoas preferem fazer todo o seu trabalho em um galho.

O que costuma acontecer é que um engenheiro cria uma filial no dia 1. Ele inicia seu trabalho e esquece. Algum tempo depois, um chefe aparece e pergunta se ele pode liberar seu trabalho para o tronco. O engenheiro teme esse dia porque reintegrar significa:

  • Mesclando seu ramo de longa vida novamente no tronco e resolvendo todos os conflitos, e liberando código não relacionado que deveria estar em um ramo separado, mas não estava.
  • Excluindo sua ramificação
  • Criando uma nova ramificação
  • Mudando sua cópia de trabalho para a nova filial

... e como o engenheiro faz isso o mínimo possível, não consegue se lembrar do "encantamento mágico" para executar cada etapa. Mudanças e URLs incorretos acontecem e, de repente, eles estão confusos e vão buscar o "especialista".

Eventualmente, tudo se acalma, e as pessoas aprendem a lidar com as deficiências, mas cada novo iniciante passa pelos mesmos problemas. A realidade eventual (ao contrário do que expus no início) é:

  • Nenhum trabalho é feito no tronco
  • Cada desenvolvedor tem uma filial principal
  • As ramificações duram até que o trabalho precise ser liberado
  • Correções de erros com bilhetes tendem a ter seu próprio ramo
  • As fusões de volta ao tronco são feitas quando autorizadas

...mas...

  • Às vezes, o trabalho chega ao tronco quando não deveria, porque está no mesmo ramo de outra coisa.
  • As pessoas evitam toda a fusão (até coisas fáceis), então as pessoas geralmente trabalham com suas próprias pequenas bolhas
  • Grandes fusões tendem a ocorrer e causam uma quantidade limitada de caos.

Felizmente, a equipe é pequena o suficiente para lidar, mas não seria escalável. O fato é que nada disso é um problema com o CVCS, mas mais do que isso, porque as mesclagens não são tão importantes quanto no DVCS, elas não são tão espertas. Esse "atrito de mesclagem" causa comportamento, o que significa que um modelo de "Filial de recursos" começa a quebrar. Boas fusões precisam ser um recurso de todos os VCS, não apenas do DVCS.


De acordo com isso , agora existe um --record-onlycomutador que pode ser usado para resolver o --reintegrateproblema e, aparentemente, a v1.8 escolhe quando fazer uma reintegração automática, e isso não causa a morte do ramo depois


Pelo que entendi, a opção --reintegrate diz ao svn que você já resolveu alterações conflitantes ao mesclar com o ramo do recurso. Efetivamente, em vez de tratá-lo como um patch, ele substitui arquivos inteiros pela versão da ramificação, depois de verificar no histórico de mesclagem que todas as revisões de tronco foram mescladas na ramificação.
IMSoP

@ IMSoP: possivelmente, isso faz algum sentido. Porém, isso não me explica por que era necessário ou por que impossibilitou novas fusões desse ramo. Também não ajudou que a opção também não fosse documentada.
Paul S

Eu só o usei através do TortoiseSVN, onde sempre foi explicado com destaque na interface de mesclagem. Acredito que o SVN 1.8 escolhe automaticamente a estratégia correta e não precisa de uma opção separada, mas não sei se eles corrigiram o algoritmo de mesclagem normal para lidar corretamente com um ramo que foi redefinido dessa maneira.
IMSoP

3

Antes do subversion 1.5 (se não me engano), o subversion tinha uma desvantagem significativa, pois não se lembraria do histórico de mesclagem.

Vejamos o caso descrito por VonC:

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

Observe as revisões A e B. Digamos que você mesclou as alterações da revisão A na ramificação "wss" para a ramificação "somente v2" na revisão B (por qualquer motivo), mas continuou usando as duas ramificações. Se você tentasse mesclar os dois ramos novamente usando mercurial, ele só mesclaria as alterações após as revisões A e B. Com o subversion, você precisaria mesclar tudo, como se não tivesse feito uma mesclagem antes.

Este é um exemplo da minha própria experiência, em que a fusão de B para A demorou várias horas devido ao volume de código: isso teria sido uma dor real para passar novamente , o que seria o caso do subversion anterior à 1.5.

Outra diferença provavelmente mais relevante no comportamento de mesclagem do Hginit: Reeducação do Subversion :

Imagine que você e eu estamos trabalhando em algum código, e nós ramificamos esse código, e cada um de nós entra em nossos espaços de trabalho separados e faz muitas e muitas alterações nesse código separadamente, para que tenham divergido bastante.

Quando precisamos mesclar, o Subversion tenta examinar as duas revisões - meu código modificado e seu código modificado - e tenta adivinhar como esmagá-las em uma grande bagunça profana. Geralmente falha, produzindo páginas e "conflitos de mesclagem" que não são realmente conflitos, simplesmente lugares onde o Subversion falhou em descobrir o que fizemos.

Por outro lado, enquanto trabalhamos separadamente no Mercurial, o Mercurial estava ocupado mantendo uma série de conjuntos de alterações. E assim, quando queremos mesclar nosso código, o Mercurial realmente tem muito mais informações: ele sabe o que cada um de nós mudou e pode reaplicar essas alterações, em vez de apenas olhar o produto final e tentar adivinhar como colocá-lo. juntos.

Em suma, a maneira de Mercurial de analisar as diferenças é (foi?) Superior à da subversão.


5
eu li o hginit. Pena que não mostra exemplos mais práticos de onde o hg está se saindo melhor que o svn .. basicamente ele diz para "confiar em joel" que o hg é apenas melhor. os exemplos simples que ele mostrou provavelmente também poderiam ser feitos com o svn ... na verdade, é por isso que abri esta questão.
Stmax

1
Com base em como isso é dito, a pergunta ingênua vem à mente: e se o algoritmo de mesclagem do Mercurial fosse colocado no Subversion? O svn seria tão bom quanto o hg? Não, porque a vantagem do hg está na organização de nível superior, não no nível matemático de texto de mesclar linhas de arquivos. Essa é a nova idéia que os usuários do svn precisam entender.
darenw

@stmax: Eu posso ver o que você quer dizer. No entanto, a opinião de Joel ou de qualquer outra pessoa realmente não importa: uma tecnologia é melhor que a outra (para um conjunto de casos de uso) ou não. @DarenW e @stmax: da minha própria experiência pessoal, o Hg ganha as mãos devido à sua operação distribuída (não estou conectado o tempo todo), desempenho (muitas operações locais), ramificação extremamente intuitiva, superpoderada por um algoritmo de mesclagem superior, reversão hg, saída de log modelada, hg glog, pasta .hg única ... Eu poderia continuar e continuar ... qualquer coisa que não seja talvez git e bazaar parece uma camisa de força.
Tomislav Nakic-Alfirevic

O comentário hg citado sobre "changesets" parece bastante impreciso para mim. O SVN sabe muito bem quais mudanças está mesclando (um conjunto de alterações é basicamente a diferença entre dois instantâneos e vice-versa, certo?) E pode aplicar cada um deles, se assim o desejar; certamente não precisa "adivinhar" nada. Se criar "uma grande bagunça profana", isso será um problema de implementação, e não algo fundamental para o design. O principal problema difícil de resolver sobre o design atual da arquitetura é mover / copiar / renomear arquivos.
IMSoP
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.