aragost Trifork: Mercurial Kick Start Exercises


Desenvolvimento Baseado em Tarefas

Na seção anterior, usamos bookmarks para rastrear várias linhas concorrentes de desenvolvimento (ramos de funcionalidade ou de tópico). Nesta seção, nós vamos explorar um fluxo de trabalho baseado em ramos nomeados.

Ramos nomeados diferem de bookmarka em vários aspectos:

Devido a essas diferenças, um projeto deve documentar como ramos nomeados são usados no projeto. Enquanto que bookmarkas podem ser criados e deletados sem nenhuma consequência, ramos nomeados persistem e compartilham um espaço de nomes global. Isto significa que os ramos se tornam uma parte integral do fluxo de trabalho no projeto e os membros da equipe precisam concordar com seu uso.

Nos exemplos abaixo, a equipe usará uma ferramenta de controle de pedidos de mudança como fonte de referências de longa duração. Embora seja uma configuração comum, alguns projetos apenas usam ramos nomeados para rastrearem ramos de longa duração — eles podem usar clones ou bookmarks para rastrearm consertos menores e ramos de funcionalidades. Desta maneira, evitam a burocracia associada com o uso de ramos nomeados, que são mais “pesados”.

Contents

Visão Geral

Imagine uma equipe pequena em que os desenvolvedores juniores, Alice e Bob, irão trabalhar em tarefas enquanto a desenvolvedora sênior, Carla, revisará e integrará as mudanças feitas pelos dois.

A desenvolvedora sênior será a única com acesso ao repositório central. Ela puxará as mudanças de Alice e Bob e empurrará essas mudanças ao repositório central após revê-las. Alice e Bob puxam as mudanças do repositório central:

flow.png

Usando este fluxo, Carla pode controlar o que vai para o repositório central, a partir do qual o produto final é construído.

Em uma configuração real, os repositórios provavelmente estarão localizados em máquinas diferentes, mas nós os colocaremos lado a lado nos exemplos:

$ ls
alice
bob
carla
main

Isto torna os exemplos mais fáceis.

Trabalhando com um Ramo

Alice começa seu trabalho puxando as mudanças do repositório central:

alice$ hg pull ../main
pulling from ../main
requesting all changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
(run 'hg update' to get a working copy)
alice$ hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Alice tem agora um clone atualizado do repositório central. O projeto consiste de um único arquivo, hello.txt:

alice$ cat hello.txt
Goodbye, World!

Claramente, há um defeito aqui e Carla o registrou no bug tracker como o issue 1. Ela pede a Alice para consertá-lo mudando o texto para Hello, World!, que parece ser mais apropriado.

Criando um Ramo

alice cria um novo ramo para seu trabalho:

alice$ hg branch issue-1
marked working directory as branch issue-1
(branches are permanent and global, did you want a bookmark?)
alice$ hg commit -m "Starting branch for Issue 1." 

O comando hg branch não muda o hitórico nem a cópia de trabalho, mas garante que que a próxima consolidação será “marcada” com issue-1.

A mensagem apresentada é apenas um aviso lembrando que você não pode remover um ramo depois (embora seja possível esconder um ramo se você não precisar mais dele depois). Como descrito na introdução ramos nomeados são persistentes e, portanto, não são recomendados a não ser que realmente se queira rastrear onde um changeset foi feito. Neste projeto a equipe usa o bug tracker como sua primeira referência e nomea os ramos de acordo com o tíquete (issue) em que estiverem trabalhando — desse modo, é fácil encontrar todos as consolidações relacionadas com um defeito depois.

Normalmente, você não pode fazer uma consolidação vazia, mas a mudança de nome do ramo conta como uma mudança, de modo que Alice está apta para fazer a consolidação. O repositório agora tem dois changesets:

alice-branched.png

Note os nomes dos ramos na coluna do resumo. Os nomes são mostrados para os changesets que formam as cabeças de ramos (branch heads). O changeset no ramo default é desenhado com um círculo azul claro e o changeset no ramo issue-1 é desenhado com um círculo verde.

Ela então começa as alterações:

