O que "branch", "tag" e "trunk" significam nos repositórios do Subversion?


1193

Eu já vi essas palavras bastante nas discussões do Subversion (e acho que o repositório geral).
Eu tenho usado o SVN para meus projetos nos últimos anos, mas nunca entendi o conceito completo desses diretórios.

O que eles querem dizer?


29
Aqui está um bom artigo que me deparei explicando como / quando usar tronco, ramificação e tags. Eu não tinha usado o controle de origem antes, mas este artigo facilitou bastante a compreensão de um noob como eu. Dia-a-dia com Subversion
badmoon 19/08/08

Respostas:


910

Hmm, não tenho certeza se eu concordo com Nick re tag ser semelhante a um ramo. Uma tag é apenas um marcador

  • O tronco seria o corpo principal do desenvolvimento, originário do início do projeto até o presente.

  • A ramificação será uma cópia do código derivado de um determinado ponto no tronco que é usado para aplicar grandes alterações ao código, preservando a integridade do código no tronco. Se as principais alterações funcionam de acordo com o plano, elas geralmente são mescladas de volta ao tronco.

  • A tag será um ponto no tempo no tronco ou em um ramo que você deseja preservar. As duas principais razões para a preservação seria que essa é uma versão importante do software, seja alfa, beta, RC ou RTM, ou esse é o ponto mais estável do software antes da aplicação de grandes revisões no tronco.

Em projetos de código aberto, as principais ramificações que não são aceitas no tronco pelas partes interessadas no projeto podem se tornar a base dos garfos - por exemplo, projetos totalmente separados que compartilham uma origem comum com outro código-fonte.

As subárvores de ramificação e tag são diferenciadas do tronco das seguintes maneiras:

O Subversion permite que os administradores de sistemas criem scripts de gancho que são acionados para execução quando certos eventos ocorrem; por exemplo, confirmar uma alteração no repositório. É muito comum que uma implementação típica do repositório Subversion trate qualquer caminho que contenha "/ tag /" seja protegido contra gravação após a criação; o resultado líquido é que as tags, uma vez criadas, são imutáveis ​​(pelo menos para usuários "comuns"). Isso é feito por meio de scripts de gancho, que reforçam a imutabilidade, impedindo novas alterações se a tag for um nó pai do objeto alterado.

O Subversion também adicionou recursos, desde a versão 1.5, relacionados ao "rastreamento de mesclagem de ramificação", para que as alterações confirmadas em uma ramificação possam ser mescladas novamente no tronco com suporte para mesclagem "inteligente" incremental.


284
A confusão com tags e branches é que no svn realmente não há distinção entre eles, além do nome do diretório. No svn, você é capaz de confirmar alterações em uma tag e, de fato, é difícil evitar isso. A maioria dos outros VCS trata as tags como instantâneos imutáveis ​​(pontos no tempo).
22411 Ken Liu

4
TagsO diretório também é freqüentemente usado para testes e verificação de marcos pelo usuário comum. Este seria um bom lugar para colocar um protótipo também (apenas algumas idéias em cima da minha cabeça).
26612 Jeff Noel

6
@KenLiu Existem ganchos que podem tornar as tags imutáveis. Ou seja, você pode criar e efetuar check-out de uma tag, mas não fazer alterações. Obviamente, uma tag sendo apenas parte do repositório significa que o histórico completo está disponível. Se alguém alterar uma tag, você poderá acompanhar isso e por quê. Em muitos VC, se você modificar uma etiqueta, pode não haver maneira de saber.
David W.

3
Talvez ramificações estáveis devam ser mencionadas: as alterações feitas normalmente não são mescladas de volta ao tronco .
Wolf

4
Meu entendimento é que, em um "mundo perfeito", nenhum desenvolvimento deve acontecer no tronco, o tronco deve sempre ser o código exato que está no ar ou o código que está prestes a ser lançado no ar. como tal, que tornaria os ramos o corpo principal do desenvolvimento.
MikeT

556

Primeiro, como apontam @AndrewFinnell e @KenLiu, no SVN os nomes de diretório não significam nada - "tronco, ramificações e tags" são simplesmente uma convenção comum usada pela maioria dos repositórios. Nem todos os projetos usam todos os diretórios (é razoavelmente comum não usar "tags") e, de fato, nada impede você de chamá-los como quiser, embora a quebra de convenções seja confusa.

Descreverei provavelmente o cenário de uso mais comum de ramos e tags e darei um exemplo de como eles são usados.

  • Tronco : A principal área de desenvolvimento. É aqui que reside o seu próximo grande lançamento do código e, geralmente, possui todos os recursos mais recentes.

  • Ramos : toda vez que você lança uma versão principal, ele cria um ramo. Isso permite que você faça correções de bugs e faça uma nova versão sem precisar liberar os recursos mais recentes - possivelmente inacabados ou não testados.

  • Tags : toda vez que você libera uma versão (release final, release candidate (RC) e betas)), você cria uma tag para ela. Isso fornece uma cópia point-in-time do código como estava naquele estado, permitindo que você volte e reproduza quaisquer bugs, se necessário, em uma versão anterior, ou relance uma versão anterior exatamente como estava. Ramos e tags no SVN são leves - no servidor, ele não faz uma cópia completa dos arquivos, apenas um marcador dizendo "esses arquivos foram copiados nesta revisão" que ocupa apenas alguns bytes. Com isso em mente, você nunca deve se preocupar em criar uma tag para qualquer código liberado. Como eu disse anteriormente, as tags geralmente são omitidas e, em vez disso, um registro de alterações ou outro documento esclarece o número da revisão quando uma liberação é feita.


Por exemplo, digamos que você inicie um novo projeto. Você começa a trabalhar no "trunk", no que será lançado como versão 1.0.

  • trunk / - versão de desenvolvimento, em breve 1.0
  • ramos / - vazio

Depois que a 1.0.0 é concluída, você ramifica o tronco em uma nova ramificação "1.0" e cria uma tag "1.0.0". Agora, o trabalho sobre o que será 1.1 continua no tronco.

  • trunk / - versão de desenvolvimento, em breve 1.1
  • branches / 1.0 - 1.0.0 versão
  • tags / 1.0.0 - 1.0.0 versão

Você encontra alguns bugs no código, corrige-os no tronco e depois mescla as correções na ramificação 1.0. Você também pode fazer o oposto e corrigir os erros na ramificação 1.0 e depois mesclá-los de volta ao tronco, mas geralmente os projetos aderem à fusão unidirecional apenas para diminuir a chance de perder alguma coisa. Às vezes, um bug pode ser corrigido apenas na 1.0 porque é obsoleto na 1.1. Realmente não importa: você só quer ter certeza de que não libera o 1.1 com os mesmos bugs que foram corrigidos no 1.0.

  • trunk / - versão de desenvolvimento, em breve 1.1
  • branches / 1.0 - versão 1.0.1 a ser lançada
  • tags / 1.0.0 - 1.0.0 versão

Depois de encontrar bugs suficientes (ou talvez um bug crítico), você decide executar uma versão 1.0.1. Então você cria uma tag "1.0.1" no ramo 1.0 e libera o código. Neste ponto, o tronco conterá o que será 1.1 e o ramo "1.0" conterá o código 1.0.1. Na próxima vez que você lançar uma atualização para 1.0, seria 1.0.2.

  • trunk / - versão de desenvolvimento, em breve 1.1
  • branches / 1.0 - próximo lançamento da versão 1.0.2
  • tags / 1.0.0 - 1.0.0 versão
  • tags / 1.0.1 - 1.0.1 versão

Eventualmente, você está quase pronto para a versão 1.1, mas deseja fazer uma versão beta primeiro. Nesse caso, você provavelmente cria uma ramificação "1.1" e uma tag "1.1beta1". Agora, o trabalho no que será 1,2 (ou talvez 2,0) continua no tronco, mas o trabalho no 1.1 continua no ramo "1.1".

  • trunk / - versão de desenvolvimento, em breve 1.2
  • branches / 1.0 - próximo lançamento da versão 1.0.2
  • branches / 1.1 - próximo lançamento do 1.1.0
  • tags / 1.0.0 - 1.0.0 versão
  • tags / 1.0.1 - 1.0.1 versão
  • tags / 1.1beta1 - versão 1.1 beta 1

Depois de liberar a versão 1.1 final, você cria uma tag "1.1" na ramificação "1.1".