alice$ echo "Hey, World!" > hello.txt
alice$ hg diff
diff -r eb6db6528ec2 hello.txt
--- a/hello.txt Mon Mar 15 10:25:00 2010 +0000
+++ b/hello.txt Mon Mar 15 10:28:00 2010 +0000
@@ -1,1 +1,1 @@
-Goodbye, World!
+Hey, World!
alice$ hg commit -m "Fixed Issue 1." 

O mundo agora se parece com isso:

alice$ hg glog
@  changeset:   2:2cdcbb0a5bfd
|  branch:      issue-1
|  tag:         tip
|  user:        Alice <alice@example.net>
|  date:        Mon Mar 15 10:30:00 2010 +0000
|  summary:     Fixed Issue 1.
|
o  changeset:   1:eb6db6528ec2
|  branch:      issue-1
|  user:        Alice <alice@example.net>
|  date:        Mon Mar 15 10:25:00 2010 +0000
|  summary:     Starting branch for Issue 1.
|
o  changeset:   0:e9eb044d45e0
   user:        Carla <carla@example.net>
   date:        Mon Mar 01 10:20:30 2010 +0000
   summary:     Initial import.

Os dois changesets mais à ponta estão no ramo issue-1. A raiz dos changesets está no ramo chamado default apesar de não haver um nome impresso. Changesets sempre estão em um ramo no Mercurial, mas o nome não é apresentado quando se trata do ramo default para reduzir a quantidade de informação. Note que os changesets nos ramos diferentes compartilham o mesmo grafo de changesets — os nomes dos ramos simplesmente são um modo fácil de se referir à head de um ramo: ao invés de 2cdcbb0a5bfd você escreve issue-1 e ao invés de e9eb044d45e0 basta escrever default. Nesse sentido ramos são como “rótulos móveis” que sempre apontam para o changeset mais à frente de um ramo particular. Esses changesets mais à frente são exatamente aqueles destacados com o círculo verde no thg log:

alice-fixed-issue-1.png

Alice comenta no bug tracker que ela consertou o defeito e que Carla deve puxar as mudanças dela.

Mesclando um Ramo

Carla tem andado trabalhando também. Enquanto Alice consertou o defeito, Carla adicionou um arquivo README:

carla$ hg glog
@  changeset:   1:550bad1893cf
|  tag:         tip
|  user:        Carla <carla@example.net>
|  date:        Tue Mar 02 14:30:00 2010 +0000
|  summary:     Added README.
|
o  changeset:   0:e9eb044d45e0
   user:        Carla <carla@example.net>
   date:        Mon Mar 01 10:20:30 2010 +0000
   summary:     Initial import.

Carla puxa de Alice:

carla$ hg pull ../alice
pulling from ../alice
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads)

O Mercurial diz a ela que há uma nova head no repositório. Isto é visível pelo comando hg heads:

carla$ hg heads
changeset:   3:2cdcbb0a5bfd
branch:      issue-1
tag:         tip
user:        Alice <alice@example.net>
date:        Mon Mar 15 10:30:00 2010 +0000
summary:     Fixed Issue 1.

changeset:   1:550bad1893cf
user:        Carla <carla@example.net>
date:        Tue Mar 02 14:30:00 2010 +0000
summary:     Added README.

Agora há dois ramos no repositório:

carla$ hg branches
issue-1                        3:2cdcbb0a5bfd
default                        1:550bad1893cf

Usando thg log, Carla pode ver como o novo ramo está anexado ao ramo default:

carla-pull-issue-1.png

As duas heads estão claramente visíveis e Carla atualiza para o ramo issue-1:

carla$ hg update issue-1
1 files updated, 0 files merged, 1 files removed, 0 files unresolved

Sua própria cópia de trabalho agora se parece com a de Alice. Carla agora pode examina o arquivo e não fica inteiramente feliz com a saudação “moderna” escolhida por Alice — Carla quer um bom e clássico “Hello” no lugar. Ela argumenta isso no bug tracker e Alice concorda em fazer a mudança. Alice primeiro verifica se ela está no ramo issue-1 e então conserta o defeito:

alice$ hg branch
issue-1
alice$ echo "Hello, World!" > hello.txt
alice$ hg commit -m "Really fix Issue 1." 