Você também pode continuar mantendo a 1.0, se desejar, portando correções de bugs entre os três ramos (1.0, 1.1 e tronco). O importante é que, para todas as versões principais do software que você está mantendo, você tem uma ramificação que contém a versão mais recente do código para essa versão.


Outro uso de ramificações é para recursos. É aqui que você ramifica o tronco (ou um de seus ramos de lançamento) e trabalha em um novo recurso isoladamente. Depois que o recurso é concluído, você o mescla novamente e remove a ramificação.

  • trunk / - versão de desenvolvimento, em breve 1.2
  • branches / 1.1 - próximo lançamento do 1.1.0
  • branches / ui-rewrite - ramo de recursos experimentais

A idéia disso é quando você está trabalhando em algo perturbador (que impediria ou interferiria com outras pessoas de fazer seu trabalho), algo experimental (que talvez nem consiga entrar) ou possivelmente apenas algo que demore muito tempo (e você tem medo de que, se estiver segurando uma versão 1.2, quando estiver pronto para ramificar a versão 1.2 do tronco), você poderá fazê-lo isoladamente na ramificação. Geralmente, você o mantém atualizado com o tronco mesclando alterações nele o tempo todo, o que facilita a reintegração (mesclagem de volta ao tronco) quando você terminar.


Observe também que o esquema de versão que usei aqui é apenas um de muitos. Algumas equipes executam versões de correção / manutenção de bug como 1.1, 1.2, etc., e grandes alterações como 1.x, 2.x, etc. O uso aqui é o mesmo, mas você pode nomear o ramo "1" ou "1 .x "em vez de" 1.0 "ou" 1.0.x ". (Além disso, o controle de versão semântico é um bom guia sobre como executar números de versão).


6
@ Baruch - Isso está completamente errado. As tags são leves e são (no que diz respeito ao próprio Subversion) idênticas às ramificações.
Josh Kelley

7
Ame os detalhes do caso de uso. Obrigado @gregmac.
Jeromy French

2
Posso obter uma cotação de onde é declarado que as tags / ramificações são leves? Não parece que maneira ..
Cardin Lee JH

3
Esta deve ser a resposta aceita, que é muito melhor ^^ #
G Nam Nam VU

4
@ Cardin Eu não tenho referência no momento, mas é importante observar que as tags são leves no servidor, mas não no cliente. Se você fizer o check-out de todas as tags, receberá muitas cópias completas. No entanto, se você observar o tamanho do repositório no servidor, ele aumentará apenas alguns bytes por tag. É por isso que você não deve fazer check-out no diretório raiz, de um modo geral.
gregmac

97

Além do que Nick disse, você pode descobrir mais em Streamed Lines: Padrões de ramificação para desenvolvimento de software paralelo

insira a descrição da imagem aqui

Nesta figura mainestá o tronco, rel1-mainté um ramo e 1.0é uma etiqueta.


1
@ Wolf ele poderia ser - esse diagrama é bastante genérico, independentemente das ferramentas. Todos os SCMs usam palavras diferentes, mas os mesmos conceitos, não há diferença entre tronco e Principal; ou tronco e mestre. Esse diagrama mostra como minha empresa atual usa SVN.
Gbjbaanb

@gbjbaanb Obrigado por compartilhar. ... e as tags parecem não ser abordadas pela pergunta. É pura coincidência (também na sua empresa atual) que nenhuma fusão passe das filiais principais para as mantidas?
Wolf

@ Wolf Não é coincidência - apenas ramifique do tronco, trabalhe, volte ao tronco. Em seguida, ramifique o tronco para um ramo de tag. Estamos considerando outro 'tronco' chamado Integração que concluiu ramificações mescladas a ele para testes que não constituem uma liberação; o tronco ainda é usado para as ramificações que decidimos colocar na próxima liberação. A única vez que você mescla do tronco para um ramo é atualizar um ramo de longa execução, mas é melhor (e mais fácil) simplesmente criar um novo ramo fora do tronco e mesclar as alterações do ramo antigo, se você precisar.
Gbjbaanb 27/05

75