Carla puxa a nova mudança:

carla$ hg pull ../alice
pulling from ../alice
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
(run 'hg update' to get a working copy)

Ela pode usar hg update para ir para a ponta do ramo corrente (issue-1) ou ela pode explicitamente dizer hg update issue-1 novamente:

carla$ hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Ela concorda que o defeito está finalmente consertado.

Fechando um Ramo

O ramo não é mais necessário, então ela o marca como fechado:

carla$ hg commit --close-branch -m "Close branch, Issue 1 is fixed." 

Isto faz com que o ramo issue-1 desapareça da listagem de hg branches:

carla$ hg branches
default                        1:550bad1893cf

Se ela não o tivesse fechado, o lista de hg branches facilmente se tornaria ingerenciável. Em seguida ela atualiza para o ramo default e mescla com issue-1:

carla$ hg update default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
carla$ hg merge issue-1
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
carla$ hg commit -m "Merged fix for Issue 1." 

Ao atualizar primeiro para o default, ela garante que o changeset do merge será criado para o ramo default e não no ramo issue-1, onde ele não pertence. Note como a mesclagem de um ramo não é diferente da mesclagem de qualquer outros dois changesets — o Mercurial tem um modelo de histórico muito uniforme.

Empurrando um Ramo

Ela agora empurra a coisa toda para o repositório principal. Ela deve usar a opção --new-branch já que ela está enviando novos ramos nomeados. Isto evita que se empurre prematuramente um ramo nomeado por acidente. Sem a opção, ela recebe um aviso amigável:

carla$ hg push ../main
pushing to ../main
searching for changes
abort: push creates new remote branches: issue-1!
(use 'hg push --new-branch' to create new remote branches)

Note

A opção --new-branch foi introduzida no Mercurial na revisão 1.6. Versões mais antigas requerem o uso da opção --force no lugar. O perigo de user usar --force é que ela desabilita todas as verificações, enquanto que --new-branch não permitirá fazer coisas como empurrar múltiplas heads.

Com certeza, adicionar a opção faz a operação push funcionar:

carla$ hg push --new-branch ../main
pushing to ../main
searching for changes
adding changesets
adding manifests
adding file changes
added 6 changesets with 3 changes to 2 files

O repositório principal está agora neste estado:

carla-merge-issue-1.png

Ramos de Longa Duração

Enquanto Alice estava trabalhando no conserto da issue 1, Cara pediu para que Bob olhasse a tradução do arquivo hello.txt para outros idiomas. Eles concordaram com Dinamarquês, Alemão e Francês e criaram issue 2 para isto. Como Alice, Bob trabalhará no seu próprio ramo e ele começa puxando as mudanças do ramo principal:

bob$ hg pull ../main 
pulling from ../main
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files
(run 'hg update' to get a working copy)
bob$ hg update
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
bob$ hg glog
@  changeset:   1:550bad1893cf
|  tag:         tip
|  user:        Carla <carla@example.net>
|  date:        Tue Mar 02 14:30:00 2010 +0000
|  summary:     Added README.
|
o  changeset:   0:e9eb044d45e0
   user:        Carla <carla@example.net>
   date:        Mon Mar 01 10:20:30 2010 +0000
   summary:     Initial import.

Neste ponto, o ramo de Alice ainda não foi mesclado com o ramo principal, de modo que Bob apenas vê os dois primeiros changesets feitos por Carla. Ele cria um ramo e renomeia o arquivo hello.txt para refletir o idioma:

bob$ hg branch issue-2
marked working directory as branch issue-2
(branches are permanent and global, did you want a bookmark?)
bob$ hg commit -m "Began Issue 2 branch." 
bob$ hg rename hello.txt hello.en.txt
bob$ hg commit -m "English translation." 

Ele rapidamente cria uma versão em dinamarquês e alemão e as consolida:

bob$ echo "Hej, verden!" > hello.da.txt
bob$ echo "Hallo, Welt!" > hello.de.txt
bob$ hg add
adding hello.da.txt
adding hello.de.txt
bob$ hg commit -m "Danish and German translations." 