Em geral (visão independente da ferramenta), um ramo é o mecanismo usado para o desenvolvimento paralelo. Um SCM pode ter de 0 a n ramificações. O Subversion possui 0.

  • Trunk é um ramo principal recomendado pelo Subversion , mas você não é obrigado a criá-lo. Você poderia chamá-lo de 'principal' ou 'lançamentos', ou não ter um!

  • O ramo representa um esforço de desenvolvimento. Nunca deve ser nomeado após um recurso (como 'vonc_branch'), mas depois:

    • uma finalidade 'myProject_dev' ou 'myProject_Merge'
    • um perímetro de liberação 'myProjetc1.0_dev'ou myProject2.3_Merge' ou 'myProject6..2_Patch1' ...
  • Tag é uma captura instantânea de arquivos para retornar facilmente a esse estado. O problema é que tag e branch são os mesmos no Subversion . E eu recomendaria definitivamente a abordagem paranóica:

    você pode usar um dos scripts de controle de acesso fornecidos com o Subversion para impedir que alguém faça qualquer coisa, exceto criar novas cópias na área de tags.

Uma tag é final. Seu conteúdo nunca deve mudar. NUNCA. Sempre. Você esqueceu uma linha na nota de lançamento? Crie uma nova tag. Obsoleto ou remova o antigo.

Agora, eu li muito sobre "mesclar de volta tal e tal em tais e tais ramos, e finalmente no ramo tronco". Isso é chamado de fluxo de trabalho de mesclagem e não há nada obrigatório aqui . Não é porque você tem um ramo de tronco que você precisa mesclar de volta qualquer coisa.

Por convenção, o ramo do tronco pode representar o estado atual do seu desenvolvimento, mas isso é para um projeto seqüencial simples, que é um projeto que possui:

  • nenhum desenvolvimento 'antecipado' (para a preparação da próxima versão seguinte implicando alterações que não sejam compatíveis com o atual desenvolvimento 'tronco')
  • sem refatoração maciça (para testar uma nova opção técnica)
  • sem manutenção a longo prazo de uma versão anterior

Como em um (ou todos) desses cenários, você obtém quatro 'troncos', quatro 'desenvolvimentos atuais' e nem tudo o que faz nesse desenvolvimento paralelo necessariamente precisa ser mesclado de volta no 'tronco'.


38

No SVN, uma marca e um ramo são realmente semelhantes.

Tag = uma fatia definida no tempo, geralmente usada para liberações

Ramificação = também uma fatia definida no tempo em que o desenvolvimento pode continuar, geralmente usado para versões principais como 1.0, 1.5, 2.0, etc; quando você libera, marca a ramificação. Isso permite que você continue a oferecer suporte a uma liberação de produção enquanto avança com alterações no tronco

Tronco = espaço de trabalho de desenvolvimento; é aqui que todo o desenvolvimento deve ocorrer e, em seguida, as alterações são mescladas novamente nas liberações de ramificação.


30

Eles realmente não têm nenhum significado formal. Uma pasta é uma pasta para o SVN. Eles são uma maneira geralmente aceita de organizar seu projeto.

  • O tronco é onde você mantém sua linha principal de desenvolvimento. A pasta do ramo é onde você pode criar, bem, ramos, difíceis de explicar em uma publicação curta.

  • Uma ramificação é uma cópia de um subconjunto do seu projeto no qual você trabalha separadamente do tronco. Talvez seja para experimentos que talvez não cheguem a lugar algum, ou talvez seja para a próxima versão, que mais tarde você voltará a entrar no tronco quando ele se tornar estável.

  • E a pasta tags é para criar cópias marcadas do seu repositório, geralmente nos pontos de verificação de lançamento.

Mas como eu disse, para o SVN, uma pasta é uma pasta. branch,trunk e tag são apenas uma convenção.

Estou usando a palavra 'copiar' liberalmente. O SVN não faz cópias completas das coisas no repositório.


13

O tronco é a linha de desenvolvimento que contém o código-fonte e os recursos mais recentes. Ele deve ter as correções mais recentes, bem como os recursos mais recentes adicionados ao projeto.

Os galhos geralmente são usados ​​para fazer algo fora do tronco (ou outra linha de desenvolvimento) que, de outra forma, quebraria a construção. Novos recursos geralmente são criados em uma ramificação e depois mesclados de volta ao tronco. As ramificações geralmente contêm código que não é necessariamente aprovado para a linha de desenvolvimento da qual ramificou. Por exemplo, um programador pode tentar uma otimização em algo em uma ramificação e voltar à linha de desenvolvimento apenas quando a otimização for satisfatória.