Ele precisa de mais tempo para pesquisar a tradução para o francês, então ele adia a mudança. Seu grafo de changeset é mostrado abaixo e você pode notar que o ramo issue-2 é apresentado em vermelho:

bob-first-commits.png

Mesclando default em um Ramo

Enquanto isso, Carla mesclou o conserto de Alice no default:

carla-merge-issue-1.png

Este conserto também é adequado para o ramo de Bob, por isso ele o puxa do repositório principal:

bob$ hg pull ../main
pulling from ../main
searching for changes
adding changesets
adding manifests
adding file changes
added 5 changesets with 2 changes to 1 files (+1 heads)
(run 'hg heads' to see heads)
bob-pull-main.png

Ele mescla o ramo default no ramo issue-2 para trazer o conserto ao seu ramo:

bob$ hg merge default
merging hello.en.txt and hello.txt to hello.en.txt
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
bob$ hg commit -m "Merge in bugfix from default." 

Notice how smoothly Alice’s change to hello.txt was merged into the renamed hello.en.txt. Just like a good movie soundtrack, a good merge implementation is characterized by the fact that you do not notice it — it should just work and do the right thing. We will compare this to how Subversion handles this below. The resulting changeset graph looks like this:

bob-merge-bugfix.png

Usando os ramos coloridos, você rapidamente pode ver que o ramo issue-1 em verde foi mesclado com o ramo default em azul, e que a coisa toda foi mesclada com o ramo issue-2 em rosa. As cores são de grande ajuda na análise de históricos complicados.

Bob finalmente descobriu como traduzir o arquivo em Francês, e ele faz a consolidação final antes de pedir à Carla para revisar o ramo.

bob$ echo "Bonjour tout le monde !" > hello.fr.txt
bob$ hg add hello.fr.txt
bob$ hg commit -m "French translation." 

Satisfeito com seu trabalho, ele o submete à Carla, que puxa para o seu repositório:

carla$ hg pull ../bob
pulling from ../bob
searching for changes
adding changesets
adding manifests
adding file changes
added 5 changesets with 5 changes to 4 files
(run 'hg update' to get a working copy)

Seu repositório se parece com isso agora:

carla-pull-issue-2.png

Carla verifica duas vezes os arquivos e decide mesclar o trabalho de Bob agora. Ela atualiza para o ramo dele para fechá-lo, e retorna ao ramo default para mesclar:

carla$ hg update issue-2
4 files updated, 0 files merged, 1 files removed, 0 files unresolved
carla$ hg commit --close-branch -m "Close branch, Issue 2 is fixed." 
carla$ hg update default
1 files updated, 0 files merged, 4 files removed, 0 files unresolved
carla$ hg merge issue-2
4 files updated, 0 files merged, 1 files removed, 0 files unresolved
(branch merge, don't forget to commit)
carla$ hg commit -m "Merged fix for Issue 2." 

O grafo de changeset final mostra como o ramo default (em preto) foi mesclado no ramo issue-2 (em vermelho), antes de issue-2 ter sido mesclado em default novamente:

carla-merge-issue-2.png

Este é um padrão típico para ramos de longa duração: o ramo default é periodicamente mesclado em um ramo mais experimental para propagar as mudanças mais recentes. Mantendo os ramos em sincronia ajuda a tornar as mesclagems mais fáceis. Em cada mesclagem, o Mercurial encontrará o ancestral comum mais próximo e aplicará as mudanças a partir desse ponto. Com mesclagens periódicas, o ancestral comum não ficará muito no passado, e os ramos não ficarão muito para trás um dos outros.

Mesclando Arquivos Renomeados no Subversion

Ao mesclar dois ramos, é crucial poder encontrar o ancestram comum entre os dois ramos. São as mudanças feitas desde este ancestral que precisam ser mescladas no ramo alvo. O Mercurial rastreia a informação desse ancestral automaticamente — ele é uma parte integral do nosso modelo de histórico e você o viu várias vezes no thg log.

O histórico no Subversion não é baseado em um grafo como no Mercurial e por um longo tempo, Subversion não teve suporte para rastreamento de mesclagens. Isto significava que você precisa descobrir manualmente as faixas de revisão corretas para usar com o comando svn merge. Subversion 1.5 e posteriores rastream as mesclagens, mas o suporte é incompleto. A implementação do rastreamento de mudanças é descrito como extremamente complexo no livro ‘Version Control with Subversion’ (“SVN Book”). Em particular, o Subversion não lida bem com mesclagens envolvendo arquivos renomeados.

Acima, nós mesclamos o ramo issue-2 com o default. No default, o arquivo hello.txt tinha sido alterado e no ramo issue-2 tinha sido renomeado para hello.en.txt. O Mercurial fez a coisa certa e aplicou a mudança para hello.en.txt durante a mesclagem. O Subversion, por outro lado, informa uma mensagem sobre um conflito:

$ svn merge --reintegrate file://$HOME/hello/branches/issue-2
--- Merging differences between repository URLs into '.':
A    hello.en.txt
   C hello.txt
Summary of conflicts:
  Tree conflicts: 1

Note também que deve-se usar uma opção especial no Subversion quando um ramo é mesclado de volta ao trunk. Nenhuma opção é necessária no Mercurial pois não há nada inerentemente especial no ramo default (exceto que é, bem, o ramo default).

Enxertando Mudanças

Quando ramos nomeados são usados por muito tempo, costuma acontecer de alguns changesets serem necessários em mais de um ramo. Acima, Bob poderia ter simplesmente puxado todas as mudanças do repositório principal, mas isso frequentemente não é desejável porque a mudança que você quer pode estar misturada com outras.

O cenário típico é um conserto que está consolidado no ramo default, quando deveria realmente estar no ramo da versão estável mais recente. Considere um repositório com este grafo:

carla-graft-before.png

Aqui, dois ramos são usados: default (em azul) para o desenvolvimento em andamento e stable (em verde) para consertos e lançamentos. Você pode ver como stable é mesclado em default depois de um conserto para propagá-lo para o desenvolvimento corrente. Quando um lançamento é feito, a mesclagem é feito na outra direção — de default para stable — e um rótulo é adicionado. O resultado são dois rastros paralelos de desenvolvimento, com o ramo stable sempre contendo um subconjunto das mudanças do ramo default.

A consolidação final conserta um defeito antigo e um pouco depois, Carla percebe que este defeito também está presente na versão 2.0 lançada antes. O changeset deveria ter sido feito no ramo stable. É muito tarde para simplesmente mesclar default em stable, pois isso também mesclaria o changeset experimental.

A solução é usar hg graft para copiar o changeset. Estando no ramo stable ela executa:

carla$ hg graft tip
grafting revision 12

Note

O comando graft foi introduzido no Mercurial 2.0. Em versões anteriores, você pode usar a extensão transplant para copiar os changesets. É uma extensão padrão e você a habilita através das seguintes linhas:

[extensions]
transplant =

no arquivo de configuração. Esta extensão funciona exportando o changeset como um patch e depois aplicando este patch no alvo. Se isto falhar, então você terá de resolver o conflito baseado no arquivo de rejeitos (.rej).

Isto copia o changeset para seu ramo:

carla-graft-after.png

Uma vez que o changeset duplicado tem um passado diferente do original, ele ganhará um novo hash de identificação. A cópia é feita internamente pela mesclagem de três vias e Carla pode resolver os conflitos usando seu programa preferido de resolução de conflitos de três vias. A maior parte dos pequenos consertos podem ser enxertados tranquilamente. Ela pode agora mesclar o ramo stable de volta no default:

carla$ hg update default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
carla$ hg merge stable
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
carla$ hg commit -m "Merge with stable." 

Note que os changesets não são realmente movidos com esta técnica. Em um ambiente distribuído, é quase impossível destruir o histórico. O Mercurial, por padrão, apenas permite acrescentar ao histórico. Isto significa que consertos envolvem a produção de um novo changeset que desfaça o engano. No nosso caso, o conserto foi a duplicação de um changeset. Quando mesclado, essas duplicação são reconciliadas automaticamente:

carla-graft-merged.png

Exercícios

Os grupos serão divididos em equipes com cinco integrantes. Cada grupo terá uma pessoa com o papel de Carla, isto é, que deve puxar dos repositórios dos outros e decidir quando mesclar. Chamaremos essa pessoa de C.

Siga os seguintes passos:

  1. C começa clonando um repositório de exemmplo a partir de:

    https://bitbucket.org/aragost/cantons/
    

    Quando estiver clonado, C inicia o servidor embutido do mercurial com hg serve.

  2. Os outros membros do grupo fazem um clone a partir de C. Você precisará trocar endereços de IP neste ponto. Você pode habilitar a extensão zeroconf, esperar um pouco, e depois executar hg paths. Você poderá ver os repositórios dos outros na lista.

    No Windows, você deve tomar cuidado pois hg serve disponibiliza o repositório na porta 8000 por padrão. O Mercurial tentará nomear o clone algo como 172.16.0.1:8000, que é um nome ilegal no Windows. Portanto, você deve especificar um segundo argumento para hg clone para que o clone tenha um nome apropriado.

  3. No clone, você encontrará um arquivo chamado cantons.txt. É uma lista de abreviações de registros Suíços. As abreviações precisam ser expandidas, por exemplo, a linha que diz:

    * FR
    

    deve ser alterada para:

    * FR: Fribourg
    

    C apontará uma faixa de letras para cada um dos membros do grupo.

  4. Faça um ramo para sua faixa, por exemplo, hg branch range-a-b. Em seguida, exapanda cada abreviação. Faça uma consolidação depois de expandir a abrevisação para uma única letra.

  5. Enquanto as pessoas estiverem ocupada expandindo abrevisações, C adicionará outra seção no arquivo que se parecerá com isso:

    Por Linguagem
    =============
    
    As linguagens oficials da Suiça são Francês, Alemão, Italiano e
    Romanche.
    
    Francês
    -------
    
    Francês é falado em: VD, ...
    
    
    Alemão
    ------
    
    Alemão é falado em: FR, ...
    
    
    Italiano
    --------
    
    Italiano é falado em: GR, ...
    
    
    Romanche
    --------
    
    Romanche é falado em: GR.
    

    Preencha os distritos para Francês, Alemão e Italiano e consolide as mudanças. Espere para que os outros terminem de expandir suas abreviações.

  6. Quando alguém terminar sua tarefa, ele deve usar hg sere e informar a C onde achar suas mudanças, tal como fizemos acima.

  7. C puxará dos repositórios dos outros membros da equipe e mesclará suas mudanças no default. Se todos prestaram atenção e editaram apenas sua seções, então não deverá haver conflitos.

  8. Finalmente, note que a Wikipedia é o lugar para procurar ajuda se você emperrado :-)