As tags são capturas instantâneas do repositório em um momento específico. Nenhum desenvolvimento deve ocorrer sobre eles. Eles costumam ser usados ​​para tirar uma cópia do que foi lançado para um cliente, para que você possa acessar facilmente o que um cliente está usando.

Aqui está um link para um guia muito bom para repositórios:

Também vale a pena ler os artigos da Wikipedia.


12

Agora, esse é o problema do desenvolvimento de software, não há conhecimento consistente sobre nada, todo mundo parece ter seu próprio caminho, mas é porque, de qualquer maneira, é uma disciplina relativamente jovem.

Aqui está o meu caminho simples e simples,

tronco - O diretório de tronco contém o corpo de trabalho mais atual, aprovado e mesclado. Ao contrário do que muitos confessaram, meu tronco é apenas para trabalho limpo, arrumado e aprovado, e não uma área de desenvolvimento, mas uma área de liberação.

Em um determinado momento, quando o tronco parece pronto para ser liberado, é marcado e liberado.

galhos - O diretório branches contém experimentos e trabalho em andamento. O trabalho em uma filial permanece lá até que seja aprovado para ser mesclado no tronco. Para mim, esta é a área em que todo o trabalho é feito.

Por exemplo: posso ter uma ramificação de iteração-5 para uma quinta rodada de desenvolvimento do produto, talvez uma ramificação de protótipo-9 para uma nona rodada de experimentação e assim por diante.

tags - O diretório tags contém instantâneos de ramificações aprovadas e liberações de tronco. Sempre que uma ramificação é aprovada para mesclar no tronco ou uma liberação é feita, uma captura instantânea da ramificação ou liberação de tronco aprovada é feita em tags.

Suponho que, com as tags, eu possa ir e voltar no tempo para apontar o interesse com bastante facilidade.


10

Encontrei este ótimo tutorial sobre SVN quando estava consultando o site do autor do livro de receitas de programação de aplicativos OpenCV 2 Computer Vision e achei que deveria compartilhar.

Ele tem um tutorial sobre como usar o SVN e o que significam as frases 'tronco', 'tag' e 'ramificação'.

Citado diretamente de seu tutorial:

A versão atual do seu projeto de software, na qual sua equipe está trabalhando atualmente, geralmente está localizada em um diretório chamado tronco . À medida que o projeto evolui, o desenvolvedor atualiza essa versão, corrige bugs, adiciona novos recursos) e envia suas alterações nesse diretório.

A qualquer momento, você pode congelar uma versão e capturar uma captura instantânea do software, como está neste estágio do desenvolvimento. Isso geralmente corresponde às versões oficiais do seu software, por exemplo, as que você entregará aos seus clientes. Esses instantâneos estão localizados no diretório de tags do seu projeto.

Por fim, geralmente é útil criar, em algum momento, uma nova linha de desenvolvimento para o seu software. Isso acontece, por exemplo, quando você deseja testar uma implementação alternativa na qual precisa modificar seu software, mas não deseja enviar essas alterações ao projeto principal até decidir se adota a nova solução. A equipe principal pode continuar trabalhando no projeto, enquanto outros desenvolvedores trabalham no protótipo. Você colocaria essas novas linhas de desenvolvimento do projeto em um diretório chamado branches .


9

O diretório de tronco é o diretório com o qual você provavelmente está mais familiarizado, porque é usado para armazenar as alterações mais recentes. Sua principal base de código deve estar no porta-malas.

O diretório branches é para armazenar seus branches, sejam eles quais forem.

O diretório de tags é basicamente para marcar um determinado conjunto de arquivos. Você faz isso em itens como releases, nos quais deseja "1.0" como esses arquivos nessas revisões e "1.1" como esses arquivos nessas revisões. Você geralmente não modifica as tags depois que elas são feitas. Para obter mais informações sobre tags, consulte o Capítulo 4. Ramificação e mesclagem (no Controle de versão com Subversion ).


9