Em seguida, experimentaremos como resolver conflitos:

  1. Faça duas pessoas editarem a mesma linha — uma pode adicionar uma linha em um distrito francês, e a outra com o nome Italiano.

  2. O conflito será discoberto quando C puxar as mudanças e executar um hg merge. Dependendo da configuração exata do Mercurial, uma ferramenta de mesclagem será invocada. Tanto no Windows quanto no Linux, KDiff3 é a ferramenta padrão. Tem a seguinte aparência quando iniciada:

    cantons-merge-before.png

    O painel de baixo mostra o resultado da mesclagem, e no momento há três conflitos de mesclagem. Você pode clicar com o botão direito em uma linha de <Merge Conflict> e escolher resolver incluindo as linhas do arquivo B (sua versão local) ou C (a outra versão). No nosso caso, queremos incluir ambas as alterações:

    cantons-merge-after.png

    Salve a saida e saia do KDiff3. O Mercurial notará a saída foi bem sucedida e marcará os conflitos como resolvidos. Verifique com o comando hg resolve --list.

    Se o Mercurial não estiver configurado para inciar a ferramenta de mesclagem, então você precisará editar o arquivo para remover os marcadores de conflito inseridos pelo Mercurial. Use hg resolve --mark cantons.txt quando terminado para marcar o arquivo como resolvido.

  3. Consolide o resultado da mesclagem.

Resumo

Nos mostramos como ramos nomeados podem ser usados para adicionar estrutura a um repositório do Mercurial. Eles facilitam o trabalho em vários ramos distintos de desenvolvimento ao mesmo tempo, sem misturá-los.