Uma das razões pelas quais todos têm uma definição um pouco diferente é porque o Subversion implementa suporte zero para ramificações e tags. Subversion basicamente diz: Nós olhamos full-featured ramos e rótulos em outros sistemas e não encontrei-los úteis, por isso não implementar qualquer coisa. Basta fazer uma cópia para um novo diretório com um nome convenção vez . Então, é claro, todo mundo é livre para ter convenções um pouco diferentes. Para entender a diferença entre uma tag real e uma simples convenção de cópia + nomeação, consulte a entrada da Wikipedia Tags e ramificações do Subversion .


8

Tag = uma fatia definida no tempo, geralmente usada para liberações

Eu acho que isso é o que normalmente se entende por "tag". Mas no Subversion:

Eles realmente não têm nenhum significado formal. Uma pasta é uma pasta para o SVN.

o que acho bastante confuso: um sistema de controle de revisão que não sabe nada sobre ramos ou tags. Do ponto de vista da implementação, acho que a maneira do Subversion de criar "cópias" é muito inteligente, mas eu ter que saber sobre isso é o que eu chamaria de abstração com vazamento .

Ou talvez eu esteja usando o CVS por muito tempo.


Uma perspectiva alternativa é que o oposto é verdadeiro, que impor o conceito de tags no modelo de objeto do subversion seria uma abstração com vazamento na direção oposta. Como eu acho que você sabe, a subversão foi uma reação ao CVS, uma tentativa de "fazer o CVS corretamente". Não consegui encontrar a referência, mas os designers originais do subversion disseram que lançaram o conceito de tags 100% deliberadamente, que a distinção entre ramos e tags é puramente uma questão de política. Se as equipes querem impor políticas e convenções sobre o modelo de objeto do subversion, que assim seja. É exatamente o que temos hoje.
Darryl

6

Eu acho que parte da confusão vem da diferença entre o conceito de uma tag e a implementação no SVN. Para o SVN, uma tag é um ramo que é uma cópia. A modificação de tags é considerada incorreta e, de fato, ferramentas como o TortoiseSVN o alertarão se você tentar modificar algo com ../tags/ .. no caminho.


5

Não tenho muita certeza do que é 'tag', mas branch é um conceito de controle de origem bastante comum.

Basicamente, uma ramificação é uma maneira de trabalhar com alterações no código sem afetar o tronco. Digamos que você queira adicionar um novo recurso que seja bastante complicado. Você deseja poder verificar as alterações à medida que as fizer, mas não quer que isso afete o tronco até que você termine o recurso.

Primeiro você criaria uma ramificação. Esta é basicamente uma cópia do tronco a partir do momento em que você criou o ramo. Você faria todo o seu trabalho no ramo. Quaisquer alterações feitas na ramificação não afetam o tronco, portanto o tronco ainda é utilizável, permitindo que outras pessoas continuem trabalhando lá (como fazer correções ou pequenos aprimoramentos). Depois que o recurso estiver pronto, você integrará o ramo novamente ao tronco. Isso moveria todas as suas alterações do ramo para o tronco.

Existem vários padrões que as pessoas usam para ramificações. Se você tiver um produto com várias versões principais sendo suportadas ao mesmo tempo, geralmente cada versão seria uma ramificação. Onde trabalho, temos um ramo de controle de qualidade e um ramo de produção. Antes de liberar nosso código para o controle de qualidade, integramos as alterações à filial do controle de qualidade e implantamos a partir daí. Ao liberar para produção, integramos do ramo de controle de qualidade ao ramo de produção, para que saibamos que o código em execução na produção é idêntico ao que o controle de qualidade testou.

Aqui está a entrada da Wikipedia sobre ramos , pois eles provavelmente explicam as coisas melhor do que eu. :)


4

Tronco : Após a conclusão de cada sprint em modo ágil, lançamos um produto parcialmente expedível. Esses lançamentos são mantidos no porta-malas.

Ramificações : todos os códigos de desenvolvimento paralelo para cada sprint em andamento são mantidos em ramificações.

Tags : sempre que lançamos um tipo de versão beta de produto parcialmente expedível, fazemos uma tag para ele. Isso nos fornece o código que estava disponível naquele momento, permitindo que voltássemos nesse estado, se necessário em algum momento do desenvolvimento.


Este é o seu fluxo de trabalho específico, não é aplicável em geral.
Jason S

4

Para pessoas familiarizadas com o GIT, o mestre no GIT é equivalente ao tronco no SVN.

Ramificação e tag têm a mesma terminologia no GIT e no SVN.

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.