Estratégia de versão ágil
Obter através da App Store Leia esta publicação em nosso aplicativo!
Abordagens de controle de versão em Scrum [fechado]
Recentemente, com meus colegas de trabalho, discutimos como organizar o controle de versão em um projeto Scrum. Mais especificamente, os critérios para a criação de filiais (por desenvolvedor, por tarefa, por história, por Sprint?) E os métodos de integração.
Minha opinião era que uma maneira útil de organizá-lo é criar um ramo para cada História do Usuário, para que você possa integrar cada História no tronco removível assim que for concluída e também permite que você sempre tenha uma "versão entregue" do aplicativo a qualquer momento.
Então, se uma história não puder ser concluída, pode ser deixada de fora e não compromete a versão do sprint. (Que considerando uma ferramenta centralizada, pode ser se usando uma distribuída, as considerações seriam diferentes)
Gostaria de conhecer suas próprias abordagens, qual o tipo de ferramentas que você prefere e os prós e contras que você viu com a experiência e as lições aprendidas.
fechado como primeiramente baseado em opinião, por Robin Green, Paul Roub, Skatox, Qiu, Cory Charlton 18 de agosto de 15 às 19:06.
Muitas boas perguntas geram algum grau de opinião com base na experiência de especialistas, mas as respostas a esta pergunta tendem a ser quase inteiramente baseadas em opiniões, ao invés de fatos, referências ou conhecimentos específicos. Se esta questão pode ser reformulada para ajustar as regras no centro de ajuda, edite a questão.
Mantenha o protocolo de ramificação leve. No Scrum, se alguém quiser ramificar o código para trabalhar em um recurso específico, deixe-os. Ninguém deve ter medo de se ramificar. Este é um dos benefícios do DVCS - ramo de pessoas quando eles querem e não são tendenciosos pelas regras ou padrões da equipe.
Para o meu dinheiro, é caso a caso, e se você começar a ver padrões em seus processos de desenvolvimento, formalize-os para que todos estejam na mesma página.
Apenas certifique-se de que cada desenvolvedor entende que é sua responsabilidade integrar e mesclar suas mudanças. Isso deve definir a barra em torno do lugar certo para garantir que as pessoas façam a chamada certa quanto ao momento de se ramificar o código.
Um ramo por história de usuário soa bastante excessivo para mim. Nós mantemos uma única base de código (tronco) e trabalhamos com isso. A única vez que normalmente nos ramificamos é consertar um problema de produção atual que não poderia esperar até uma versão regular.
Esse é um tópico muito interessante.
Nós sempre reforçamos a ramificação por criação de tarefas, de fato, cada tarefa (não história, mas tarefas reais como decompostas após a reunião de planejamento de Scrum) terá pelo menos uma ramificação associada.
Você pode ver como parece no seguinte diagrama:
Isso faz com que as coisas gostem de encorajar opiniões de pares muito fáceis, já que a equipe pode verificar o que foi modificado em uma tarefa (ramo), mesmo quando os desenvolvedores decidiram fazer muitos compromissos intermediários (o que é uma prática muito boa!)
Há uma série de links abaixo que podem ser úteis:
Sempre que temos uma história ou um conjunto de histórias que ameaçam deixar o ramo mestre em desordem durante vários dias ou envolver "muitos" desenvolvedores, criamos um ramo para isso (não é muito comum, tentamos fazer tarefas para evitar isso, mas isso acontece) como uma espécie de coisa de mitigação de risco. Queremos ter certeza de que o ramo principal está sempre pronto para a liberação no final de cada sprint, mesmo que isso signifique potencialmente que não possamos aumentar o valor do ramo principal depois do sprint.
O ramo da história / função / tarefa é sincronizado contra o ramo principal com muita frequência, e o objetivo é sempre ter o ramo fundido novamente bem antes do final do sprint.
Claro, todos usamos 'git', então, de fato, sempre temos uma filial local em que trabalhamos, e nos tornamos muito bons em sincronizar com o mestre com freqüência suficiente para evitar integrações big-bang e raramente o suficiente para não deixar inútil / código não utilizado no ramo mestre.
Além disso, fazemos "ramificação por propósito" (PDF). Eu também escrevi um pouco mais sobre como fazemos aqui.
Gostaria de usar um ramo por versão e usar a Integração Contínua para evitar que uma história de usuário prejudique os outros.
A única alteração que você deve fazer no seu sistema de controle de origem é integrá-lo ao sistema de integração contínua (como o TeamCity ou o CruiseControl).
Sim, eu sei que não estou realmente respondendo sua pergunta, mas eu realmente quero dizer isso. No projeto de software ágil, você deseja liberar o produto aos clientes (ou ser capaz de) o mais rápido possível. É por isso que você precisa saber que o que está em seu sistema fonte está funcionando ou se não está funcionando por que não está funcionando.
Não vejo como um ramo por recurso pode torná-lo mais ágil ou magra. O custo do gerenciamento de filiais é muito alto. Eu argumentaria que, se você sentir que precisa de um ramo por recurso, suas histórias são muito grandes. Uma história deve ser completada pelo próximo scrum e, se não, certamente pelo próximo. Então, se uma filial existe apenas por 1-2 dias, não vejo como isso pode ser útil ou seu custo reembolsado. É rotina para muitas pessoas trabalharem em uma história, então eu às vezes uso um ramo de desenvolvedores para que os desenvolvedores trabalhem para que possam mesclar o código Muitas vezes ao dia enquanto trabalham na mesma história sem que o código seja implantado para testar (ramo principal) . Idealmente, você teria poucas histórias em andamento e as completaria tão rápido que você só precisaria do ramo principal e nenhum outro.
Estratégia de versão ágil
Obter através da App Store Leia esta publicação em nosso aplicativo!
Controlando números de versão em sprints [fechado]
Tradicionalmente, os números de compilação de software se encaixam no formato.
Onde uma versão principal é implementada sempre que há mudanças de quebra, Menor quando novos recursos mini são adicionados, Libere quando algo for publicado e seja construído sempre que for construído.
Eu acho que os dois últimos dígitos funcionam muito bem em um ambiente de CI, cada construção de CI aumenta o número de compilação e cada versão para o Live incrementa o número de lançamento.
No entanto, o grande trabalho de cachoeira e os desenvolvimentos de mudança de ruptura são desencorajados em metodologias mais modernas. Nós preferimos liberar pouco e muitas vezes e, portanto, não tendem a fazer grandes mudanças de ruptura. Dado isto, é muito difícil determinar quando criar uma nova versão principal.
Nós tentamos evitar fazer grandes mudanças de ruptura e, assim, estamos conseguindo números muito baixos e menores, mas não tivemos a desculpa para avançar ainda uma versão importante. Que critérios devem ser usados para determinar quando as construções maiores e menores devem ser feitas (particularmente aplicativos baseados na web)?
Estou ciente de que há versões de estilo de data, mas estou interessado em Major. Minor. Build. Release apenas.
fechado como baseado principalmente em opinião de Michael Kohne, Dan Pichelman, MichaelT, mattnz, Dynamic Jul 2 '14 às 15:03.
Muitas boas perguntas geram algum grau de opinião com base na experiência de especialistas, mas as respostas a esta pergunta tendem a ser quase inteiramente baseadas em opiniões, ao invés de fatos, referências ou conhecimentos específicos. Se esta questão pode ser reformulada para ajustar as regras no centro de ajuda, edite a questão.
Eu acho que algum dos desafios começa com sua afirmação de:
Onde uma versão principal é implementada sempre que houver mudanças de quebra.
E tenho certeza de que você quer dizer "quebrar mudanças" no sentido de mudanças significativas na API; Mudanças na comunicação cliente / servidor; protocolo de trabalho, etc. "Big Stuff" (TM) em outras palavras.
Mas o problema é que não é apenas quebrar as mudanças que contam, mas qualquer mudança significativa que conta. "Significativo" varia de aplicativo para aplicativo, e é por isso que é difícil ser específico sobre o que se qualifica como significativo. Alterar o número principal deve ser um anúncio informal para os usuários finais que:
Algo grande é diferente. Eles devem preparar ou estar cientes de que isso provavelmente não será uma atualização trivial.
Então, aqui estão algumas das razões comuns que eu vi atualizar o número principal:
Marketing (ou desenvolvimento) sentiu como ele. Wicked legal, novo, grande recurso introduzido. Ou todo um monte disso. O esquema de banco de dados sofreu mudanças significativas. Muitas vezes isso irá forçar uma atualização para os usuários finais. Protocolo muda. Isso pode incluir mudanças significativas entre o navegador e o servidor da Web, ou pode estar entre o cliente e o servidor. A estabilidade da aplicação melhorou significativamente (ou piorou) e você deseja alterar a versão principal para informar ou alertar os usuários sobre a mudança. Alguma quantidade específica de tempo já passou - isso pode ser X meses, anos, seja o que for. O produto agora é compatível com algum padrão externo ou processo de auditoria. A aplicação foi bifurcada. Mudanças significativas foram feitas no suporte de bibliotecas, ou quais bibliotecas poderiam ser usadas.
De um modo geral, você quer dizer por que você aumentou o valor principal. É aqui que construir um "passo de elevador" para justificar o incremento pode ser útil. E, assim como um passo de elevador que você usa para se vender, esta é uma declaração rápida que resume o que era grande o suficiente para merecer a mudança.
Alguns pensamentos adicionais sobre o controle de versão.
Primeiro, você não precisa atualizar o valor principal de forma regular. Pode não fazer sentido se tudo o que está sendo fornecido é um monte de pequenos itens.
Se você sentir que está "ficando sem espaço" com os valores dos outros números, considere:
Use valores hexadecimais (base 16) ou outra base em vez de apenas valores decimais (base 10). Use dois ou mais dígitos dentro de cada valor. Então, você teria M. mm. rr. bbb, por exemplo.
Se o usuário do seu software não puder atualizar para a nova versão e usá-lo sem outras etapas de migração manual (dados, configuração, interfaces e similares), esse é um indicador forte para aumentar a versão principal.
Seja o que for que você esteja usando internamente, externamente você ainda terá uma cachoeira como o ciclo de liberação.
Quer se chame agora de "lançamento de recursos", "lançamento para produção", "versão de entrega do cliente", ou seja, será o produto combinado de uma série de sprints e ciclos internos mais curtos.
Estratégia de versão ágil
Obter através da App Store Leia esta publicação em nosso aplicativo!
Como você faz a numeração da versão em um projeto ágil? [fechadas]
Atualmente, estamos usando o seguinte esquema de numeração de versão para o nosso projeto C # winforms:
"Major Release". "Minor Release". "Número de iterações". "Número de compilação dentro dessa iteração"
Queríamos ser capazes de identificar o número de iteração e o número de compilação dentro dessa iteração, apenas observando o número da versão.
No passado, fizemos algo como: "Major Release". "Minor Release". "Sequential Build Number from 1.0". Por exemplo, "4.0.648" significaria que havia 648 compilações desde 1.0 - mas esta informação é bastante inútil e anedótica, e é por isso que mudamos para refletir as iterações e as construções dentro das iterações.
Portanto, considerando esta nova numeração de versão ágil, agora temos o problema em que um grupo de produtos diferente gostaria de fazer mudanças em sua iteração para o nosso projeto. Nessa instância, o número da versão não faz sentido, porque seus números de iteração e compilação não correspondem. Por exemplo, a última compilação do meu projeto foi 1.0.5.1 indicando a 1ª versão da iteração 5. Agora, esse outro projeto que na terceira iteração gostaria de fazer alterações no meu projeto e reconstruir.
Como eu deveria lidar com essa situação? Como você faz numeração de versão em seu projeto ágil?
fechado como primeiramente baseado em opinião por EJoshuaS, robinCTS, MikeT, Luke Peterson, Steven Scott 14 de novembro de 17 às 20:10.
Muitas boas perguntas geram algum grau de opinião com base na experiência de especialistas, mas as respostas a esta pergunta tendem a ser quase inteiramente baseadas em opiniões, ao invés de fatos, referências ou conhecimentos específicos. Se esta questão pode ser reformulada para ajustar as regras no centro de ajuda, edite a questão.
Eu acompanho a iteração dos projetos ágeis e não a iteração dos projetos de software. Se um projeto do lado do iniciante atrasado se juntar após outro projeto, ele iniciará com a atual iteração do projeto ágil, e não haverá desalinhamento.
Não deve ser possível que um projeto técnico fora do domínio do projeto ágil interaja com um projeto dentro do domínio. Isso seria uma falha PM do processo e deveria ser eliminada em todos os casos em que uma base de código compartilhada seja usada com ramificação, para ser corrigida no tronco como um passo de limpeza após a conclusão do projeto.
Pessoalmente, acho que o controle de versão do lançamento que eu gostei do melhor é acabar com todo o assunto principal. Eu acho que isso só é realmente viável para aplicações internas, mas para isso, torna a vida muito mais fácil.
Normalmente, se você está desenvolvendo aplicativos voltados para o interior, notei que o negócio nunca se importa com a versão principal / menor que eles estão usando. Em vez disso, eles tendem a querer saber a) quando é a próxima versão e b) o que vai ser dentro ou fora - e é sobre isso. Tentando manter o fato de que você está trabalhando no FOO-4.34.0.1-a e BAR-3.19.4.1 quando ninguém se importa apenas serve para complicar a comunicação.
Em um grupo anterior, não tivemos grandes lançamentos além de um lançamento inicial do projeto. Cada lançamento foi tão importante quanto o anterior.
Como resultado, eu acho que eles fizeram o senso e, em vez disso, se comunicaram com o negócio como PROJECT_RELEASENUM. O número de lançamento incrementado por '1' toda vez que fizemos uma versão, com patches como PROJECT_RELEASENUM_PATCHNUM, que também foi incrementado por '1'.
Funciona bem com a noção de que o desenvolvimento é feito como uma série contínua de sprints até que o negócio tenha toda a funcionalidade que eles precisam (o que na prática nunca acontece - sempre há algo mais que eles querem). Os empresários entenderam, os desenvolvedores poderiam comunicá-lo, e se emprestou naturalmente ao modelo de desenvolvimento contínuo que possuímos.
Eu prefiro Major. Minor. Build. Revision onde Build - o número de lançamentos públicos, Revision - uma revisão do sistema de versão de origem.
Eu prefiro separar o processo de compilação e lançamento do processo de desenvolvimento da equipe, então eu quase não adicionaria a iteração, sprint ou similar à versão. Seu caso é um bom exemplo sobre como ter ambas as coisas misturadas não é fácil de gerenciar. E se você mudar a metodologia no meio do projeto (qualquer que seja o motivo)?
Estratégia de versão ágil
Obter através da App Store Leia esta publicação em nosso aplicativo!
Versionamento semântico em Agile.
Digamos que eu tenho 14 dias de iteração de sprint, onde eu tenho várias histórias para novos recursos, poucas melhorias e alguns erros para consertar. Eu também implanto essas mudanças quando estão prontas, eu não estou aguardando o fim do sprint.
Meu problema é - como controlar o controle de versão semântico de produtos desenvolvidos e mantidos assim? Se houver lançamento a cada 14 dias, será fácil, aumentarei o número da versão e anotarei todas as alterações no changelog. Mas e se as mudanças forem implantadas de forma contínua? Deveria haver uma versão aumentada sempre que algo for implantado? Ou devo esperar até que Sprint termine e depois disso, faça algum "currículo" e aumente o número da versão apenas uma vez por iteração de forma independente na implantação real? Quais são as melhores práticas para o controle de versão semântico no Agile?
EDITAR: Para melhor explicar minhas necessidades, eu quero uma mudança para as partes interessadas em primeiro lugar. Eu não acho que eles estarão interessados em novo registro em changelog após cada mudança implantada.
Para o gerenciamento de lançamento típico, você quer que um número de compilação seja gerado pelo seu sistema de compilação para que as DLLs sejam versionadas sempre que forem implantadas. Isso garantirá que você poderá verificar a versão que é implantada em um determinado servidor.
Sua versão de "marketing", que geralmente é colocada em notas de versão ou publicada em seu site, não deve ser atualizada em cada versão. Essas notas de lançamento devem ser acumuladas e agrupadas, provavelmente programadas com o final do seu sprint.
Se o clássico esquema de versão semântica "MAJOR. MINOR. PATCH" faz sentido, depende de quem você implantar e, especialmente, quando e com que frequência você se implanta para o usuário final. O esquema é mais útil se você trabalhar com versão estável "4.5", onde você começa com 4.5.0. As versões 4.5.1, 4.5.2, e assim por diante, contêm apenas correções de bugs, enquanto você internamente já trabalha na versão 4.6.
Por exemplo, se você fornecer um "ramo estável" ao seu usuário final, dê uma versão 4.5.0 para a implantação inicial e 4.5.1, 4.5.2 sempre que você liberar um patch. No seu desenvolvimento interno "ágil" e na implementação do meio do sprint, você já pode ter uma versão 4.6, basta chamá-la de "versão beta". Sempre que você o implanta no meio do sprint, adicione o número de compilação gerado automaticamente como "4.6.beta build 123". Quando o seu sprint termina, atribua-o "4.6.0" e mude o número da versão para o próximo sprint internamente para "4.7". Começar com ".0" é apenas uma convenção, você também pode usar o ".0" para etiquetar versões beta e começar com ".1" para seus usuários finais. IMHO a palavra "beta" é muito mais expressiva, dizendo a todos que o sprint "ainda não está completo".
Se você liberar um registro completo de alterações do usuário final com cada versão beta depende de você, mas pelo menos no final do sprint, o log de alterações deve ser concluído e sempre que você fornecer um bugfix ao usuário final, você também deve atualizar os documentos de histórico.
Você encontrará a estratégia de liberar dois ramos separados, um ramo "estável" com números de versão semântica e um "ramo de desenvolvimento" marcado com números de compilação ou algo similar, em muitos produtos de código aberto como o Inkscape, o Firefox ou o 7-zip.
Se, no entanto, você não trabalha com estações estáveis e de desenvolvimento separadas, e divulgue diariamente uma nova versão ao usuário final, você também deve incrementar um número de versão diariamente. Para tal caso, os números de versão "4.5.1", "4.5.2",. provavelmente refletirá suas implantações individuais e não indicará a diferença entre correções de erros e outras alterações. Isso pode estar certo, não é mais o "controle de versão semântico" clássico. Nesse cenário, você também pode implantar versões 4.5, 4.6, 4.7, 4.8, que não dá nenhuma diferença real.
No que diz respeito à sua pergunta sobre as entradas no seu changelog: IMHO quando algo é visível para o usuário final, vale a pena uma entrada no changelog, assim que você implantar a alteração. Por exemplo, se você usar alternar recursos e fazer alterações em algum recurso meio cozido que ainda não esteja ativado para o usuário, isso não pertence a um changelog. Se você fizer apenas refatoração, sem alteração visível para o usuário, isso não pertence a um changelog. Se você corrigir um bug que poderia ter afetado alguns usuários, isso pertence definitivamente ao changelog - e deve ser mencionado lá ao mesmo tempo em que você implanta o bugfix. E não importa se você liberar diariamente, mensalmente ou anualmente.
Eu usaria números de compilação. Normalmente, um número de compilação corresponderia à versão mais alta do sistema de controle de versão. Se o número de compilação das segundas-feiras era 1745 e verificou-se 5 mudanças durante a terça-feira, o número de construção das noites de terça-feira seria 1750.
Em seguida, faça um breve resumo do que mudou entre 1745 e 1750.
Então, toda vez que você atualiza o número da versão do seu sistema, você pode adicionar todos os resumos curtos das compilações para obter as alterações do último número da versão para o novo.
O método preferido que usei há pelo menos alguns anos agora é superar o número após a conclusão de cada história. Isto significa que as versões lançadas no final do sprint não serão contínuas, p. Depois de 1.2.3 você pode encontrar 1.5.2 em vez de 1.4.0.
No changelog você pode listar as versões intermediárias com suas descrições correspondentes ou apenas agrupar todas as mudanças na versão "lançada" e ignorar as versões no meio.
Inicialmente, eu estava com medo de que os usuários achassem os "buracos" entre os números de versão problemáticos, mas uma vez que eles sabem sobre isso, não é um problema na prática. A grande vantagem é que aumentar o número após cada história torna o processo menos propenso a erros - você não precisa verificar todo o trabalho a partir de 2 semanas para decidir qual será o próximo número de versão - ao olhar para uma única história, é óbvio . Além disso, os "saltos" em números de versão entre cada versão dão uma estimativa aproximada de quantas mudanças entraram no lançamento. No geral, eu achei que este sistema funcionou bem (isto foi com os clientes internos da empresa, mas, se você já trabalha em um ciclo de liberação ágil, ele também deveria funcionar para clientes externos).
Estratégias de versão de software.
Um perfeccionista preso no mundo real.
Software Versioning pode ser uma dessas áreas onde você nunca sente como se você tivesse exatamente isso. Não existe uma orientação definitiva com uma solução que satisfaça todos. Principalmente equipes de software estão confusas sobre o assunto, ou estão escolhendo ignorá-lo. Este guia visa preencher a lacuna e oferecer uma visão prática de várias estratégias populares e trade-offs.
Algumas das técnicas serão voltadas para a Microsoft stack (Windows), pois é o que tenho mais experiência, mas os princípios se aplicam em geral. Linux, Node. js, Python & amp; Ruby também é levemente tocada.
Versões em todo lugar.
Estamos bastante acostumados com o termo "versão" hoje em dia. Mais comumente usado no mundo do software, vazou para a mídia e outras indústrias. As seqüências de filmes estão sendo versionadas - "Fast & Furious 7" (7 !?), os sapatos estão sendo versionados - "Air Jordan XX8" e, mais popular, os livros estão sendo versionados - "One Minute Manager, 1984 edition". Na verdade, olhando para os livros, as pessoas estão sendo controladas há bastante tempo - "Enciclopédia Britânica", desde 1768 !.
A premissa é simples - à medida que os produtos vivem e continuam a ser melhorados, os lançamentos mais recentes devem ser distinguidos dos anteriores. O nome do produto não muda, porque o mercado já se familiarizou com isso, então algo é anexado no final para indicar que ele é mais novo (ou diferente).
Enquanto o controle de versão existia muito antes da era digital, o software realmente empurrou o problema para a frente. Modificar e liberar uma nova cópia de software é um processo muito rápido, muitas vezes mais rápido do que mudar uma linha de produção industrial para produzir uma nova peça de roupa ou imprimir uma nova edição de livros. Assim, os ciclos de iteração do software são muito mais curtos, e um potencial para muitas edições simultâneas é muito maior.
Basta usar anos (ou mesmo meses), como em edições de livros, não é suficiente. Novas versões do software podem ser produzidas em poucos minutos. Além disso, o software tem um enorme aspecto paralelo - fluxos de software - onde várias versões principais podem existir e todas podem ser atualizadas continuamente ao mesmo tempo. Isso raramente acontece com seus sapatos. (Eu queria fazê-lo, às vezes eu simplesmente não quero atualizar para o modelo de catálogo deste ano, quero uma melhoria para o meu antigo par!)
Por que Versão?
Antes de mergulhar em como implementar o controle de versão, vamos parar e considerar o motivo pelo qual queremos fazê-lo em primeiro lugar! Afinal, se conhecemos as razões exatas de por que é útil, podemos avaliar melhor se as soluções propostas são adequadas.
Nós aludimos a isso na última seção, referindo-se ao chamado versão pública. Esta é a versão que é visivelmente visível e, principalmente, possui peso no mercado (ou seja, é mais provável que seja definido pelo departamento de marketing / vendas). "Windows 7", "iPhone 5S", "Office 2013" - são exemplos de uma versão pública.
A versão pública destina-se a ser simples e memorável, indicando aos clientes que é novo e brilhante (assumindo que as pessoas geralmente querem "novo e brilhante"). As pessoas não entendem "10.6.6527.14789" - mas recebem "2013" ou "5". Tem sido cada vez mais popular usar o ano de lançamento como o número da versão pública, pois transmite de forma simples e poderosa o status atualizado. Os fabricantes de automóveis estão fazendo isso há muito tempo.
A versão privada é o que estamos acostumados no mundo do software. Um selo interno que (espero) identifica de forma exclusiva um determinado software. O software, como um carro, pode ser feito de muitas partes. Seguindo a analogia do carro, a "versão privada" do carro é o número do chassi VIN. Os fabricantes liberam e mantêm catálogos maciços de peças, mapeando para "números de versão" do carro. Um mecânico pode então pedir uma parte exata que corresponda ao seu veículo.
Sem um "número de peça privada", você não seria capaz de atender seu software na natureza, pois não saberia o "formato" exato que um módulo de substituição deve ser para se adequar ao sistema geral. Imagine se você foi forçado a mudar seu carro inteiro quando uma luz da cauda quebrou.
Portanto, o número da versão privada é usado como um identificador de catálogo. Destina-se a ser usado quando solucionar problemas ou atender seu software. (Eu gosto da analogia "dogtag" de Jeff Attwood!) Ele deve mapear para uma descrição do que é essa peça de software - qual é a sua forma e função. E o que melhor "descrição" do que o próprio código-fonte original!
O uso essencialmente se resume a:
Identificando o código fonte original para uma peça de software, para permitir parches incrementais e para confirmar a operação defeituosa Identificando se uma parte é "compatível" com outra, ou se ela pode substituí-la.
Tudo isso é realizado com um número de versão privado. A versão pública é simplesmente um moniker de marketing, e mapeia para uma ou mais partes internas de software, cada uma com sua própria versão privada. (Assim como o Toyota Corolla 2011 contém um quadro ZRE142 e um conversor de torque 32000-12420)
Uso da versão.
No Windows, um conceito de número de versão é suportado por uma camada do sistema operacional. Os números de versão são incorporados em todos os arquivos executáveis binários e podem ser vistos ao pairar sobre o EXE / DLL no Windows Explorer ou ao exibir Propriedades. Na verdade, qualquer arquivo que possa ter "recursos" pode ter uma versão, pois está armazenado no recurso VERSIONINFO.
Ele usa o formato comum em que todos nós somos usados para: major. minor. build. revision (por exemplo, "1.2.360.0"). É importante notar que cada número é limitado a 16 bits e, portanto, não pode exceder 65535. Isso tem certas implicações sobre o que podemos representar com esses números.
Observe que um rótulo para esses números não é definido de forma estrita - são simples 4 números inteiros curtos. Os dois primeiros são referidos como principais e menores, por unanimidade. Os dois últimos são onde vemos algumas variações, dependendo do esquema de versão.
Esta versão é usada de forma proeminente durante o processo de atualização do Windows, que utiliza a tecnologia Windows Installer (MSI) para atualizar várias partes do sistema. Essencialmente, o Windows Installer segue certas regras para determinar se a atualização que está instalando é mais nova que o que já está instalado. Se a versão for maior, então está ok para atualizar.
Naturalmente, esse conceito flui para o Framework, que foi construído em torno de muitos conceitos existentes do Windows. Nós temos a classe Version, que segue o paradigma 4 integer. Também podemos definir AssemblyVersionAttribute e AssemblyFileVersionAttribute, que especificam uma versão de montagem e um recurso de versão do Windows, respectivamente.
Em, a versão de montagem existe separadamente da versão baseada no Windows VERSIONINFO, que é o que você vê no Windows Explorer (ou propriedades do arquivo). Ele forma parte do nome forte da montagem e é usado exclusivamente pelo Framework ao resolver assemblies. A versão de duas montagens e a versão do arquivo do Windows - podem ser diferentes, mas, mais frequentemente, elas são as mesmas para evitar confusões.
usa a versão para rastreamento de dependência, ou seja, observando as versões de montagens a serem referenciadas, tornando assim óbvio quando uma atualização quebra a compatibilidade para aplicativos que dependem de uma determinada biblioteca. Este é um passo à frente da versão do arquivo nativo do Windows, que foi usada apenas durante o processo de atualização, e não ao referenciar uma biblioteca, levando ao infame "DLL Hell".
Vale ressaltar que a Versão da Versão permite 4 inteiros de 32 bits, enquanto o AssemblyFileVersionAttribute é limitado a 16 bits, pois ele mapeia diretamente para o recurso VERSIONINFO. Assim, se queremos que AssemblyVersionAttribute e AssemblyFileVersionAttribute sejam iguais, isso também coloca um limite nos componentes da versão de montagem.
O Linux, em geral, usa um método diferente para abordar o controle de versão. Os arquivos binários não possuem um selo de versão incorporado, como a maioria dos binários do Windows. Em vez disso, um nome de arquivo da biblioteca compartilhada indica sua versão, e. /usr/local/lib/mylib. so.1.5.
São criados vários links simbólicos, e. mylib. so - & gt; mylib. so.1 e mylib. so.1 - & gt; mylib. so.1.5. Um aplicativo pode fazer referência a uma biblioteca via link simbólico, como mylib. so.1, e obter a versão compatível 1.x mais recente instalada.
Isso funciona bastante bem, desde que todos sigam esta convenção. Cada biblioteca pode então, por sua vez, carregar bibliotecas, dependendo da mesma maneira.
Os usuários de Linux também estarão familiarizados com o popular "Advanced Package Tool", apt-get, usado de forma ubíqua nos sistemas derivados da Debian, como o Ubuntu. Sendo um verdadeiro gerenciador de pacotes, ele suporta a instalação de versões lado a lado e dependências de rastreamento entre pacotes. Examinamos as vantagens dos gerenciadores de pacotes nas seções a seguir.
Esquemas dos números de versão.
Existem vários esquemas de numeração popular para software, mas todos eles são uma variação do mesmo tema e compartilham características comuns. Ter componentes principais e menores da versão é o mesmo em toda a placa. O que eles representam é bastante consistente:
Maior aumento de número: representa grandes mudanças de falha no sistema de software, muitas vezes não é compatível com versões anteriores, ou adição de grande quantidade de novas funcionalidades. Aumento de número menor: representa mudanças substanciais de evolução, principalmente atualizações ou melhorias na funcionalidade existente ou adição de uma nova nova conjunto de características.
Acima é apenas uma diretriz - não há regras estabelecidas sobre o que as versões maiores e menores devem representar. Só que eles deveriam aumentar à medida que mais recursos são adicionados ao software com o tempo.
Windows e binários especificam um esquema de versão de 4 partes: maior. menor. construir. revisão . Os dois últimos componentes são de forma bastante gratuita, existem muitas variações no que representam - alguns usam contadores de construção incrementais, alguns utilizam a data / hora da compilação, e alguns os derivam dos números de revisão interna do controle de origem.
Muitos ignoram o número da revisão e focam apenas na compilação. O Windows Installer, por exemplo, possui apenas 3 componentes. Se você deseja que sua versão abranja binários e o pacote que contém, então é melhor limitar-se a apenas três números: maior. menor. construir.
Em qualquer caso, o padrão geral: quanto maior for o número da versão, mais recente será o software.
Um popular sistema de controle de versão nos últimos anos (especialmente entre projetos de código aberto) foi apelidado de Versão Semântica (também conhecido como SemVer) e documentado no Semver. Ele apresenta alguns outros componentes, e torna a versão uma string alfanumérica, em vez de um número puro - abrindo algumas possibilidades interessantes.
Os três primeiros componentes são os mesmos que já discutimos, sendo o patch opcional. Patch é bastante equivalente ao componente de compilação, mas a semântica pode ser diferente. A versão semântica realmente prescreve quando cada componente deve ser incrementado (com base em mudanças de "API pública").
O pré-lançamento, se especificado, é uma seqüência alfanumérica que é usada para marcar uma versão como uma que precede a versão final. Por exemplo, 1.3.567-rc1 precederá 1.3.567. Isso é útil para adicionar mais significado ao rótulo da versão do que simplesmente usando números.
Metadata is another optional component, which allows further tagging of the version label (usually with a build timestamp), but it does not participate in version ordering, i. e. versions that only differ in metadata are considered the same.
Prerelease is useful with Package Managers like NuGet, which treat them differently - they are considered unstable and are not visible to general public, unless explicitly requested. This allows releasing alpha/beta versions without affecting those relying on stable releases.
Prerelease tags can also be useful in the internal release flow when dealing with parallel hotfixes and private builds, as discussed later in this article.
Versioning Non-Binary Files.
So we know how to stamp a version on the binary files . But what about the other files comprising a software system - configuration files, images, documents, fonts, etc? How do you stamp a version on them?
What about web frameworks like ASP (or Ruby, Node. js, Python, etc) where source files and pages can be modified in-place, and automatically updated? How can we patch a web system, i. e. update few target files, and still keep it versioned?
The answer is - don't update individual files ! There is no way for you to keep a meaningful version number for your software application, if individual non-binary files can be updated ad-hoc as hotfixes.
Update using a package instead.
Importance of Build and Package.
When you hear the term "build", normally the compilation comes to mind - most compiled languages, such as C#, C++ or Java, have to be compiled into a binary before being able to be executed. And so building is commonly associated with the process of compiling .
But that's not an entire picture. Some languages or frameworks, such as Python or ASP, don't strictly require compilation. They can be either interpreted, in Python's case, or compiled on-the-fly, in ASP's case. What should a build do for these systems? How do you "build" a Python app?
That's why it is more helpful to think of build as an assembly process , or simply packaging . Just like a line of consumer goods, e. g. shoes, gets packaged before shipping to the stores, so does a software system, before being released.
A package concept is essential to versioning , because a package is a single collection of the pieces that comprise a software system, or part of it, and can therefore be identified, and stamped with a version . With the right Package Management system (which we look at in the next section), it can be deployed and updated, and specify dependencies on the other packages.
Software today is never a single binary executable file - it is a collection of various binaries, libraries, documents, configuration files, images, and other resources. A package is what helps us group them together, version and release to the outside world.
A package doesn't have to be sophisticated, although it helps in some situations (e. g. databases). It can even be a simple ZIP file, that can contain version in the file name, or embedded as a text file. In fact, many open source projects do just that - a release is a ZIP or a. tar. gz archive.
The important thing is that a package is a single unit, that is released and updated at the same time, leading to consistency . It is common to have several packages, for example, representing "client" and "server" components, or any other logical grouping applicable to a software system. Each package can then be updated on its own.
Let's take a look at some of the common packaging methods, the versioning approach, and which application they are best suited for.
Windows Installer.
Best Suited : Complete Windows GUI Applications, Windows Services, or Drivers.
The oldest, and for a long time the only recommended way, to install applications on a Windows platform. It has a built-in versioning support and a sophisticated (some would say "complicated") set of rules for determining when to update components. While a Windows Installer package (.msi) is a single file, in essence, it is a collection of small logical components (down to single files) that can be updated independently.
Windows Installer will actually check each individual file that is being installed, whether it has a version and whether the version is greater than a file with the same name already installed. That means it is important to version not just the installer package, but each file contained in it. But it also means that it is incredibly difficult to do downgrades (i. e. rollbacks) with Windows Installer.
It is best suited for traditional Windows Applications (GUI, services, drivers) that are released to the public. It is, however, not the best choice for internally developed & distributed applications, any kind of Web applications, or database systems.
It was also used to deploy distributable libraries (native DLLs) and COM objects, but with today's focus on , it is not the right mechanism for distributing libraries.
Web Deploy.
Best Suited : Web Applications (IIS, ASP)
Web Deploy technology was specifically designed for deploying and synchronizing applications on Microsoft IIS web servers. IIS Web Farm replication uses Web Deploy commands and packages behind the scenes to synchronize sites across a set of servers. IIS Manager has an extension (enabled by installing Web Deploy) to "Import Application", which can install or update a web application using a Web Deploy zip package.
Its biggest disadvantage is that it can only be used for web applications on Microsoft IIS platform, and the limited mechanism for customizing installation. While it could be suited for simple web applications, it can quickly become frustrating for anything more sophisticated, i. e. variables, conditional logic, databases, etc.
In addition, it has no inherent support for versioning .
Package Managers.
Best Suited : Shared Libraries, Dependencies, Command-line Utilities.
Package Managers are great for releasing and versioning shared components, and tracking dependencies between them. For example, if you have a shared library that you want others to use, then a Package Manager allows you to publish multiple versions side-by-side, and for consumers of the library to reference the version they depend on. Package Managers can resolve all inter-package dependencies, and retrieve only the versions that are expected. In effect, Package Managers solve the "DLL Hell" problem.
They are best used during development, to resolve library dependencies. However some Package Manager, like Chocolatey for Windows or apt-get for Ubuntu, are geared towards installing complete software.
Most importantly, Package Managers are designed around the versioning concept . So they are a perfect mechanism for distributing versioned software libraries.
For we have NuGet. A lot of open-source libraries have been published to its online repository, and it is now the defacto standard for distributing 3rd party components. It is encouraged that every team sets up their own NuGet repository to share and publish internally developed libraries in a versioned manner.
NuGet can even be used to release complete software systems - see next section.
Other development environments have their own - npm for Node. js, pip for Python, gems for Ruby, apt-get on Linux. Package Managers have been proven to be extremely useful, and have exploded in popularity.
Octopus Deploy.
Best Suited : Internally Developed & Deployed Software.
Octopus uses NuGet as the packaging and versioning shell. It is similar to an installer, only driven by PowerShell, meaning infinite flexibility in how the software is to be deployed. PowerShell already has a great support for configuring Windows Services, IIS Web Applications, Scheduled Tasks, SQL Server, and more.
For internally developed and distributed software (i. e. for a company running home-grown software solutions) this is a perfect release management vehicle. Packages are versioned and pushed to a shared NuGet feed (e. g. a network share), from where Octopus Deploy can release and deploy each package into the appropriate environment.
NuGet here plays a role of the application package/container, with a version stamped on it. Package can be built once, and then deployed as many times as needed to whatever environment.
Versioning & Packaging Databases.
Database versioning is one of the biggest challenges in software projects. Almost every team I encountered, either completely ignored it or had something inadequate in place. It certainly presents a challenge - database systems mix schema definition with actual live data , and there is no single "file" that can be effectively versioned.
We have to recognize the database as an integral part of the software system. One that executes on a proprietary 3rd-party platform (SQL Server, Oracle, PostgreSQL, etc), but the source of which is part of the software definition. It can be compared to script-based systems, such as Node. js or Python, only the scripts are written in a SQL dialect.
There are essentially three popular approaches to database versioning, that support automated deployments (I am not considering manual approaches, because they are error-prone, and have nothing to do with real versioning!).
DB - Migrations.
"Migrations" is a concept where developers keep a set of organized SQL script files, numbered sequentially, where each script applies modifications to the target DB to bring it to the expected state. Whenever a change is needed to the application database, a developer creates a new migration script that applies the delta changes.
All of the scripts are kept as part of the source control, and are packaged with the application (either embedded into the executable binary, or installed along-side). A migrations library then checks the target database for a dedicated table which holds the last "migration script number" applied, and then runs all the scripts with a number greater than that in order, effectively applying all of the changes in turn.
While this approach is simple to implement, and is favored among several popular frameworks (Ruby Rails, Entity Framework), it has a number of significant short-comings . Firstly, there is no single source view of all database objects (i. e. tables, stored procedures, etc), they are sprinkled through the multiple migration scripts. It is not clear which of the scripts contains which of the modifications. One has to "replay" them all to generate a database, and then look directly in the database (rather than source code).
Secondly, the migration scripts number becomes the "version" of the database, which is different from the software package version number for the rest of the application. This is somewhat confusing. In addition, this "version" does not really identify the state of the database, since a database can be changed outside an application without updating the "version". This may potentially break future installs, because migration scripts expect the database to be in a certain state to work.
Thirdly, developers have to be disciplined enough to follow the structure and apply ALL changes through migration scripts . Furthermore, when developing and debugging locally, one often has to go through several iterations before getting that table or store procedure change right. Yet only the final changes should make it into the migration script , meaning they have to be remembered and written manually. Otherwise, migration scripts would contain all of the intermediate changes made by all developers on the project. It is easy to see how that can grow out of proportion quickly.
Finally, there is an argument that migration scripts are a "history of changes", and it is a bit of a redundancy to store them in source control, which already is a "history" of code changes. We would be storing a history of a history . There's something philosophical about that.
Supported by some frameworks and libraries (Rails, DbUp, RoundHousE, EF Code First) Can work with any database Potentially high degree of control over SQL scripts.
Have to manually maintain all migration scripts Tracking changes through source control is difficult Not robust against target database out-of-band changes.
DB - SQL Compare.
Most often this is used in a manual approach, comparing a database between two environments (e. g. development vs test) to copy over the changes. We are considering an automated approach, suitable for the packaging and versioning strategies being discussed.
In source control, database is represent by a series of creation scripts (e. g. to create tables, stored procedures, triggers, etc), such that a new database with the right schema can be created from scratch. Usually each script file logically represents a corresponding object in the database, e. g. Table1.sql would be the create script for Table1 table. All of the scripts are included in the released package (sometimes even combined into a large single create script, by concatenating them).
The idea is that during automated package deployment a temporary fresh database copy is created, by running all of the creation scripts , and then a SQL Compare tool is executed to compare the pristine copy with the target database to generate a migration delta script on the fly.
The advantage of this approach is that it is robust against the target database out-of-band changes, since delta script is generated during deployment , rather than during development. SQL Compare tools (such a RedGate's SQLCompare or XSQL Compare) are sophisticated and mature enough tools that we can have some confidence in the generate SQL code. Each can be controlled by a multitude of options to fine-tune behavior with respect to renames, reordering columns, avoiding drops, etc.
In this case, target database is considered as a runtime environment , and we avoid having the issue of versioning it . Instead we version the package that contains all of the creation scripts , which is much easier, and use it to synchronize target database with what's expected in each version.
The big disadvantage of this approach is the difficulty of getting it right - there is no off-the-shelf framework that would support it, and it has to be developed. For SQL Server, read the next section for a better approach. For others, some day I may put together the set of scripts and logic necessary to achieve this, based on some of my prior work (unless someone else beats me to it).
Automatically detect and migrate changes, regardless of target DB state Only maintaining DDL (i. e. create) scripts in source control, meaning easy change tracking.
More difficult to setup, especially to be automated Having to create a temporary database during each deployment (need " create database " permission)
DB - DACPAC (SQL Server)
For SQL Server there is now a new recommended approach - DACPAC, and it can be produced by Visual Studio 2012 and above, if using the SQL Server database project. Really, this is a slick variation of the "SQL Compare" method above, just that Microsoft has done all the heavy lifting for you!
Essentially, DACPAC is a zip package which contains an XML schema model of what the target database should look like. It is compiled by Visual Studio based on the creation scripts in your project. In fact, it represents that temporary pristine database that we would have had to create manually. Only it is done automatically and the schema represented in an XML format. The real bonus is that a DACPAC can be versioned , i. e. its metadata supports storing a version number.
SQL Server Data Tools can be used to deploy a DACPAC package, which really performs a SQL Compare operation between the in-memory database model loaded from DACPAC and the target database. It does the same thing as SQL Compare, but avoids having to create the extra temporary database copy to do the comparison.
For applications having SQL Server as a back-end, a DACPAC can be included as one of the deployable packages, stamped with appropriate version generated during the build. Starting with SQL Server 2008 R2, database can be registered as a Data-Tier Application, and the latest DAC version is tracked in a system view that can be queried.
Can package the whole DB definition into a single package (or several packages) Can apply the same version to the package as the rest of the software system Same advantages as the SQL Compare method.
SQL Server only Need to treat lookup data in a special way (post-deploy MERGE script)
Build Auto-versioning.
Given the importance of consistent versioning discussed above, it makes sense to implement a strategy for automatically generating and stamping a version number during the software automated build process. We want the version number to be applied to the produced packages, and also applied to all the binaries generated through compilation.
There are several well-known and not so well-known ways of achieving this. We look at pros and cons of each.
Applying Build Number.
There are some who prefer to update the version number manually just before a release. I will argue that this is a bad practice. Firstly, it is easy to forget to do it, if you don't have an automated system for incrementing the version build number. And, if it is easy to forget, it will be forgotten at some point.
Secondly, without automatically updating build number, there will be multiple packages produced from the source code that have the same version number, but different functionality (as more commits are made to the source control). This will be confusing to say the least.
It is better to have a process, like ones described below, where version number build component is automatically updated whenever a non-local build is made.
Multiple Versions for Multiple Components.
If there are multiple software components, where each needs to have its own version number, then it is best to split them each into its own separate build. Don't mix multiple version numbers in the same build, as it unnecessarily increases the complexity, and raises a question about which of the build numbers should be used to label the build itself (in addition to having to tag each source sub-tree separately).
Developer vs Continuous vs Release Builds.
Release build is the one that will potentially be released to public or a particular environment - test, staging, production, etc. That's the build that needs to be consistently versioned to keep track of changes that are included and to link back to the source code at the time of compilation.
Note that the Release build can scheduled - it is popular to have a Daily or Nightly build. In most situations it should be the Release build, i. e. it should be versioned and packaged ready to be released.
Continuous Integration builds run whenever someone commits to the repository and are used to validate that the code compiles, and passes unit tests. There is no need to version this build, as it is not intended to be released.
Developers must also be able to do a Developer build , whether it is to test/fix the build process itself, or to generate shared software components to be used in development. Such builds are intended to be run locally only and should never be publicly released.
You can default the build part of the version number to "0". This will identify Developer builds, i. e. ones that are not supposed to be released. For Release builds pass the build number to your build scripts as a property. Have MSBuild stamp a version number on all generated assemblies and packages.
Tagging Source Control.
Since one of the primary reasons for having a version number is to be able to link back to source code used to build the software (see beginning of the article), it is important to create tags/labels in source control that identify the state of source code at the time that version was built.
Various systems call it differently - TFS has "Labels", Git has "tags". Tag should include the full version (including the build number) of the build, so that it can later be found, if needed.
Build Number - Version File Auto Increment.
Common technique is to record version number together with source code, usually in a separate file (e. g. "version. txt"). The build process then finds the file, reads the version, increments the build number portion, and commits the file back to repository.
If the commit message also includes the version number, e. g "Auto-increment: 1.3.156.0" , then it comes in handy when viewing commit history. You can see the changes that occurred between versions clearly by seeing the commits between the two "Auto-increment: . " messages.
This works fairly well, but has a few drawbacks. Mainly due to the fact that "version" becomes part of the source code. When merging changes between say release branch and main, you have to resort to "cherry-picking" (i. e. selecting just the code changesets) to avoid merging the modified version number. That requires being always careful, because you can accidentally change the versioning sequence of another branch just by merging the "version file" into it.
Control over the build number sequence (i. e. sequential) Can make it easy to see changes between versions in source control history.
Difficult to control merging between code branches in source control.
Build Number - External.
Overcoming the drawbacks of the auto increment approach, it is possible to track the build number outside of the source tree. Build server software such as CruiseControl or TFS Builds can do that - they track a build number internally for each "project" and are able to pass it as a parameter to MSBuild.
Version file is still used, but it records major and minor versions only, and doesn't have to change between each build. This makes it easier to merge changes from release branches back to main and others, since they will contain only code changes, without being intermingled with version increments. Major/minor version changes would occur early in the development cycle, when starting work on the next update, and are already set by the time release branch is created.
Not modifying source tree on every build makes merging between branches easier Versioned builds are forced to be built by a dedicated build server.
Relies on a build system that can supply a build number (e. g. CruiseControl, TFS Builds) Changing build number sequence can be difficult (e. g. TFS Builds)
Build Number - Derived from Date/Time.
A popular alternative is to derive build number for the date/time of the build. The advantage being that it carries more meaning (useful in diagnosis), and each build inherently should get a different build number (with later builds getting a higher number).
The trick, of course, is fitting all this into a 16-bit number, if using the standard 4-part Windows version number. While some solve it by using both, the build and revision components, I cannot recommend it, because revision cannot always be applied to external packages (like Windows Installer, or NuGet), which use only a 3-part version number.
This only allows only 4 unique builds per day, which is not a lot, unless all you want is a daily build .
Not depending on keeping track of the last build number Build number can be given more meaning, if it derives from a date.
Build number is not sequential (but it increases nevertheless) Limited to 16-bit (maximum 65535), so some overflow into revision (4th) number.
Build Number - Derived from Source Control.
A variation of the previous technique is to derive build number from a unique property in source control. With a centralized SCM like Subversion or TFS, a revision or changeset number is an ever increasing number that is tied directly to the source code. The big problem with it is that it can quickly overflow the 16-bit limit, meaning you may have to accept build numbers looping back to zero.
An alternative in distributed SCM, like Git, is to use the size of the commit history log as the build number. This will monotonously increase for any single branch, as new commits are made. It too can overflow the 16-bit limit, but goes a lot further than the global revision number.
Example: git rev-list HEAD --count.
Not depending on keeping track of the last build number No possibility of "forgetting" to update version file, or accidentally merge it to/from another branch.
Commit history size will grow beyond 65,535 at some point, overflowing the 16-bit build number.
Parallel Branches.
It's no secret that developing for multiple versions requires multiple branches in source control, each representing a "version" stream for the software. They can be roughly divided into:
Development branches - where unstable code for the next version lives, and where developers commit daily work Feature branches - veering off from development branches, encorporating larger feature development, that would otherwise disrupt other team members Release branches - representing versions of released software, or a release undergoing stabilization.
Each release branch needs to have an identifying version, and is usually named after it, e. g. "1.7" . A decision of whether to create a new release branch depends on how long it is expected that it will be in stabilization mode before releasing, and whether concurrent live versions are permitted (i. e. for packaged software). If you need to be able to maintain & hotfix the current released version, while a new version is being tested & stabilized, then create a new branch.
Development and feature branches need to have a version number that is above any of the existing release branches to avoid confusion. For example, if a 1.7 release branch is created, for the upcoming 1.7 release, then immediately update development branch version sequence to 1.8 .
Versioning feature branches is more difficult, since you don't want to start a new versioning sequence for every feature . Nothing should be "released" from feature branches, so this version is for internal purposes only. If using Semantic Versioning, attach a prerelease tag to clearly indicate this is a version for a feature branch, e. g. 1.8.781-dev-feature-x .
In any case, you wouldn't deploy anything built from a feature branch to the shared testing or production environment, or release a package from it. So it is acceptable to have version sequence overlap with that of development branch.
Finally, in the next section we look at how to version patches & hotfixes that are applied to release branches.
Handling Patches / Hotfixes.
Devising a system to handle patches depends heavily on the rest of the software development cycle, which is what many teams forget when searching for the "one, true way" of handling concurrent patching of the released/production software in parallel with working on the new version.
For example, having a short QA/test cycle, where most of the tests are automated, results in a more simplified and robust system, which does not have to deal with multiple parallel hotfixes "in test".
Overlapping hotfixes.
One difficulty that comes with managing parallel development is consistent versioning and deployment strategy that would overcome inherent conflicts. Consider following scenario: you have recently released a software package 1.5.167. Two urgent show-stopping issues have slipped past your QA process and now require a quick fix. You assign two developers to work on each one in parallel. How would they commit their fixes to minimize conflicts? How do you test each fix? How do you release one independent of the other?
This is a good example of the complexity of software release processes that can be encountered in real-world teams. It applies both to internal software and packaged software, but distribution of the hotfix might be slightly different for each one.
First, let's consider what happens if we remove concurrency . In the case where the two issues are worked one after the other , the solution becomes simple. The first fix gets committed into the maintenance/hotfix branch for 1.5 release stream, a new build is generated, with an incremented build number. Build goes through a quick QA cycle to make sure there is no regression, and then it is ready to be deployed. Same process repeats for the second fix.
The problem with concurrent approach is the time when development is in parallel, creating the entangled case where there is no build/package that contains only one of the fixes , i. e. independent of the other. This problem is magnified by a slow QA cycle , usually meaning there are no automated tests. While one fix is in test, if a commit for a second fix is made to the same branch, and a problem is discovered with the first one, it becomes very difficult to separate the two now.
The culprit here is, of course, the concept of a partial fix - the state where the fix is not complete. It has been committed, but has a problem with it, requiring further commits . This can easily create the case of a hotfix branch where the two fixes are "entangled" (quantum physics on the code level!).
Solution is to remove possibility of a partial hotfix .
This means that each hotfix has to be coded and tested in a separate code stream, independent of the other. Once tested, and ready for release, it is merged into the main hotfix release branch, where the automated build can create a new package and apply versioning (i. e. increment build number, for example, to 1.5.168).
Second hotfix, once tested, also has to be merged into the main hotfix release branch. But, because during the work on this second hotfix, the first hotfix got released, we first merge the first hotfix into the second hotfix's branch ! This ensures that we can test how the second hotfix operates, when applied on top of the first hotfix, and merge any code conflicts, if any.
In the end, you want a system with both hotfixes applied - that is the "next" version. So it makes sense that whatever hotfix is "second", it is applied on top of the "first" one. And creating a packaged release from the single hotfix release branch ensures that the version number is consistently incremented for the whole system.
Of course, above means that we must create a separate branch for each hotfix. Some version control systems, namely Git, make this very easy and part of the expected developer workflow. If you are using a version control system like TFS, then creating new branches for each hotfix is a bit more painful. In TFS, I suggest using named Shelvesets feature to emulate Git's process, and perform initial QA tests for a hotfix from a Shelveset-branch build. Then commit Shelveset into the hotfix branch to build the official hotfix package (and perform necessary merging).
What about the versioning of the interim hotfix builds ? The main hotfix release branch would have a standard versioning scheme applied (as discussed above), either incrementing a build number, or using a timestamp. Each new hotfix, applied on top of all previous hotfixes, gets an increased build number , and the software version keeps moving forward.
However, when building from the developer hotfix branch (or Shelveset in TFS), we also need to apply a version to distinguish it from other builds, and be able to deploy it into QA/test environment. We want to be able to test each hotfix in isolation, applied on top of an existing released version of the software system. This becomes problematic, if you have a single test environment .
You do not want to apply both hotfixes into one test environment, because there is no guarantee that they won't conflict or affect each other. If you are able to quickly spin up a test environment for a hotfix development branch, then you can truly parallelize team efforts. For a shared test environment, they have to be applied one at a time :
Force install latest release version (e. g. 1.5.168) to bring environment to a known state Install the hotfix version to be tested Perform the tests (preferably automated) For shared test environnments this is the bottleneck, since no other hotfixes can be tested at the same time (automation can help minimize the time spent in this step) Repeat 1-3, until tests are satisfactory.
What this means is that each hotfix has to have its build version number greater than the latest released version, the one it is being applied on top of. There are several ways to achieve that. If using a derived build number , this should just work out of the box. If incrementing or using external build numbers, then the easiest option is to simply force the build for hotfix development branch (or Shelveset) to use a number greater than latest released version (i. e. .168).
With Semantic Versioning, we can setup hotfix builds to use a "prerelease" tag that clearly marks it as a hotfix-test build. For example - 1.5.169-check14761 , where the trailing number could be a reference to the issue tracking system. This works especially well when using NuGet as the packaging mechanism.
Once tested, the changes can be merged into hotfix release branch, and an official build generated, with incremented build version number.
NOTE: Above process to resolve concurrent hotfixes is undoubtedly complicated. It is intended to solve a particular real-world scenario, but one that does not happen too often. If there are no concurrent fixes expected, you can simplify your life by applying fixes directly to the hotfix release branch.
Patching a large system.
If applying hotfixes to a large system, we don't want to upgrade the whole thing, which may involve a lot of different components - services, GUI applications, scheduled jobs, databases, etc. Instead, we want to apply the fix only to affected parts.
This is where splitting the system into multiple packages helps. Each corresponds to a logically contained piece of the system - for example, each service, application, database, etc is its own package. That means they can be patched independently by applying just that package .
Care must be taken about dependencies, if hotfix affects multiple packages at once. Although, in that case, ask yourself is it really a hotfix or a new minor version?
Patching for specific installation.
Some software shops may have developed the practice of patching the software for individual customers (for packaged software), in other words creating a "custom" version for just that installation, without including this fix in the rest of released software streams. This is one of the worst situations to be in, with regards to versioning, since it creates a large number of variations that have to be maintained separately.
Instead, release a general update , moving the overall software version forward for that release stream. Adopt a "feature" system , where parts of the software can be turned on & off based on configuration. If a specific fix is needed for a particular installation, then that code can be encapsulated behind a configuration switch which turns this section of the code on or off. That particular customer can turn it on , while the rest can have it off!
This is also a popular technique in web applications, of which only one installation exists (on the server), where various "features" can be enabled based on "configuration" for each user , or a set of users.
Patching the changes only.
There is often the temptation to simply patch in the changes to the live/production system by editing/replacing one file, or updating one table or stored procedure. The change is small, and it seems like the fastest way to solve the imminent issue, without changing anything else in the system.
While it seems like a smaller risk to make only the necessary updates directly, it makes it a whole lot harder to know the state of the system in the future. As more and more such "small" patches get applied, there is no longer any reliable way to link the running system back to the original source code, making further maintenance exponentially more complicated (and, ironically, increasing the risk).
Updating individual non-binary (e. g. config files) or altering database objects does not update any version number . That means it is difficult to tell which changes have been made to the system, leading to "maintenance hell" (a variation of the infamous "DLL Hell").
Rule of thumb: Any change to the system should change the version number.
NOTE : Windows Installer allows a so called "small update", where product version number does not have to change, used for small hotfix patches. I believe this creates too much confusion, and so I do not recommend it. Windows Installer does track each patch, through package code, so you always know which patches have been applied. But it means now having to track and remove patches on subsequent product updates, which complicates the process. It may work for Microsoft Windows and Microsoft Office, but I wouldn't recommend using it for any system.
Palavras finais.
This turned out to be a much longer article than I originally anticipated when I sat down to write about versioning . I am hoping it proves useful for software engineers out there looking for some guidance on how to apply these concepts in their own projects.
Still this seems like only a partial treatment of the topic.
Everything I wrote above has been learned through the painful process of trial & error over the years. If just a few readers have an "aha!" moment while reading this, then I have achieved my goal!
O recurso abre caminho para a grandeza.
Ou tarefa que se ramifica para lá. Ou liberar ramificações. Você escolhe.
Almost all version control systems today support branches–independent lines of work that stem from one central code base. Dependendo do seu sistema de controle de versão, o ramo principal pode ser chamado de mestre, mainline, padrão ou tronco. Os desenvolvedores podem criar seus próprios ramos da linha principal do código e trabalhar de forma independente ao lado dele.
Por que se preocupar com a ramificação?
Branching allows teams of developers to easily collaborate inside of one central code base. Quando um desenvolvedor cria um ramo, o sistema de controle de versão cria uma cópia da base de código naquele momento. As mudanças na filial não afetam outros desenvolvedores no time. This is a good thing, obviously, because features under development can create instability, which would be highly disruptive if all work was happening on the main code line. But branches need not live in solitary confinement. Os desenvolvedores podem facilmente reduzir as mudanças de outros desenvolvedores para colaborar em recursos e garantir que seus ramos privados não se divertem muito do mestre.
Branches aren't just good for feature work. Os ramos podem isolar o time de mudanças arquitetônicas importantes, como atualizações, bibliotecas comuns, etc.
Três estratégias de ramificação para equipes ágeis.
Os modelos de ramificação geralmente diferem entre equipes e são objeto de muito debate na comunidade de software. Um grande tema é a quantidade de trabalho que deve permanecer em um ramo antes de ser incorporado de novo ao mestre.
Liberação de ramificação.
A ramificação de lançamento refere-se à idéia de que uma versão está inteiramente contida em um ramo. This means that late in the development cycle, the release manager will create a branch from the master (e. g., “1.1 development branch”). All changes for the 1.1 release need to be applied twice: once to the 1.1 branch and then to the master code line. Trabalhar com dois ramos é trabalho extra para a equipe e é fácil esquecer de fundir os dois ramos. Release branches can be unwieldy and hard to manage as many people are working on the same branch. Nós sentimos a dor de ter que combinar muitas mudanças diferentes em um único ramo. Se você deve fazer um ramo de lançamento, crie o ramo o mais próximo possível da versão real.
A ramificação de lançamento é uma parte importante do suporte de software versionado no mercado. Um único produto pode ter vários ramos de libertação (por exemplo, 1.1, 1.2, 2.0) para suportar o desenvolvimento de sustentação. Tenha em mente que as mudanças em versões anteriores (ou seja, 1.1) podem precisar ser mescladas para ramos de liberação posteriores (ou seja, 1.2, 2.0). Confira nosso webinar abaixo para saber mais sobre como gerenciar os ramos de lançamento com o Git.
Característica ramificação.
Os ramos de recursos geralmente são acoplados com bandeiras de recurso e ndash; & quot; toggles & quot; que permitem ou desativam um recurso dentro do produto. Isso facilita a implantação do código em mestre e controle quando o recurso é ativado, facilitando a implantação do código antes do recurso ser exposto aos usuários finais.
Outro benefício das bandeiras de recursos é que o código pode permanecer dentro da compilação, mas inativo enquanto ele está em desenvolvimento. Se algo estiver errado quando o recurso estiver ativado, um administrador do sistema pode reverter o sinalizador de recurso e voltar para um bom estado conhecido em vez de ter que implantar uma nova compilação.
Ramificação de tarefas.
Na Atlassian, nos concentramos em um fluxo de trabalho de ramificação por tarefa. Toda organização tem uma maneira natural de quebrar o trabalho em tarefas individuais dentro de um rastreador de problemas, como o Jira Software. As questões então se tornam o ponto central de contato da equipe para esse trabalho. A ramificação de tarefas, também conhecida como ramificação de problemas, conecta diretamente esses problemas com o código-fonte. Cada questão é implementada em seu próprio ramo com a chave de problema incluída no nome da filial. É fácil ver qual código implementa qual questão: basta procurar a chave de problema no nome do ramo. Com esse nível de transparência, é mais fácil aplicar mudanças específicas ao mestre ou qualquer ramo de lançamento legado em execução.
Desde centros ágiles em torno de histórias de usuários, os ramos de tarefas parecem bem com o desenvolvimento ágil. Cada história de usuário (ou correção de bugs) vive dentro de seu próprio ramo, facilitando a visão de quais problemas estão em andamento e estão prontos para serem lançados. Para um mergulho profundo em uma ramificação de tarefas (às vezes chamado de ramificação de problemas ou ramificação por questão), pegue algumas pipocas e confira a gravação do webinar abaixo do & ndash, um dos nossos mais populares de todos os tempos.
Agora conheça o gêmeo malvado da ramificação: a fusão.
Todos nós sofremos a dor de tentar integrar vários ramos em uma solução sensata. Tradicionalmente, sistemas de controle de versão centralizados como o Subversion fizeram uma operação muito dolorosa. But newer version control systems like Git and Mercurial take a different approach to tracking versions of files that live on different branches.
Os ramos tendem a ser de curta duração, tornando-os mais fáceis de fundir e mais flexíveis em toda a base do código. Entre a capacidade de mesclar freqüentemente e automaticamente os ramos como parte da integração contínua (CI), e o fato de que os ramos de curta duração simplesmente contêm menos mudanças, "fundir o inferno" torna-se uma coisa do passado para equipes usando Git e Mercurial.
That's what makes task branching so awesome!
Validar, validar, validar.
A version control system can only go so far in affecting the outcome of a merge. Testes automatizados e integração contínua também são críticos. A maioria dos servidores CI pode automaticamente colocar novos ramos em teste, reduzindo drasticamente o número de "surpresas" na junção final a montante e ajudando a manter a linha principal do código estável.
A Agile teve um enorme impacto sobre mim tanto profissional como pessoalmente, como eu aprendi, as melhores experiências são ágeis, tanto no código como na vida. You'll often find me at the intersection of technology, photography, and motorcycling. Encontre-me no Twitter! danradigan.
Sign up for more articles.
Obrigado por inscrever-se!
Como fazer scrum com o Jira Software.
Um guia passo a passo sobre como conduzir um projeto de Scrum, priorizar e organizar seu backlog em sprints, executar cerimônias Scrum e muito mais, tudo dentro do Jira Software.
Git se ramifica para equipes ágeis.
Mudar para o Git abre um novo nível de agilidade para equipes de software. Aqui é como projetar esquemas de ramificação para envio tanto SaaS quanto produtos instalados.
Obter através da App Store Leia esta publicação em nosso aplicativo!
Controlando números de versão em sprints [fechado]
Tradicionalmente, os números de compilação de software se encaixam no formato.
Onde uma versão principal é implementada sempre que há mudanças de quebra, Menor quando novos recursos mini são adicionados, Libere quando algo for publicado e seja construído sempre que for construído.
Eu acho que os dois últimos dígitos funcionam muito bem em um ambiente de CI, cada construção de CI aumenta o número de compilação e cada versão para o Live incrementa o número de lançamento.
No entanto, o grande trabalho de cachoeira e os desenvolvimentos de mudança de ruptura são desencorajados em metodologias mais modernas. Nós preferimos liberar pouco e muitas vezes e, portanto, não tendem a fazer grandes mudanças de ruptura. Dado isto, é muito difícil determinar quando criar uma nova versão principal.
Nós tentamos evitar fazer grandes mudanças de ruptura e, assim, estamos conseguindo números muito baixos e menores, mas não tivemos a desculpa para avançar ainda uma versão importante. Que critérios devem ser usados para determinar quando as construções maiores e menores devem ser feitas (particularmente aplicativos baseados na web)?
Estou ciente de que há versões de estilo de data, mas estou interessado em Major. Minor. Build. Release apenas.
fechado como baseado principalmente em opinião de Michael Kohne, Dan Pichelman, MichaelT, mattnz, Dynamic Jul 2 '14 às 15:03.
Muitas boas perguntas geram algum grau de opinião com base na experiência de especialistas, mas as respostas a esta pergunta tendem a ser quase inteiramente baseadas em opiniões, ao invés de fatos, referências ou conhecimentos específicos. Se esta questão pode ser reformulada para ajustar as regras no centro de ajuda, edite a questão.
Eu acho que algum dos desafios começa com sua afirmação de:
Onde uma versão principal é implementada sempre que houver mudanças de quebra.
E tenho certeza de que você quer dizer "quebrar mudanças" no sentido de mudanças significativas na API; Mudanças na comunicação cliente / servidor; protocolo de trabalho, etc. "Big Stuff" (TM) em outras palavras.
Mas o problema é que não é apenas quebrar as mudanças que contam, mas qualquer mudança significativa que conta. "Significativo" varia de aplicativo para aplicativo, e é por isso que é difícil ser específico sobre o que se qualifica como significativo. Alterar o número principal deve ser um anúncio informal para os usuários finais que:
Algo grande é diferente. Eles devem preparar ou estar cientes de que isso provavelmente não será uma atualização trivial.
Então, aqui estão algumas das razões comuns que eu vi atualizar o número principal:
Marketing (ou desenvolvimento) sentiu como ele. Wicked legal, novo, grande recurso introduzido. Ou todo um monte disso. O esquema de banco de dados sofreu mudanças significativas. Muitas vezes isso irá forçar uma atualização para os usuários finais. Protocolo muda. Isso pode incluir mudanças significativas entre o navegador e o servidor da Web, ou pode estar entre o cliente e o servidor. A estabilidade da aplicação melhorou significativamente (ou piorou) e você deseja alterar a versão principal para informar ou alertar os usuários sobre a mudança. Alguma quantidade específica de tempo já passou - isso pode ser X meses, anos, seja o que for. O produto agora é compatível com algum padrão externo ou processo de auditoria. A aplicação foi bifurcada. Mudanças significativas foram feitas no suporte de bibliotecas, ou quais bibliotecas poderiam ser usadas.
De um modo geral, você quer dizer por que você aumentou o valor principal. É aqui que construir um "passo de elevador" para justificar o incremento pode ser útil. E, assim como um passo de elevador que você usa para se vender, esta é uma declaração rápida que resume o que era grande o suficiente para merecer a mudança.
Alguns pensamentos adicionais sobre o controle de versão.
Primeiro, você não precisa atualizar o valor principal de forma regular. Pode não fazer sentido se tudo o que está sendo fornecido é um monte de pequenos itens.
Se você sentir que está "ficando sem espaço" com os valores dos outros números, considere:
Use valores hexadecimais (base 16) ou outra base em vez de apenas valores decimais (base 10). Use dois ou mais dígitos dentro de cada valor. Então, você teria M. mm. rr. bbb, por exemplo.
Se o usuário do seu software não puder atualizar para a nova versão e usá-lo sem outras etapas de migração manual (dados, configuração, interfaces e similares), esse é um indicador forte para aumentar a versão principal.
Seja o que for que você esteja usando internamente, externamente você ainda terá uma cachoeira como o ciclo de liberação.
Quer se chame agora de "lançamento de recursos", "lançamento para produção", "versão de entrega do cliente", ou seja, será o produto combinado de uma série de sprints e ciclos internos mais curtos.
Estratégia de versão ágil
Obter através da App Store Leia esta publicação em nosso aplicativo!
Como você faz a numeração da versão em um projeto ágil? [fechadas]
Atualmente, estamos usando o seguinte esquema de numeração de versão para o nosso projeto C # winforms:
"Major Release". "Minor Release". "Número de iterações". "Número de compilação dentro dessa iteração"
Queríamos ser capazes de identificar o número de iteração e o número de compilação dentro dessa iteração, apenas observando o número da versão.
No passado, fizemos algo como: "Major Release". "Minor Release". "Sequential Build Number from 1.0". Por exemplo, "4.0.648" significaria que havia 648 compilações desde 1.0 - mas esta informação é bastante inútil e anedótica, e é por isso que mudamos para refletir as iterações e as construções dentro das iterações.
Portanto, considerando esta nova numeração de versão ágil, agora temos o problema em que um grupo de produtos diferente gostaria de fazer mudanças em sua iteração para o nosso projeto. Nessa instância, o número da versão não faz sentido, porque seus números de iteração e compilação não correspondem. Por exemplo, a última compilação do meu projeto foi 1.0.5.1 indicando a 1ª versão da iteração 5. Agora, esse outro projeto que na terceira iteração gostaria de fazer alterações no meu projeto e reconstruir.
Como eu deveria lidar com essa situação? Como você faz numeração de versão em seu projeto ágil?
fechado como primeiramente baseado em opinião por EJoshuaS, robinCTS, MikeT, Luke Peterson, Steven Scott 14 de novembro de 17 às 20:10.
Muitas boas perguntas geram algum grau de opinião com base na experiência de especialistas, mas as respostas a esta pergunta tendem a ser quase inteiramente baseadas em opiniões, ao invés de fatos, referências ou conhecimentos específicos. Se esta questão pode ser reformulada para ajustar as regras no centro de ajuda, edite a questão.
Eu acompanho a iteração dos projetos ágeis e não a iteração dos projetos de software. Se um projeto do lado do iniciante atrasado se juntar após outro projeto, ele iniciará com a atual iteração do projeto ágil, e não haverá desalinhamento.
Não deve ser possível que um projeto técnico fora do domínio do projeto ágil interaja com um projeto dentro do domínio. Isso seria uma falha PM do processo e deveria ser eliminada em todos os casos em que uma base de código compartilhada seja usada com ramificação, para ser corrigida no tronco como um passo de limpeza após a conclusão do projeto.
Pessoalmente, acho que o controle de versão do lançamento que eu gostei do melhor é acabar com todo o assunto principal. Eu acho que isso só é realmente viável para aplicações internas, mas para isso, torna a vida muito mais fácil.
Normalmente, se você está desenvolvendo aplicativos voltados para o interior, notei que o negócio nunca se importa com a versão principal / menor que eles estão usando. Em vez disso, eles tendem a querer saber a) quando é a próxima versão e b) o que vai ser dentro ou fora - e é sobre isso. Tentando manter o fato de que você está trabalhando no FOO-4.34.0.1-a e BAR-3.19.4.1 quando ninguém se importa apenas serve para complicar a comunicação.
Em um grupo anterior, não tivemos grandes lançamentos além de um lançamento inicial do projeto. Cada lançamento foi tão importante quanto o anterior.
Como resultado, eu acho que eles fizeram o senso e, em vez disso, se comunicaram com o negócio como PROJECT_RELEASENUM. O número de lançamento incrementado por '1' toda vez que fizemos uma versão, com patches como PROJECT_RELEASENUM_PATCHNUM, que também foi incrementado por '1'.
Funciona bem com a noção de que o desenvolvimento é feito como uma série contínua de sprints até que o negócio tenha toda a funcionalidade que eles precisam (o que na prática nunca acontece - sempre há algo mais que eles querem). Os empresários entenderam, os desenvolvedores poderiam comunicá-lo, e se emprestou naturalmente ao modelo de desenvolvimento contínuo que possuímos.
Eu prefiro Major. Minor. Build. Revision onde Build - o número de lançamentos públicos, Revision - uma revisão do sistema de versão de origem.
Eu prefiro separar o processo de compilação e lançamento do processo de desenvolvimento da equipe, então eu quase não adicionaria a iteração, sprint ou similar à versão. Seu caso é um bom exemplo sobre como ter ambas as coisas misturadas não é fácil de gerenciar. E se você mudar a metodologia no meio do projeto (qualquer que seja o motivo)?
Estratégia de versão ágil
Obter através da App Store Leia esta publicação em nosso aplicativo!
Versionamento semântico em Agile.
Digamos que eu tenho 14 dias de iteração de sprint, onde eu tenho várias histórias para novos recursos, poucas melhorias e alguns erros para consertar. Eu também implanto essas mudanças quando estão prontas, eu não estou aguardando o fim do sprint.
Meu problema é - como controlar o controle de versão semântico de produtos desenvolvidos e mantidos assim? Se houver lançamento a cada 14 dias, será fácil, aumentarei o número da versão e anotarei todas as alterações no changelog. Mas e se as mudanças forem implantadas de forma contínua? Deveria haver uma versão aumentada sempre que algo for implantado? Ou devo esperar até que Sprint termine e depois disso, faça algum "currículo" e aumente o número da versão apenas uma vez por iteração de forma independente na implantação real? Quais são as melhores práticas para o controle de versão semântico no Agile?
EDITAR: Para melhor explicar minhas necessidades, eu quero uma mudança para as partes interessadas em primeiro lugar. Eu não acho que eles estarão interessados em novo registro em changelog após cada mudança implantada.
Para o gerenciamento de lançamento típico, você quer que um número de compilação seja gerado pelo seu sistema de compilação para que as DLLs sejam versionadas sempre que forem implantadas. Isso garantirá que você poderá verificar a versão que é implantada em um determinado servidor.
Sua versão de "marketing", que geralmente é colocada em notas de versão ou publicada em seu site, não deve ser atualizada em cada versão. Essas notas de lançamento devem ser acumuladas e agrupadas, provavelmente programadas com o final do seu sprint.
Se o clássico esquema de versão semântica "MAJOR. MINOR. PATCH" faz sentido, depende de quem você implantar e, especialmente, quando e com que frequência você se implanta para o usuário final. O esquema é mais útil se você trabalhar com versão estável "4.5", onde você começa com 4.5.0. As versões 4.5.1, 4.5.2, e assim por diante, contêm apenas correções de bugs, enquanto você internamente já trabalha na versão 4.6.
Por exemplo, se você fornecer um "ramo estável" ao seu usuário final, dê uma versão 4.5.0 para a implantação inicial e 4.5.1, 4.5.2 sempre que você liberar um patch. No seu desenvolvimento interno "ágil" e na implementação do meio do sprint, você já pode ter uma versão 4.6, basta chamá-la de "versão beta". Sempre que você o implanta no meio do sprint, adicione o número de compilação gerado automaticamente como "4.6.beta build 123". Quando o seu sprint termina, atribua-o "4.6.0" e mude o número da versão para o próximo sprint internamente para "4.7". Começar com ".0" é apenas uma convenção, você também pode usar o ".0" para etiquetar versões beta e começar com ".1" para seus usuários finais. IMHO a palavra "beta" é muito mais expressiva, dizendo a todos que o sprint "ainda não está completo".
Se você liberar um registro completo de alterações do usuário final com cada versão beta depende de você, mas pelo menos no final do sprint, o log de alterações deve ser concluído e sempre que você fornecer um bugfix ao usuário final, você também deve atualizar os documentos de histórico.
Você encontrará a estratégia de liberar dois ramos separados, um ramo "estável" com números de versão semântica e um "ramo de desenvolvimento" marcado com números de compilação ou algo similar, em muitos produtos de código aberto como o Inkscape, o Firefox ou o 7-zip.
Se, no entanto, você não trabalha com estações estáveis e de desenvolvimento separadas, e divulgue diariamente uma nova versão ao usuário final, você também deve incrementar um número de versão diariamente. Para tal caso, os números de versão "4.5.1", "4.5.2",. provavelmente refletirá suas implantações individuais e não indicará a diferença entre correções de erros e outras alterações. Isso pode estar certo, não é mais o "controle de versão semântico" clássico. Nesse cenário, você também pode implantar versões 4.5, 4.6, 4.7, 4.8, que não dá nenhuma diferença real.
No que diz respeito à sua pergunta sobre as entradas no seu changelog: IMHO quando algo é visível para o usuário final, vale a pena uma entrada no changelog, assim que você implantar a alteração. Por exemplo, se você usar alternar recursos e fazer alterações em algum recurso meio cozido que ainda não esteja ativado para o usuário, isso não pertence a um changelog. Se você fizer apenas refatoração, sem alteração visível para o usuário, isso não pertence a um changelog. Se você corrigir um bug que poderia ter afetado alguns usuários, isso pertence definitivamente ao changelog - e deve ser mencionado lá ao mesmo tempo em que você implanta o bugfix. E não importa se você liberar diariamente, mensalmente ou anualmente.
Eu usaria números de compilação. Normalmente, um número de compilação corresponderia à versão mais alta do sistema de controle de versão. Se o número de compilação das segundas-feiras era 1745 e verificou-se 5 mudanças durante a terça-feira, o número de construção das noites de terça-feira seria 1750.
Em seguida, faça um breve resumo do que mudou entre 1745 e 1750.
Então, toda vez que você atualiza o número da versão do seu sistema, você pode adicionar todos os resumos curtos das compilações para obter as alterações do último número da versão para o novo.
O método preferido que usei há pelo menos alguns anos agora é superar o número após a conclusão de cada história. Isto significa que as versões lançadas no final do sprint não serão contínuas, p. Depois de 1.2.3 você pode encontrar 1.5.2 em vez de 1.4.0.
No changelog você pode listar as versões intermediárias com suas descrições correspondentes ou apenas agrupar todas as mudanças na versão "lançada" e ignorar as versões no meio.
Inicialmente, eu estava com medo de que os usuários achassem os "buracos" entre os números de versão problemáticos, mas uma vez que eles sabem sobre isso, não é um problema na prática. A grande vantagem é que aumentar o número após cada história torna o processo menos propenso a erros - você não precisa verificar todo o trabalho a partir de 2 semanas para decidir qual será o próximo número de versão - ao olhar para uma única história, é óbvio . Além disso, os "saltos" em números de versão entre cada versão dão uma estimativa aproximada de quantas mudanças entraram no lançamento. No geral, eu achei que este sistema funcionou bem (isto foi com os clientes internos da empresa, mas, se você já trabalha em um ciclo de liberação ágil, ele também deveria funcionar para clientes externos).
Estratégias de versão de software.
Um perfeccionista preso no mundo real.
Software Versioning pode ser uma dessas áreas onde você nunca sente como se você tivesse exatamente isso. Não existe uma orientação definitiva com uma solução que satisfaça todos. Principalmente equipes de software estão confusas sobre o assunto, ou estão escolhendo ignorá-lo. Este guia visa preencher a lacuna e oferecer uma visão prática de várias estratégias populares e trade-offs.
Algumas das técnicas serão voltadas para a Microsoft stack (Windows), pois é o que tenho mais experiência, mas os princípios se aplicam em geral. Linux, Node. js, Python & amp; Ruby também é levemente tocada.
Versões em todo lugar.
Estamos bastante acostumados com o termo "versão" hoje em dia. Mais comumente usado no mundo do software, vazou para a mídia e outras indústrias. As seqüências de filmes estão sendo versionadas - "Fast & Furious 7" (7 !?), os sapatos estão sendo versionados - "Air Jordan XX8" e, mais popular, os livros estão sendo versionados - "One Minute Manager, 1984 edition". Na verdade, olhando para os livros, as pessoas estão sendo controladas há bastante tempo - "Enciclopédia Britânica", desde 1768 !.
A premissa é simples - à medida que os produtos vivem e continuam a ser melhorados, os lançamentos mais recentes devem ser distinguidos dos anteriores. O nome do produto não muda, porque o mercado já se familiarizou com isso, então algo é anexado no final para indicar que ele é mais novo (ou diferente).
Enquanto o controle de versão existia muito antes da era digital, o software realmente empurrou o problema para a frente. Modificar e liberar uma nova cópia de software é um processo muito rápido, muitas vezes mais rápido do que mudar uma linha de produção industrial para produzir uma nova peça de roupa ou imprimir uma nova edição de livros. Assim, os ciclos de iteração do software são muito mais curtos, e um potencial para muitas edições simultâneas é muito maior.
Basta usar anos (ou mesmo meses), como em edições de livros, não é suficiente. Novas versões do software podem ser produzidas em poucos minutos. Além disso, o software tem um enorme aspecto paralelo - fluxos de software - onde várias versões principais podem existir e todas podem ser atualizadas continuamente ao mesmo tempo. Isso raramente acontece com seus sapatos. (Eu queria fazê-lo, às vezes eu simplesmente não quero atualizar para o modelo de catálogo deste ano, quero uma melhoria para o meu antigo par!)
Por que Versão?
Antes de mergulhar em como implementar o controle de versão, vamos parar e considerar o motivo pelo qual queremos fazê-lo em primeiro lugar! Afinal, se conhecemos as razões exatas de por que é útil, podemos avaliar melhor se as soluções propostas são adequadas.
Nós aludimos a isso na última seção, referindo-se ao chamado versão pública. Esta é a versão que é visivelmente visível e, principalmente, possui peso no mercado (ou seja, é mais provável que seja definido pelo departamento de marketing / vendas). "Windows 7", "iPhone 5S", "Office 2013" - são exemplos de uma versão pública.
A versão pública destina-se a ser simples e memorável, indicando aos clientes que é novo e brilhante (assumindo que as pessoas geralmente querem "novo e brilhante"). As pessoas não entendem "10.6.6527.14789" - mas recebem "2013" ou "5". Tem sido cada vez mais popular usar o ano de lançamento como o número da versão pública, pois transmite de forma simples e poderosa o status atualizado. Os fabricantes de automóveis estão fazendo isso há muito tempo.
A versão privada é o que estamos acostumados no mundo do software. Um selo interno que (espero) identifica de forma exclusiva um determinado software. O software, como um carro, pode ser feito de muitas partes. Seguindo a analogia do carro, a "versão privada" do carro é o número do chassi VIN. Os fabricantes liberam e mantêm catálogos maciços de peças, mapeando para "números de versão" do carro. Um mecânico pode então pedir uma parte exata que corresponda ao seu veículo.
Sem um "número de peça privada", você não seria capaz de atender seu software na natureza, pois não saberia o "formato" exato que um módulo de substituição deve ser para se adequar ao sistema geral. Imagine se você foi forçado a mudar seu carro inteiro quando uma luz da cauda quebrou.
Portanto, o número da versão privada é usado como um identificador de catálogo. Destina-se a ser usado quando solucionar problemas ou atender seu software. (Eu gosto da analogia "dogtag" de Jeff Attwood!) Ele deve mapear para uma descrição do que é essa peça de software - qual é a sua forma e função. E o que melhor "descrição" do que o próprio código-fonte original!
O uso essencialmente se resume a:
Identificando o código fonte original para uma peça de software, para permitir parches incrementais e para confirmar a operação defeituosa Identificando se uma parte é "compatível" com outra, ou se ela pode substituí-la.
Tudo isso é realizado com um número de versão privado. A versão pública é simplesmente um moniker de marketing, e mapeia para uma ou mais partes internas de software, cada uma com sua própria versão privada. (Assim como o Toyota Corolla 2011 contém um quadro ZRE142 e um conversor de torque 32000-12420)
Uso da versão.
No Windows, um conceito de número de versão é suportado por uma camada do sistema operacional. Os números de versão são incorporados em todos os arquivos executáveis binários e podem ser vistos ao pairar sobre o EXE / DLL no Windows Explorer ou ao exibir Propriedades. Na verdade, qualquer arquivo que possa ter "recursos" pode ter uma versão, pois está armazenado no recurso VERSIONINFO.
Ele usa o formato comum em que todos nós somos usados para: major. minor. build. revision (por exemplo, "1.2.360.0"). É importante notar que cada número é limitado a 16 bits e, portanto, não pode exceder 65535. Isso tem certas implicações sobre o que podemos representar com esses números.
Observe que um rótulo para esses números não é definido de forma estrita - são simples 4 números inteiros curtos. Os dois primeiros são referidos como principais e menores, por unanimidade. Os dois últimos são onde vemos algumas variações, dependendo do esquema de versão.
Esta versão é usada de forma proeminente durante o processo de atualização do Windows, que utiliza a tecnologia Windows Installer (MSI) para atualizar várias partes do sistema. Essencialmente, o Windows Installer segue certas regras para determinar se a atualização que está instalando é mais nova que o que já está instalado. Se a versão for maior, então está ok para atualizar.
Naturalmente, esse conceito flui para o Framework, que foi construído em torno de muitos conceitos existentes do Windows. Nós temos a classe Version, que segue o paradigma 4 integer. Também podemos definir AssemblyVersionAttribute e AssemblyFileVersionAttribute, que especificam uma versão de montagem e um recurso de versão do Windows, respectivamente.
Em, a versão de montagem existe separadamente da versão baseada no Windows VERSIONINFO, que é o que você vê no Windows Explorer (ou propriedades do arquivo). Ele forma parte do nome forte da montagem e é usado exclusivamente pelo Framework ao resolver assemblies. A versão de duas montagens e a versão do arquivo do Windows - podem ser diferentes, mas, mais frequentemente, elas são as mesmas para evitar confusões.
usa a versão para rastreamento de dependência, ou seja, observando as versões de montagens a serem referenciadas, tornando assim óbvio quando uma atualização quebra a compatibilidade para aplicativos que dependem de uma determinada biblioteca. Este é um passo à frente da versão do arquivo nativo do Windows, que foi usada apenas durante o processo de atualização, e não ao referenciar uma biblioteca, levando ao infame "DLL Hell".
Vale ressaltar que a Versão da Versão permite 4 inteiros de 32 bits, enquanto o AssemblyFileVersionAttribute é limitado a 16 bits, pois ele mapeia diretamente para o recurso VERSIONINFO. Assim, se queremos que AssemblyVersionAttribute e AssemblyFileVersionAttribute sejam iguais, isso também coloca um limite nos componentes da versão de montagem.
O Linux, em geral, usa um método diferente para abordar o controle de versão. Os arquivos binários não possuem um selo de versão incorporado, como a maioria dos binários do Windows. Em vez disso, um nome de arquivo da biblioteca compartilhada indica sua versão, e. /usr/local/lib/mylib. so.1.5.
São criados vários links simbólicos, e. mylib. so - & gt; mylib. so.1 e mylib. so.1 - & gt; mylib. so.1.5. Um aplicativo pode fazer referência a uma biblioteca via link simbólico, como mylib. so.1, e obter a versão compatível 1.x mais recente instalada.
Isso funciona bastante bem, desde que todos sigam esta convenção. Cada biblioteca pode então, por sua vez, carregar bibliotecas, dependendo da mesma maneira.
Os usuários de Linux também estarão familiarizados com o popular "Advanced Package Tool", apt-get, usado de forma ubíqua nos sistemas derivados da Debian, como o Ubuntu. Sendo um verdadeiro gerenciador de pacotes, ele suporta a instalação de versões lado a lado e dependências de rastreamento entre pacotes. Examinamos as vantagens dos gerenciadores de pacotes nas seções a seguir.
Esquemas dos números de versão.
Existem vários esquemas de numeração popular para software, mas todos eles são uma variação do mesmo tema e compartilham características comuns. Ter componentes principais e menores da versão é o mesmo em toda a placa. O que eles representam é bastante consistente:
Maior aumento de número: representa grandes mudanças de falha no sistema de software, muitas vezes não é compatível com versões anteriores, ou adição de grande quantidade de novas funcionalidades. Aumento de número menor: representa mudanças substanciais de evolução, principalmente atualizações ou melhorias na funcionalidade existente ou adição de uma nova nova conjunto de características.
Acima é apenas uma diretriz - não há regras estabelecidas sobre o que as versões maiores e menores devem representar. Só que eles deveriam aumentar à medida que mais recursos são adicionados ao software com o tempo.
Windows e binários especificam um esquema de versão de 4 partes: maior. menor. construir. revisão . Os dois últimos componentes são de forma bastante gratuita, existem muitas variações no que representam - alguns usam contadores de construção incrementais, alguns utilizam a data / hora da compilação, e alguns os derivam dos números de revisão interna do controle de origem.
Muitos ignoram o número da revisão e focam apenas na compilação. O Windows Installer, por exemplo, possui apenas 3 componentes. Se você deseja que sua versão abranja binários e o pacote que contém, então é melhor limitar-se a apenas três números: maior. menor. construir.
Em qualquer caso, o padrão geral: quanto maior for o número da versão, mais recente será o software.
Um popular sistema de controle de versão nos últimos anos (especialmente entre projetos de código aberto) foi apelidado de Versão Semântica (também conhecido como SemVer) e documentado no Semver. Ele apresenta alguns outros componentes, e torna a versão uma string alfanumérica, em vez de um número puro - abrindo algumas possibilidades interessantes.
Os três primeiros componentes são os mesmos que já discutimos, sendo o patch opcional. Patch é bastante equivalente ao componente de compilação, mas a semântica pode ser diferente. A versão semântica realmente prescreve quando cada componente deve ser incrementado (com base em mudanças de "API pública").
O pré-lançamento, se especificado, é uma seqüência alfanumérica que é usada para marcar uma versão como uma que precede a versão final. Por exemplo, 1.3.567-rc1 precederá 1.3.567. Isso é útil para adicionar mais significado ao rótulo da versão do que simplesmente usando números.
Metadata is another optional component, which allows further tagging of the version label (usually with a build timestamp), but it does not participate in version ordering, i. e. versions that only differ in metadata are considered the same.
Prerelease is useful with Package Managers like NuGet, which treat them differently - they are considered unstable and are not visible to general public, unless explicitly requested. This allows releasing alpha/beta versions without affecting those relying on stable releases.
Prerelease tags can also be useful in the internal release flow when dealing with parallel hotfixes and private builds, as discussed later in this article.
Versioning Non-Binary Files.
So we know how to stamp a version on the binary files . But what about the other files comprising a software system - configuration files, images, documents, fonts, etc? How do you stamp a version on them?
What about web frameworks like ASP (or Ruby, Node. js, Python, etc) where source files and pages can be modified in-place, and automatically updated? How can we patch a web system, i. e. update few target files, and still keep it versioned?
The answer is - don't update individual files ! There is no way for you to keep a meaningful version number for your software application, if individual non-binary files can be updated ad-hoc as hotfixes.
Update using a package instead.
Importance of Build and Package.
When you hear the term "build", normally the compilation comes to mind - most compiled languages, such as C#, C++ or Java, have to be compiled into a binary before being able to be executed. And so building is commonly associated with the process of compiling .
But that's not an entire picture. Some languages or frameworks, such as Python or ASP, don't strictly require compilation. They can be either interpreted, in Python's case, or compiled on-the-fly, in ASP's case. What should a build do for these systems? How do you "build" a Python app?
That's why it is more helpful to think of build as an assembly process , or simply packaging . Just like a line of consumer goods, e. g. shoes, gets packaged before shipping to the stores, so does a software system, before being released.
A package concept is essential to versioning , because a package is a single collection of the pieces that comprise a software system, or part of it, and can therefore be identified, and stamped with a version . With the right Package Management system (which we look at in the next section), it can be deployed and updated, and specify dependencies on the other packages.
Software today is never a single binary executable file - it is a collection of various binaries, libraries, documents, configuration files, images, and other resources. A package is what helps us group them together, version and release to the outside world.
A package doesn't have to be sophisticated, although it helps in some situations (e. g. databases). It can even be a simple ZIP file, that can contain version in the file name, or embedded as a text file. In fact, many open source projects do just that - a release is a ZIP or a. tar. gz archive.
The important thing is that a package is a single unit, that is released and updated at the same time, leading to consistency . It is common to have several packages, for example, representing "client" and "server" components, or any other logical grouping applicable to a software system. Each package can then be updated on its own.
Let's take a look at some of the common packaging methods, the versioning approach, and which application they are best suited for.
Windows Installer.
Best Suited : Complete Windows GUI Applications, Windows Services, or Drivers.
The oldest, and for a long time the only recommended way, to install applications on a Windows platform. It has a built-in versioning support and a sophisticated (some would say "complicated") set of rules for determining when to update components. While a Windows Installer package (.msi) is a single file, in essence, it is a collection of small logical components (down to single files) that can be updated independently.
Windows Installer will actually check each individual file that is being installed, whether it has a version and whether the version is greater than a file with the same name already installed. That means it is important to version not just the installer package, but each file contained in it. But it also means that it is incredibly difficult to do downgrades (i. e. rollbacks) with Windows Installer.
It is best suited for traditional Windows Applications (GUI, services, drivers) that are released to the public. It is, however, not the best choice for internally developed & distributed applications, any kind of Web applications, or database systems.
It was also used to deploy distributable libraries (native DLLs) and COM objects, but with today's focus on , it is not the right mechanism for distributing libraries.
Web Deploy.
Best Suited : Web Applications (IIS, ASP)
Web Deploy technology was specifically designed for deploying and synchronizing applications on Microsoft IIS web servers. IIS Web Farm replication uses Web Deploy commands and packages behind the scenes to synchronize sites across a set of servers. IIS Manager has an extension (enabled by installing Web Deploy) to "Import Application", which can install or update a web application using a Web Deploy zip package.
Its biggest disadvantage is that it can only be used for web applications on Microsoft IIS platform, and the limited mechanism for customizing installation. While it could be suited for simple web applications, it can quickly become frustrating for anything more sophisticated, i. e. variables, conditional logic, databases, etc.
In addition, it has no inherent support for versioning .
Package Managers.
Best Suited : Shared Libraries, Dependencies, Command-line Utilities.
Package Managers are great for releasing and versioning shared components, and tracking dependencies between them. For example, if you have a shared library that you want others to use, then a Package Manager allows you to publish multiple versions side-by-side, and for consumers of the library to reference the version they depend on. Package Managers can resolve all inter-package dependencies, and retrieve only the versions that are expected. In effect, Package Managers solve the "DLL Hell" problem.
They are best used during development, to resolve library dependencies. However some Package Manager, like Chocolatey for Windows or apt-get for Ubuntu, are geared towards installing complete software.
Most importantly, Package Managers are designed around the versioning concept . So they are a perfect mechanism for distributing versioned software libraries.
For we have NuGet. A lot of open-source libraries have been published to its online repository, and it is now the defacto standard for distributing 3rd party components. It is encouraged that every team sets up their own NuGet repository to share and publish internally developed libraries in a versioned manner.
NuGet can even be used to release complete software systems - see next section.
Other development environments have their own - npm for Node. js, pip for Python, gems for Ruby, apt-get on Linux. Package Managers have been proven to be extremely useful, and have exploded in popularity.
Octopus Deploy.
Best Suited : Internally Developed & Deployed Software.
Octopus uses NuGet as the packaging and versioning shell. It is similar to an installer, only driven by PowerShell, meaning infinite flexibility in how the software is to be deployed. PowerShell already has a great support for configuring Windows Services, IIS Web Applications, Scheduled Tasks, SQL Server, and more.
For internally developed and distributed software (i. e. for a company running home-grown software solutions) this is a perfect release management vehicle. Packages are versioned and pushed to a shared NuGet feed (e. g. a network share), from where Octopus Deploy can release and deploy each package into the appropriate environment.
NuGet here plays a role of the application package/container, with a version stamped on it. Package can be built once, and then deployed as many times as needed to whatever environment.
Versioning & Packaging Databases.
Database versioning is one of the biggest challenges in software projects. Almost every team I encountered, either completely ignored it or had something inadequate in place. It certainly presents a challenge - database systems mix schema definition with actual live data , and there is no single "file" that can be effectively versioned.
We have to recognize the database as an integral part of the software system. One that executes on a proprietary 3rd-party platform (SQL Server, Oracle, PostgreSQL, etc), but the source of which is part of the software definition. It can be compared to script-based systems, such as Node. js or Python, only the scripts are written in a SQL dialect.
There are essentially three popular approaches to database versioning, that support automated deployments (I am not considering manual approaches, because they are error-prone, and have nothing to do with real versioning!).
DB - Migrations.
"Migrations" is a concept where developers keep a set of organized SQL script files, numbered sequentially, where each script applies modifications to the target DB to bring it to the expected state. Whenever a change is needed to the application database, a developer creates a new migration script that applies the delta changes.
All of the scripts are kept as part of the source control, and are packaged with the application (either embedded into the executable binary, or installed along-side). A migrations library then checks the target database for a dedicated table which holds the last "migration script number" applied, and then runs all the scripts with a number greater than that in order, effectively applying all of the changes in turn.
While this approach is simple to implement, and is favored among several popular frameworks (Ruby Rails, Entity Framework), it has a number of significant short-comings . Firstly, there is no single source view of all database objects (i. e. tables, stored procedures, etc), they are sprinkled through the multiple migration scripts. It is not clear which of the scripts contains which of the modifications. One has to "replay" them all to generate a database, and then look directly in the database (rather than source code).
Secondly, the migration scripts number becomes the "version" of the database, which is different from the software package version number for the rest of the application. This is somewhat confusing. In addition, this "version" does not really identify the state of the database, since a database can be changed outside an application without updating the "version". This may potentially break future installs, because migration scripts expect the database to be in a certain state to work.
Thirdly, developers have to be disciplined enough to follow the structure and apply ALL changes through migration scripts . Furthermore, when developing and debugging locally, one often has to go through several iterations before getting that table or store procedure change right. Yet only the final changes should make it into the migration script , meaning they have to be remembered and written manually. Otherwise, migration scripts would contain all of the intermediate changes made by all developers on the project. It is easy to see how that can grow out of proportion quickly.
Finally, there is an argument that migration scripts are a "history of changes", and it is a bit of a redundancy to store them in source control, which already is a "history" of code changes. We would be storing a history of a history . There's something philosophical about that.
Supported by some frameworks and libraries (Rails, DbUp, RoundHousE, EF Code First) Can work with any database Potentially high degree of control over SQL scripts.
Have to manually maintain all migration scripts Tracking changes through source control is difficult Not robust against target database out-of-band changes.
DB - SQL Compare.
Most often this is used in a manual approach, comparing a database between two environments (e. g. development vs test) to copy over the changes. We are considering an automated approach, suitable for the packaging and versioning strategies being discussed.
In source control, database is represent by a series of creation scripts (e. g. to create tables, stored procedures, triggers, etc), such that a new database with the right schema can be created from scratch. Usually each script file logically represents a corresponding object in the database, e. g. Table1.sql would be the create script for Table1 table. All of the scripts are included in the released package (sometimes even combined into a large single create script, by concatenating them).
The idea is that during automated package deployment a temporary fresh database copy is created, by running all of the creation scripts , and then a SQL Compare tool is executed to compare the pristine copy with the target database to generate a migration delta script on the fly.
The advantage of this approach is that it is robust against the target database out-of-band changes, since delta script is generated during deployment , rather than during development. SQL Compare tools (such a RedGate's SQLCompare or XSQL Compare) are sophisticated and mature enough tools that we can have some confidence in the generate SQL code. Each can be controlled by a multitude of options to fine-tune behavior with respect to renames, reordering columns, avoiding drops, etc.
In this case, target database is considered as a runtime environment , and we avoid having the issue of versioning it . Instead we version the package that contains all of the creation scripts , which is much easier, and use it to synchronize target database with what's expected in each version.
The big disadvantage of this approach is the difficulty of getting it right - there is no off-the-shelf framework that would support it, and it has to be developed. For SQL Server, read the next section for a better approach. For others, some day I may put together the set of scripts and logic necessary to achieve this, based on some of my prior work (unless someone else beats me to it).
Automatically detect and migrate changes, regardless of target DB state Only maintaining DDL (i. e. create) scripts in source control, meaning easy change tracking.
More difficult to setup, especially to be automated Having to create a temporary database during each deployment (need " create database " permission)
DB - DACPAC (SQL Server)
For SQL Server there is now a new recommended approach - DACPAC, and it can be produced by Visual Studio 2012 and above, if using the SQL Server database project. Really, this is a slick variation of the "SQL Compare" method above, just that Microsoft has done all the heavy lifting for you!
Essentially, DACPAC is a zip package which contains an XML schema model of what the target database should look like. It is compiled by Visual Studio based on the creation scripts in your project. In fact, it represents that temporary pristine database that we would have had to create manually. Only it is done automatically and the schema represented in an XML format. The real bonus is that a DACPAC can be versioned , i. e. its metadata supports storing a version number.
SQL Server Data Tools can be used to deploy a DACPAC package, which really performs a SQL Compare operation between the in-memory database model loaded from DACPAC and the target database. It does the same thing as SQL Compare, but avoids having to create the extra temporary database copy to do the comparison.
For applications having SQL Server as a back-end, a DACPAC can be included as one of the deployable packages, stamped with appropriate version generated during the build. Starting with SQL Server 2008 R2, database can be registered as a Data-Tier Application, and the latest DAC version is tracked in a system view that can be queried.
Can package the whole DB definition into a single package (or several packages) Can apply the same version to the package as the rest of the software system Same advantages as the SQL Compare method.
SQL Server only Need to treat lookup data in a special way (post-deploy MERGE script)
Build Auto-versioning.
Given the importance of consistent versioning discussed above, it makes sense to implement a strategy for automatically generating and stamping a version number during the software automated build process. We want the version number to be applied to the produced packages, and also applied to all the binaries generated through compilation.
There are several well-known and not so well-known ways of achieving this. We look at pros and cons of each.
Applying Build Number.
There are some who prefer to update the version number manually just before a release. I will argue that this is a bad practice. Firstly, it is easy to forget to do it, if you don't have an automated system for incrementing the version build number. And, if it is easy to forget, it will be forgotten at some point.
Secondly, without automatically updating build number, there will be multiple packages produced from the source code that have the same version number, but different functionality (as more commits are made to the source control). This will be confusing to say the least.
It is better to have a process, like ones described below, where version number build component is automatically updated whenever a non-local build is made.
Multiple Versions for Multiple Components.
If there are multiple software components, where each needs to have its own version number, then it is best to split them each into its own separate build. Don't mix multiple version numbers in the same build, as it unnecessarily increases the complexity, and raises a question about which of the build numbers should be used to label the build itself (in addition to having to tag each source sub-tree separately).
Developer vs Continuous vs Release Builds.
Release build is the one that will potentially be released to public or a particular environment - test, staging, production, etc. That's the build that needs to be consistently versioned to keep track of changes that are included and to link back to the source code at the time of compilation.
Note that the Release build can scheduled - it is popular to have a Daily or Nightly build. In most situations it should be the Release build, i. e. it should be versioned and packaged ready to be released.
Continuous Integration builds run whenever someone commits to the repository and are used to validate that the code compiles, and passes unit tests. There is no need to version this build, as it is not intended to be released.
Developers must also be able to do a Developer build , whether it is to test/fix the build process itself, or to generate shared software components to be used in development. Such builds are intended to be run locally only and should never be publicly released.
You can default the build part of the version number to "0". This will identify Developer builds, i. e. ones that are not supposed to be released. For Release builds pass the build number to your build scripts as a property. Have MSBuild stamp a version number on all generated assemblies and packages.
Tagging Source Control.
Since one of the primary reasons for having a version number is to be able to link back to source code used to build the software (see beginning of the article), it is important to create tags/labels in source control that identify the state of source code at the time that version was built.
Various systems call it differently - TFS has "Labels", Git has "tags". Tag should include the full version (including the build number) of the build, so that it can later be found, if needed.
Build Number - Version File Auto Increment.
Common technique is to record version number together with source code, usually in a separate file (e. g. "version. txt"). The build process then finds the file, reads the version, increments the build number portion, and commits the file back to repository.
If the commit message also includes the version number, e. g "Auto-increment: 1.3.156.0" , then it comes in handy when viewing commit history. You can see the changes that occurred between versions clearly by seeing the commits between the two "Auto-increment: . " messages.
This works fairly well, but has a few drawbacks. Mainly due to the fact that "version" becomes part of the source code. When merging changes between say release branch and main, you have to resort to "cherry-picking" (i. e. selecting just the code changesets) to avoid merging the modified version number. That requires being always careful, because you can accidentally change the versioning sequence of another branch just by merging the "version file" into it.
Control over the build number sequence (i. e. sequential) Can make it easy to see changes between versions in source control history.
Difficult to control merging between code branches in source control.
Build Number - External.
Overcoming the drawbacks of the auto increment approach, it is possible to track the build number outside of the source tree. Build server software such as CruiseControl or TFS Builds can do that - they track a build number internally for each "project" and are able to pass it as a parameter to MSBuild.
Version file is still used, but it records major and minor versions only, and doesn't have to change between each build. This makes it easier to merge changes from release branches back to main and others, since they will contain only code changes, without being intermingled with version increments. Major/minor version changes would occur early in the development cycle, when starting work on the next update, and are already set by the time release branch is created.
Not modifying source tree on every build makes merging between branches easier Versioned builds are forced to be built by a dedicated build server.
Relies on a build system that can supply a build number (e. g. CruiseControl, TFS Builds) Changing build number sequence can be difficult (e. g. TFS Builds)
Build Number - Derived from Date/Time.
A popular alternative is to derive build number for the date/time of the build. The advantage being that it carries more meaning (useful in diagnosis), and each build inherently should get a different build number (with later builds getting a higher number).
The trick, of course, is fitting all this into a 16-bit number, if using the standard 4-part Windows version number. While some solve it by using both, the build and revision components, I cannot recommend it, because revision cannot always be applied to external packages (like Windows Installer, or NuGet), which use only a 3-part version number.
This only allows only 4 unique builds per day, which is not a lot, unless all you want is a daily build .
Not depending on keeping track of the last build number Build number can be given more meaning, if it derives from a date.
Build number is not sequential (but it increases nevertheless) Limited to 16-bit (maximum 65535), so some overflow into revision (4th) number.
Build Number - Derived from Source Control.
A variation of the previous technique is to derive build number from a unique property in source control. With a centralized SCM like Subversion or TFS, a revision or changeset number is an ever increasing number that is tied directly to the source code. The big problem with it is that it can quickly overflow the 16-bit limit, meaning you may have to accept build numbers looping back to zero.
An alternative in distributed SCM, like Git, is to use the size of the commit history log as the build number. This will monotonously increase for any single branch, as new commits are made. It too can overflow the 16-bit limit, but goes a lot further than the global revision number.
Example: git rev-list HEAD --count.
Not depending on keeping track of the last build number No possibility of "forgetting" to update version file, or accidentally merge it to/from another branch.
Commit history size will grow beyond 65,535 at some point, overflowing the 16-bit build number.
Parallel Branches.
It's no secret that developing for multiple versions requires multiple branches in source control, each representing a "version" stream for the software. They can be roughly divided into:
Development branches - where unstable code for the next version lives, and where developers commit daily work Feature branches - veering off from development branches, encorporating larger feature development, that would otherwise disrupt other team members Release branches - representing versions of released software, or a release undergoing stabilization.
Each release branch needs to have an identifying version, and is usually named after it, e. g. "1.7" . A decision of whether to create a new release branch depends on how long it is expected that it will be in stabilization mode before releasing, and whether concurrent live versions are permitted (i. e. for packaged software). If you need to be able to maintain & hotfix the current released version, while a new version is being tested & stabilized, then create a new branch.
Development and feature branches need to have a version number that is above any of the existing release branches to avoid confusion. For example, if a 1.7 release branch is created, for the upcoming 1.7 release, then immediately update development branch version sequence to 1.8 .
Versioning feature branches is more difficult, since you don't want to start a new versioning sequence for every feature . Nothing should be "released" from feature branches, so this version is for internal purposes only. If using Semantic Versioning, attach a prerelease tag to clearly indicate this is a version for a feature branch, e. g. 1.8.781-dev-feature-x .
In any case, you wouldn't deploy anything built from a feature branch to the shared testing or production environment, or release a package from it. So it is acceptable to have version sequence overlap with that of development branch.
Finally, in the next section we look at how to version patches & hotfixes that are applied to release branches.
Handling Patches / Hotfixes.
Devising a system to handle patches depends heavily on the rest of the software development cycle, which is what many teams forget when searching for the "one, true way" of handling concurrent patching of the released/production software in parallel with working on the new version.
For example, having a short QA/test cycle, where most of the tests are automated, results in a more simplified and robust system, which does not have to deal with multiple parallel hotfixes "in test".
Overlapping hotfixes.
One difficulty that comes with managing parallel development is consistent versioning and deployment strategy that would overcome inherent conflicts. Consider following scenario: you have recently released a software package 1.5.167. Two urgent show-stopping issues have slipped past your QA process and now require a quick fix. You assign two developers to work on each one in parallel. How would they commit their fixes to minimize conflicts? How do you test each fix? How do you release one independent of the other?
This is a good example of the complexity of software release processes that can be encountered in real-world teams. It applies both to internal software and packaged software, but distribution of the hotfix might be slightly different for each one.
First, let's consider what happens if we remove concurrency . In the case where the two issues are worked one after the other , the solution becomes simple. The first fix gets committed into the maintenance/hotfix branch for 1.5 release stream, a new build is generated, with an incremented build number. Build goes through a quick QA cycle to make sure there is no regression, and then it is ready to be deployed. Same process repeats for the second fix.
The problem with concurrent approach is the time when development is in parallel, creating the entangled case where there is no build/package that contains only one of the fixes , i. e. independent of the other. This problem is magnified by a slow QA cycle , usually meaning there are no automated tests. While one fix is in test, if a commit for a second fix is made to the same branch, and a problem is discovered with the first one, it becomes very difficult to separate the two now.
The culprit here is, of course, the concept of a partial fix - the state where the fix is not complete. It has been committed, but has a problem with it, requiring further commits . This can easily create the case of a hotfix branch where the two fixes are "entangled" (quantum physics on the code level!).
Solution is to remove possibility of a partial hotfix .
This means that each hotfix has to be coded and tested in a separate code stream, independent of the other. Once tested, and ready for release, it is merged into the main hotfix release branch, where the automated build can create a new package and apply versioning (i. e. increment build number, for example, to 1.5.168).
Second hotfix, once tested, also has to be merged into the main hotfix release branch. But, because during the work on this second hotfix, the first hotfix got released, we first merge the first hotfix into the second hotfix's branch ! This ensures that we can test how the second hotfix operates, when applied on top of the first hotfix, and merge any code conflicts, if any.
In the end, you want a system with both hotfixes applied - that is the "next" version. So it makes sense that whatever hotfix is "second", it is applied on top of the "first" one. And creating a packaged release from the single hotfix release branch ensures that the version number is consistently incremented for the whole system.
Of course, above means that we must create a separate branch for each hotfix. Some version control systems, namely Git, make this very easy and part of the expected developer workflow. If you are using a version control system like TFS, then creating new branches for each hotfix is a bit more painful. In TFS, I suggest using named Shelvesets feature to emulate Git's process, and perform initial QA tests for a hotfix from a Shelveset-branch build. Then commit Shelveset into the hotfix branch to build the official hotfix package (and perform necessary merging).
What about the versioning of the interim hotfix builds ? The main hotfix release branch would have a standard versioning scheme applied (as discussed above), either incrementing a build number, or using a timestamp. Each new hotfix, applied on top of all previous hotfixes, gets an increased build number , and the software version keeps moving forward.
However, when building from the developer hotfix branch (or Shelveset in TFS), we also need to apply a version to distinguish it from other builds, and be able to deploy it into QA/test environment. We want to be able to test each hotfix in isolation, applied on top of an existing released version of the software system. This becomes problematic, if you have a single test environment .
You do not want to apply both hotfixes into one test environment, because there is no guarantee that they won't conflict or affect each other. If you are able to quickly spin up a test environment for a hotfix development branch, then you can truly parallelize team efforts. For a shared test environment, they have to be applied one at a time :
Force install latest release version (e. g. 1.5.168) to bring environment to a known state Install the hotfix version to be tested Perform the tests (preferably automated) For shared test environnments this is the bottleneck, since no other hotfixes can be tested at the same time (automation can help minimize the time spent in this step) Repeat 1-3, until tests are satisfactory.
What this means is that each hotfix has to have its build version number greater than the latest released version, the one it is being applied on top of. There are several ways to achieve that. If using a derived build number , this should just work out of the box. If incrementing or using external build numbers, then the easiest option is to simply force the build for hotfix development branch (or Shelveset) to use a number greater than latest released version (i. e. .168).
With Semantic Versioning, we can setup hotfix builds to use a "prerelease" tag that clearly marks it as a hotfix-test build. For example - 1.5.169-check14761 , where the trailing number could be a reference to the issue tracking system. This works especially well when using NuGet as the packaging mechanism.
Once tested, the changes can be merged into hotfix release branch, and an official build generated, with incremented build version number.
NOTE: Above process to resolve concurrent hotfixes is undoubtedly complicated. It is intended to solve a particular real-world scenario, but one that does not happen too often. If there are no concurrent fixes expected, you can simplify your life by applying fixes directly to the hotfix release branch.
Patching a large system.
If applying hotfixes to a large system, we don't want to upgrade the whole thing, which may involve a lot of different components - services, GUI applications, scheduled jobs, databases, etc. Instead, we want to apply the fix only to affected parts.
This is where splitting the system into multiple packages helps. Each corresponds to a logically contained piece of the system - for example, each service, application, database, etc is its own package. That means they can be patched independently by applying just that package .
Care must be taken about dependencies, if hotfix affects multiple packages at once. Although, in that case, ask yourself is it really a hotfix or a new minor version?
Patching for specific installation.
Some software shops may have developed the practice of patching the software for individual customers (for packaged software), in other words creating a "custom" version for just that installation, without including this fix in the rest of released software streams. This is one of the worst situations to be in, with regards to versioning, since it creates a large number of variations that have to be maintained separately.
Instead, release a general update , moving the overall software version forward for that release stream. Adopt a "feature" system , where parts of the software can be turned on & off based on configuration. If a specific fix is needed for a particular installation, then that code can be encapsulated behind a configuration switch which turns this section of the code on or off. That particular customer can turn it on , while the rest can have it off!
This is also a popular technique in web applications, of which only one installation exists (on the server), where various "features" can be enabled based on "configuration" for each user , or a set of users.
Patching the changes only.
There is often the temptation to simply patch in the changes to the live/production system by editing/replacing one file, or updating one table or stored procedure. The change is small, and it seems like the fastest way to solve the imminent issue, without changing anything else in the system.
While it seems like a smaller risk to make only the necessary updates directly, it makes it a whole lot harder to know the state of the system in the future. As more and more such "small" patches get applied, there is no longer any reliable way to link the running system back to the original source code, making further maintenance exponentially more complicated (and, ironically, increasing the risk).
Updating individual non-binary (e. g. config files) or altering database objects does not update any version number . That means it is difficult to tell which changes have been made to the system, leading to "maintenance hell" (a variation of the infamous "DLL Hell").
Rule of thumb: Any change to the system should change the version number.
NOTE : Windows Installer allows a so called "small update", where product version number does not have to change, used for small hotfix patches. I believe this creates too much confusion, and so I do not recommend it. Windows Installer does track each patch, through package code, so you always know which patches have been applied. But it means now having to track and remove patches on subsequent product updates, which complicates the process. It may work for Microsoft Windows and Microsoft Office, but I wouldn't recommend using it for any system.
Palavras finais.
This turned out to be a much longer article than I originally anticipated when I sat down to write about versioning . I am hoping it proves useful for software engineers out there looking for some guidance on how to apply these concepts in their own projects.
Still this seems like only a partial treatment of the topic.
Everything I wrote above has been learned through the painful process of trial & error over the years. If just a few readers have an "aha!" moment while reading this, then I have achieved my goal!
O recurso abre caminho para a grandeza.
Ou tarefa que se ramifica para lá. Ou liberar ramificações. Você escolhe.
Almost all version control systems today support branches–independent lines of work that stem from one central code base. Dependendo do seu sistema de controle de versão, o ramo principal pode ser chamado de mestre, mainline, padrão ou tronco. Os desenvolvedores podem criar seus próprios ramos da linha principal do código e trabalhar de forma independente ao lado dele.
Por que se preocupar com a ramificação?
Branching allows teams of developers to easily collaborate inside of one central code base. Quando um desenvolvedor cria um ramo, o sistema de controle de versão cria uma cópia da base de código naquele momento. As mudanças na filial não afetam outros desenvolvedores no time. This is a good thing, obviously, because features under development can create instability, which would be highly disruptive if all work was happening on the main code line. But branches need not live in solitary confinement. Os desenvolvedores podem facilmente reduzir as mudanças de outros desenvolvedores para colaborar em recursos e garantir que seus ramos privados não se divertem muito do mestre.
Branches aren't just good for feature work. Os ramos podem isolar o time de mudanças arquitetônicas importantes, como atualizações, bibliotecas comuns, etc.
Três estratégias de ramificação para equipes ágeis.
Os modelos de ramificação geralmente diferem entre equipes e são objeto de muito debate na comunidade de software. Um grande tema é a quantidade de trabalho que deve permanecer em um ramo antes de ser incorporado de novo ao mestre.
Liberação de ramificação.
A ramificação de lançamento refere-se à idéia de que uma versão está inteiramente contida em um ramo. This means that late in the development cycle, the release manager will create a branch from the master (e. g., “1.1 development branch”). All changes for the 1.1 release need to be applied twice: once to the 1.1 branch and then to the master code line. Trabalhar com dois ramos é trabalho extra para a equipe e é fácil esquecer de fundir os dois ramos. Release branches can be unwieldy and hard to manage as many people are working on the same branch. Nós sentimos a dor de ter que combinar muitas mudanças diferentes em um único ramo. Se você deve fazer um ramo de lançamento, crie o ramo o mais próximo possível da versão real.
A ramificação de lançamento é uma parte importante do suporte de software versionado no mercado. Um único produto pode ter vários ramos de libertação (por exemplo, 1.1, 1.2, 2.0) para suportar o desenvolvimento de sustentação. Tenha em mente que as mudanças em versões anteriores (ou seja, 1.1) podem precisar ser mescladas para ramos de liberação posteriores (ou seja, 1.2, 2.0). Confira nosso webinar abaixo para saber mais sobre como gerenciar os ramos de lançamento com o Git.
Característica ramificação.
Os ramos de recursos geralmente são acoplados com bandeiras de recurso e ndash; & quot; toggles & quot; que permitem ou desativam um recurso dentro do produto. Isso facilita a implantação do código em mestre e controle quando o recurso é ativado, facilitando a implantação do código antes do recurso ser exposto aos usuários finais.
Outro benefício das bandeiras de recursos é que o código pode permanecer dentro da compilação, mas inativo enquanto ele está em desenvolvimento. Se algo estiver errado quando o recurso estiver ativado, um administrador do sistema pode reverter o sinalizador de recurso e voltar para um bom estado conhecido em vez de ter que implantar uma nova compilação.
Ramificação de tarefas.
Na Atlassian, nos concentramos em um fluxo de trabalho de ramificação por tarefa. Toda organização tem uma maneira natural de quebrar o trabalho em tarefas individuais dentro de um rastreador de problemas, como o Jira Software. As questões então se tornam o ponto central de contato da equipe para esse trabalho. A ramificação de tarefas, também conhecida como ramificação de problemas, conecta diretamente esses problemas com o código-fonte. Cada questão é implementada em seu próprio ramo com a chave de problema incluída no nome da filial. É fácil ver qual código implementa qual questão: basta procurar a chave de problema no nome do ramo. Com esse nível de transparência, é mais fácil aplicar mudanças específicas ao mestre ou qualquer ramo de lançamento legado em execução.
Desde centros ágiles em torno de histórias de usuários, os ramos de tarefas parecem bem com o desenvolvimento ágil. Cada história de usuário (ou correção de bugs) vive dentro de seu próprio ramo, facilitando a visão de quais problemas estão em andamento e estão prontos para serem lançados. Para um mergulho profundo em uma ramificação de tarefas (às vezes chamado de ramificação de problemas ou ramificação por questão), pegue algumas pipocas e confira a gravação do webinar abaixo do & ndash, um dos nossos mais populares de todos os tempos.
Agora conheça o gêmeo malvado da ramificação: a fusão.
Todos nós sofremos a dor de tentar integrar vários ramos em uma solução sensata. Tradicionalmente, sistemas de controle de versão centralizados como o Subversion fizeram uma operação muito dolorosa. But newer version control systems like Git and Mercurial take a different approach to tracking versions of files that live on different branches.
Os ramos tendem a ser de curta duração, tornando-os mais fáceis de fundir e mais flexíveis em toda a base do código. Entre a capacidade de mesclar freqüentemente e automaticamente os ramos como parte da integração contínua (CI), e o fato de que os ramos de curta duração simplesmente contêm menos mudanças, "fundir o inferno" torna-se uma coisa do passado para equipes usando Git e Mercurial.
That's what makes task branching so awesome!
Validar, validar, validar.
A version control system can only go so far in affecting the outcome of a merge. Testes automatizados e integração contínua também são críticos. A maioria dos servidores CI pode automaticamente colocar novos ramos em teste, reduzindo drasticamente o número de "surpresas" na junção final a montante e ajudando a manter a linha principal do código estável.
A Agile teve um enorme impacto sobre mim tanto profissional como pessoalmente, como eu aprendi, as melhores experiências são ágeis, tanto no código como na vida. You'll often find me at the intersection of technology, photography, and motorcycling. Encontre-me no Twitter! danradigan.
Sign up for more articles.
Obrigado por inscrever-se!
Como fazer scrum com o Jira Software.
Um guia passo a passo sobre como conduzir um projeto de Scrum, priorizar e organizar seu backlog em sprints, executar cerimônias Scrum e muito mais, tudo dentro do Jira Software.
Git se ramifica para equipes ágeis.
Mudar para o Git abre um novo nível de agilidade para equipes de software. Aqui é como projetar esquemas de ramificação para envio tanto SaaS quanto produtos instalados.
Obter através da App Store Leia esta publicação em nosso aplicativo!
Versionamento semântico em Agile.
Digamos que eu tenho 14 dias de iteração de sprint, onde eu tenho várias histórias para novos recursos, poucas melhorias e alguns erros para consertar. Eu também implanto essas mudanças quando estão prontas, eu não estou aguardando o fim do sprint.
Meu problema é - como controlar o controle de versão semântico de produtos desenvolvidos e mantidos assim? Se houver lançamento a cada 14 dias, será fácil, aumentarei o número da versão e anotarei todas as alterações no changelog. Mas e se as mudanças forem implantadas de forma contínua? Deveria haver uma versão aumentada sempre que algo for implantado? Ou devo esperar até que Sprint termine e depois disso, faça algum "currículo" e aumente o número da versão apenas uma vez por iteração de forma independente na implantação real? Quais são as melhores práticas para o controle de versão semântico no Agile?
EDITAR: Para melhor explicar minhas necessidades, eu quero uma mudança para as partes interessadas em primeiro lugar. Eu não acho que eles estarão interessados em novo registro em changelog após cada mudança implantada.
Para o gerenciamento de lançamento típico, você quer que um número de compilação seja gerado pelo seu sistema de compilação para que as DLLs sejam versionadas sempre que forem implantadas. Isso garantirá que você poderá verificar a versão que é implantada em um determinado servidor.
Sua versão de "marketing", que geralmente é colocada em notas de versão ou publicada em seu site, não deve ser atualizada em cada versão. Essas notas de lançamento devem ser acumuladas e agrupadas, provavelmente programadas com o final do seu sprint.
Se o clássico esquema de versão semântica "MAJOR. MINOR. PATCH" faz sentido, depende de quem você implantar e, especialmente, quando e com que frequência você se implanta para o usuário final. O esquema é mais útil se você trabalhar com versão estável "4.5", onde você começa com 4.5.0. As versões 4.5.1, 4.5.2, e assim por diante, contêm apenas correções de bugs, enquanto você internamente já trabalha na versão 4.6.
Por exemplo, se você fornecer um "ramo estável" ao seu usuário final, dê uma versão 4.5.0 para a implantação inicial e 4.5.1, 4.5.2 sempre que você liberar um patch. No seu desenvolvimento interno "ágil" e na implementação do meio do sprint, você já pode ter uma versão 4.6, basta chamá-la de "versão beta". Sempre que você o implanta no meio do sprint, adicione o número de compilação gerado automaticamente como "4.6.beta build 123". Quando o seu sprint termina, atribua-o "4.6.0" e mude o número da versão para o próximo sprint internamente para "4.7". Começar com ".0" é apenas uma convenção, você também pode usar o ".0" para etiquetar versões beta e começar com ".1" para seus usuários finais. IMHO a palavra "beta" é muito mais expressiva, dizendo a todos que o sprint "ainda não está completo".
Se você liberar um registro completo de alterações do usuário final com cada versão beta depende de você, mas pelo menos no final do sprint, o log de alterações deve ser concluído e sempre que você fornecer um bugfix ao usuário final, você também deve atualizar os documentos de histórico.
Você encontrará a estratégia de liberar dois ramos separados, um ramo "estável" com números de versão semântica e um "ramo de desenvolvimento" marcado com números de compilação ou algo similar, em muitos produtos de código aberto como o Inkscape, o Firefox ou o 7-zip.
Se, no entanto, você não trabalha com estações estáveis e de desenvolvimento separadas, e divulgue diariamente uma nova versão ao usuário final, você também deve incrementar um número de versão diariamente. Para tal caso, os números de versão "4.5.1", "4.5.2",. provavelmente refletirá suas implantações individuais e não indicará a diferença entre correções de erros e outras alterações. Isso pode estar certo, não é mais o "controle de versão semântico" clássico. Nesse cenário, você também pode implantar versões 4.5, 4.6, 4.7, 4.8, que não dá nenhuma diferença real.
No que diz respeito à sua pergunta sobre as entradas no seu changelog: IMHO quando algo é visível para o usuário final, vale a pena uma entrada no changelog, assim que você implantar a alteração. Por exemplo, se você usar alternar recursos e fazer alterações em algum recurso meio cozido que ainda não esteja ativado para o usuário, isso não pertence a um changelog. Se você fizer apenas refatoração, sem alteração visível para o usuário, isso não pertence a um changelog. Se você corrigir um bug que poderia ter afetado alguns usuários, isso pertence definitivamente ao changelog - e deve ser mencionado lá ao mesmo tempo em que você implanta o bugfix. E não importa se você liberar diariamente, mensalmente ou anualmente.
Eu usaria números de compilação. Normalmente, um número de compilação corresponderia à versão mais alta do sistema de controle de versão. Se o número de compilação das segundas-feiras era 1745 e verificou-se 5 mudanças durante a terça-feira, o número de construção das noites de terça-feira seria 1750.
Em seguida, faça um breve resumo do que mudou entre 1745 e 1750.
Então, toda vez que você atualiza o número da versão do seu sistema, você pode adicionar todos os resumos curtos das compilações para obter as alterações do último número da versão para o novo.
O método preferido que usei há pelo menos alguns anos agora é superar o número após a conclusão de cada história. Isto significa que as versões lançadas no final do sprint não serão contínuas, p. Depois de 1.2.3 você pode encontrar 1.5.2 em vez de 1.4.0.
No changelog você pode listar as versões intermediárias com suas descrições correspondentes ou apenas agrupar todas as mudanças na versão "lançada" e ignorar as versões no meio.
Inicialmente, eu estava com medo de que os usuários achassem os "buracos" entre os números de versão problemáticos, mas uma vez que eles sabem sobre isso, não é um problema na prática. A grande vantagem é que aumentar o número após cada história torna o processo menos propenso a erros - você não precisa verificar todo o trabalho a partir de 2 semanas para decidir qual será o próximo número de versão - ao olhar para uma única história, é óbvio . Além disso, os "saltos" em números de versão entre cada versão dão uma estimativa aproximada de quantas mudanças entraram no lançamento. No geral, eu achei que este sistema funcionou bem (isto foi com os clientes internos da empresa, mas, se você já trabalha em um ciclo de liberação ágil, ele também deveria funcionar para clientes externos).
Estratégias de versão de software.
Um perfeccionista preso no mundo real.
Software Versioning pode ser uma dessas áreas onde você nunca sente como se você tivesse exatamente isso. Não existe uma orientação definitiva com uma solução que satisfaça todos. Principalmente equipes de software estão confusas sobre o assunto, ou estão escolhendo ignorá-lo. Este guia visa preencher a lacuna e oferecer uma visão prática de várias estratégias populares e trade-offs.
Algumas das técnicas serão voltadas para a Microsoft stack (Windows), pois é o que tenho mais experiência, mas os princípios se aplicam em geral. Linux, Node. js, Python & amp; Ruby também é levemente tocada.
Versões em todo lugar.
Estamos bastante acostumados com o termo "versão" hoje em dia. Mais comumente usado no mundo do software, vazou para a mídia e outras indústrias. As seqüências de filmes estão sendo versionadas - "Fast & Furious 7" (7 !?), os sapatos estão sendo versionados - "Air Jordan XX8" e, mais popular, os livros estão sendo versionados - "One Minute Manager, 1984 edition". Na verdade, olhando para os livros, as pessoas estão sendo controladas há bastante tempo - "Enciclopédia Britânica", desde 1768 !.
A premissa é simples - à medida que os produtos vivem e continuam a ser melhorados, os lançamentos mais recentes devem ser distinguidos dos anteriores. O nome do produto não muda, porque o mercado já se familiarizou com isso, então algo é anexado no final para indicar que ele é mais novo (ou diferente).
Enquanto o controle de versão existia muito antes da era digital, o software realmente empurrou o problema para a frente. Modificar e liberar uma nova cópia de software é um processo muito rápido, muitas vezes mais rápido do que mudar uma linha de produção industrial para produzir uma nova peça de roupa ou imprimir uma nova edição de livros. Assim, os ciclos de iteração do software são muito mais curtos, e um potencial para muitas edições simultâneas é muito maior.
Basta usar anos (ou mesmo meses), como em edições de livros, não é suficiente. Novas versões do software podem ser produzidas em poucos minutos. Além disso, o software tem um enorme aspecto paralelo - fluxos de software - onde várias versões principais podem existir e todas podem ser atualizadas continuamente ao mesmo tempo. Isso raramente acontece com seus sapatos. (Eu queria fazê-lo, às vezes eu simplesmente não quero atualizar para o modelo de catálogo deste ano, quero uma melhoria para o meu antigo par!)
Por que Versão?
Antes de mergulhar em como implementar o controle de versão, vamos parar e considerar o motivo pelo qual queremos fazê-lo em primeiro lugar! Afinal, se conhecemos as razões exatas de por que é útil, podemos avaliar melhor se as soluções propostas são adequadas.
Nós aludimos a isso na última seção, referindo-se ao chamado versão pública. Esta é a versão que é visivelmente visível e, principalmente, possui peso no mercado (ou seja, é mais provável que seja definido pelo departamento de marketing / vendas). "Windows 7", "iPhone 5S", "Office 2013" - são exemplos de uma versão pública.
A versão pública destina-se a ser simples e memorável, indicando aos clientes que é novo e brilhante (assumindo que as pessoas geralmente querem "novo e brilhante"). As pessoas não entendem "10.6.6527.14789" - mas recebem "2013" ou "5". Tem sido cada vez mais popular usar o ano de lançamento como o número da versão pública, pois transmite de forma simples e poderosa o status atualizado. Os fabricantes de automóveis estão fazendo isso há muito tempo.
A versão privada é o que estamos acostumados no mundo do software. Um selo interno que (espero) identifica de forma exclusiva um determinado software. O software, como um carro, pode ser feito de muitas partes. Seguindo a analogia do carro, a "versão privada" do carro é o número do chassi VIN. Os fabricantes liberam e mantêm catálogos maciços de peças, mapeando para "números de versão" do carro. Um mecânico pode então pedir uma parte exata que corresponda ao seu veículo.
Sem um "número de peça privada", você não seria capaz de atender seu software na natureza, pois não saberia o "formato" exato que um módulo de substituição deve ser para se adequar ao sistema geral. Imagine se você foi forçado a mudar seu carro inteiro quando uma luz da cauda quebrou.
Portanto, o número da versão privada é usado como um identificador de catálogo. Destina-se a ser usado quando solucionar problemas ou atender seu software. (Eu gosto da analogia "dogtag" de Jeff Attwood!) Ele deve mapear para uma descrição do que é essa peça de software - qual é a sua forma e função. E o que melhor "descrição" do que o próprio código-fonte original!
O uso essencialmente se resume a:
Identificando o código fonte original para uma peça de software, para permitir parches incrementais e para confirmar a operação defeituosa Identificando se uma parte é "compatível" com outra, ou se ela pode substituí-la.
Tudo isso é realizado com um número de versão privado. A versão pública é simplesmente um moniker de marketing, e mapeia para uma ou mais partes internas de software, cada uma com sua própria versão privada. (Assim como o Toyota Corolla 2011 contém um quadro ZRE142 e um conversor de torque 32000-12420)
Uso da versão.
No Windows, um conceito de número de versão é suportado por uma camada do sistema operacional. Os números de versão são incorporados em todos os arquivos executáveis binários e podem ser vistos ao pairar sobre o EXE / DLL no Windows Explorer ou ao exibir Propriedades. Na verdade, qualquer arquivo que possa ter "recursos" pode ter uma versão, pois está armazenado no recurso VERSIONINFO.
Ele usa o formato comum em que todos nós somos usados para: major. minor. build. revision (por exemplo, "1.2.360.0"). É importante notar que cada número é limitado a 16 bits e, portanto, não pode exceder 65535. Isso tem certas implicações sobre o que podemos representar com esses números.
Observe que um rótulo para esses números não é definido de forma estrita - são simples 4 números inteiros curtos. Os dois primeiros são referidos como principais e menores, por unanimidade. Os dois últimos são onde vemos algumas variações, dependendo do esquema de versão.
Esta versão é usada de forma proeminente durante o processo de atualização do Windows, que utiliza a tecnologia Windows Installer (MSI) para atualizar várias partes do sistema. Essencialmente, o Windows Installer segue certas regras para determinar se a atualização que está instalando é mais nova que o que já está instalado. Se a versão for maior, então está ok para atualizar.
Naturalmente, esse conceito flui para o Framework, que foi construído em torno de muitos conceitos existentes do Windows. Nós temos a classe Version, que segue o paradigma 4 integer. Também podemos definir AssemblyVersionAttribute e AssemblyFileVersionAttribute, que especificam uma versão de montagem e um recurso de versão do Windows, respectivamente.
Em, a versão de montagem existe separadamente da versão baseada no Windows VERSIONINFO, que é o que você vê no Windows Explorer (ou propriedades do arquivo). Ele forma parte do nome forte da montagem e é usado exclusivamente pelo Framework ao resolver assemblies. A versão de duas montagens e a versão do arquivo do Windows - podem ser diferentes, mas, mais frequentemente, elas são as mesmas para evitar confusões.
usa a versão para rastreamento de dependência, ou seja, observando as versões de montagens a serem referenciadas, tornando assim óbvio quando uma atualização quebra a compatibilidade para aplicativos que dependem de uma determinada biblioteca. Este é um passo à frente da versão do arquivo nativo do Windows, que foi usada apenas durante o processo de atualização, e não ao referenciar uma biblioteca, levando ao infame "DLL Hell".
Vale ressaltar que a Versão da Versão permite 4 inteiros de 32 bits, enquanto o AssemblyFileVersionAttribute é limitado a 16 bits, pois ele mapeia diretamente para o recurso VERSIONINFO. Assim, se queremos que AssemblyVersionAttribute e AssemblyFileVersionAttribute sejam iguais, isso também coloca um limite nos componentes da versão de montagem.
O Linux, em geral, usa um método diferente para abordar o controle de versão. Os arquivos binários não possuem um selo de versão incorporado, como a maioria dos binários do Windows. Em vez disso, um nome de arquivo da biblioteca compartilhada indica sua versão, e. /usr/local/lib/mylib. so.1.5.
São criados vários links simbólicos, e. mylib. so - & gt; mylib. so.1 e mylib. so.1 - & gt; mylib. so.1.5. Um aplicativo pode fazer referência a uma biblioteca via link simbólico, como mylib. so.1, e obter a versão compatível 1.x mais recente instalada.
Isso funciona bastante bem, desde que todos sigam esta convenção. Cada biblioteca pode então, por sua vez, carregar bibliotecas, dependendo da mesma maneira.
Os usuários de Linux também estarão familiarizados com o popular "Advanced Package Tool", apt-get, usado de forma ubíqua nos sistemas derivados da Debian, como o Ubuntu. Sendo um verdadeiro gerenciador de pacotes, ele suporta a instalação de versões lado a lado e dependências de rastreamento entre pacotes. Examinamos as vantagens dos gerenciadores de pacotes nas seções a seguir.
Esquemas dos números de versão.
Existem vários esquemas de numeração popular para software, mas todos eles são uma variação do mesmo tema e compartilham características comuns. Ter componentes principais e menores da versão é o mesmo em toda a placa. O que eles representam é bastante consistente:
Maior aumento de número: representa grandes mudanças de falha no sistema de software, muitas vezes não é compatível com versões anteriores, ou adição de grande quantidade de novas funcionalidades. Aumento de número menor: representa mudanças substanciais de evolução, principalmente atualizações ou melhorias na funcionalidade existente ou adição de uma nova nova conjunto de características.
Acima é apenas uma diretriz - não há regras estabelecidas sobre o que as versões maiores e menores devem representar. Só que eles deveriam aumentar à medida que mais recursos são adicionados ao software com o tempo.
Windows e binários especificam um esquema de versão de 4 partes: maior. menor. construir. revisão . Os dois últimos componentes são de forma bastante gratuita, existem muitas variações no que representam - alguns usam contadores de construção incrementais, alguns utilizam a data / hora da compilação, e alguns os derivam dos números de revisão interna do controle de origem.
Muitos ignoram o número da revisão e focam apenas na compilação. O Windows Installer, por exemplo, possui apenas 3 componentes. Se você deseja que sua versão abranja binários e o pacote que contém, então é melhor limitar-se a apenas três números: maior. menor. construir.
Em qualquer caso, o padrão geral: quanto maior for o número da versão, mais recente será o software.
Um popular sistema de controle de versão nos últimos anos (especialmente entre projetos de código aberto) foi apelidado de Versão Semântica (também conhecido como SemVer) e documentado no Semver. Ele apresenta alguns outros componentes, e torna a versão uma string alfanumérica, em vez de um número puro - abrindo algumas possibilidades interessantes.
Os três primeiros componentes são os mesmos que já discutimos, sendo o patch opcional. Patch é bastante equivalente ao componente de compilação, mas a semântica pode ser diferente. A versão semântica realmente prescreve quando cada componente deve ser incrementado (com base em mudanças de "API pública").
O pré-lançamento, se especificado, é uma seqüência alfanumérica que é usada para marcar uma versão como uma que precede a versão final. Por exemplo, 1.3.567-rc1 precederá 1.3.567. Isso é útil para adicionar mais significado ao rótulo da versão do que simplesmente usando números.
Metadata is another optional component, which allows further tagging of the version label (usually with a build timestamp), but it does not participate in version ordering, i. e. versions that only differ in metadata are considered the same.
Prerelease is useful with Package Managers like NuGet, which treat them differently - they are considered unstable and are not visible to general public, unless explicitly requested. This allows releasing alpha/beta versions without affecting those relying on stable releases.
Prerelease tags can also be useful in the internal release flow when dealing with parallel hotfixes and private builds, as discussed later in this article.
Versioning Non-Binary Files.
So we know how to stamp a version on the binary files . But what about the other files comprising a software system - configuration files, images, documents, fonts, etc? How do you stamp a version on them?
What about web frameworks like ASP (or Ruby, Node. js, Python, etc) where source files and pages can be modified in-place, and automatically updated? How can we patch a web system, i. e. update few target files, and still keep it versioned?
The answer is - don't update individual files ! There is no way for you to keep a meaningful version number for your software application, if individual non-binary files can be updated ad-hoc as hotfixes.
Update using a package instead.
Importance of Build and Package.
When you hear the term "build", normally the compilation comes to mind - most compiled languages, such as C#, C++ or Java, have to be compiled into a binary before being able to be executed. And so building is commonly associated with the process of compiling .
But that's not an entire picture. Some languages or frameworks, such as Python or ASP, don't strictly require compilation. They can be either interpreted, in Python's case, or compiled on-the-fly, in ASP's case. What should a build do for these systems? How do you "build" a Python app?
That's why it is more helpful to think of build as an assembly process , or simply packaging . Just like a line of consumer goods, e. g. shoes, gets packaged before shipping to the stores, so does a software system, before being released.
A package concept is essential to versioning , because a package is a single collection of the pieces that comprise a software system, or part of it, and can therefore be identified, and stamped with a version . With the right Package Management system (which we look at in the next section), it can be deployed and updated, and specify dependencies on the other packages.
Software today is never a single binary executable file - it is a collection of various binaries, libraries, documents, configuration files, images, and other resources. A package is what helps us group them together, version and release to the outside world.
A package doesn't have to be sophisticated, although it helps in some situations (e. g. databases). It can even be a simple ZIP file, that can contain version in the file name, or embedded as a text file. In fact, many open source projects do just that - a release is a ZIP or a. tar. gz archive.
The important thing is that a package is a single unit, that is released and updated at the same time, leading to consistency . It is common to have several packages, for example, representing "client" and "server" components, or any other logical grouping applicable to a software system. Each package can then be updated on its own.
Let's take a look at some of the common packaging methods, the versioning approach, and which application they are best suited for.
Windows Installer.
Best Suited : Complete Windows GUI Applications, Windows Services, or Drivers.
The oldest, and for a long time the only recommended way, to install applications on a Windows platform. It has a built-in versioning support and a sophisticated (some would say "complicated") set of rules for determining when to update components. While a Windows Installer package (.msi) is a single file, in essence, it is a collection of small logical components (down to single files) that can be updated independently.
Windows Installer will actually check each individual file that is being installed, whether it has a version and whether the version is greater than a file with the same name already installed. That means it is important to version not just the installer package, but each file contained in it. But it also means that it is incredibly difficult to do downgrades (i. e. rollbacks) with Windows Installer.
It is best suited for traditional Windows Applications (GUI, services, drivers) that are released to the public. It is, however, not the best choice for internally developed & distributed applications, any kind of Web applications, or database systems.
It was also used to deploy distributable libraries (native DLLs) and COM objects, but with today's focus on , it is not the right mechanism for distributing libraries.
Web Deploy.
Best Suited : Web Applications (IIS, ASP)
Web Deploy technology was specifically designed for deploying and synchronizing applications on Microsoft IIS web servers. IIS Web Farm replication uses Web Deploy commands and packages behind the scenes to synchronize sites across a set of servers. IIS Manager has an extension (enabled by installing Web Deploy) to "Import Application", which can install or update a web application using a Web Deploy zip package.
Its biggest disadvantage is that it can only be used for web applications on Microsoft IIS platform, and the limited mechanism for customizing installation. While it could be suited for simple web applications, it can quickly become frustrating for anything more sophisticated, i. e. variables, conditional logic, databases, etc.
In addition, it has no inherent support for versioning .
Package Managers.
Best Suited : Shared Libraries, Dependencies, Command-line Utilities.
Package Managers are great for releasing and versioning shared components, and tracking dependencies between them. For example, if you have a shared library that you want others to use, then a Package Manager allows you to publish multiple versions side-by-side, and for consumers of the library to reference the version they depend on. Package Managers can resolve all inter-package dependencies, and retrieve only the versions that are expected. In effect, Package Managers solve the "DLL Hell" problem.
They are best used during development, to resolve library dependencies. However some Package Manager, like Chocolatey for Windows or apt-get for Ubuntu, are geared towards installing complete software.
Most importantly, Package Managers are designed around the versioning concept . So they are a perfect mechanism for distributing versioned software libraries.
For we have NuGet. A lot of open-source libraries have been published to its online repository, and it is now the defacto standard for distributing 3rd party components. It is encouraged that every team sets up their own NuGet repository to share and publish internally developed libraries in a versioned manner.
NuGet can even be used to release complete software systems - see next section.
Other development environments have their own - npm for Node. js, pip for Python, gems for Ruby, apt-get on Linux. Package Managers have been proven to be extremely useful, and have exploded in popularity.
Octopus Deploy.
Best Suited : Internally Developed & Deployed Software.
Octopus uses NuGet as the packaging and versioning shell. It is similar to an installer, only driven by PowerShell, meaning infinite flexibility in how the software is to be deployed. PowerShell already has a great support for configuring Windows Services, IIS Web Applications, Scheduled Tasks, SQL Server, and more.
For internally developed and distributed software (i. e. for a company running home-grown software solutions) this is a perfect release management vehicle. Packages are versioned and pushed to a shared NuGet feed (e. g. a network share), from where Octopus Deploy can release and deploy each package into the appropriate environment.
NuGet here plays a role of the application package/container, with a version stamped on it. Package can be built once, and then deployed as many times as needed to whatever environment.
Versioning & Packaging Databases.
Database versioning is one of the biggest challenges in software projects. Almost every team I encountered, either completely ignored it or had something inadequate in place. It certainly presents a challenge - database systems mix schema definition with actual live data , and there is no single "file" that can be effectively versioned.
We have to recognize the database as an integral part of the software system. One that executes on a proprietary 3rd-party platform (SQL Server, Oracle, PostgreSQL, etc), but the source of which is part of the software definition. It can be compared to script-based systems, such as Node. js or Python, only the scripts are written in a SQL dialect.
There are essentially three popular approaches to database versioning, that support automated deployments (I am not considering manual approaches, because they are error-prone, and have nothing to do with real versioning!).
DB - Migrations.
"Migrations" is a concept where developers keep a set of organized SQL script files, numbered sequentially, where each script applies modifications to the target DB to bring it to the expected state. Whenever a change is needed to the application database, a developer creates a new migration script that applies the delta changes.
All of the scripts are kept as part of the source control, and are packaged with the application (either embedded into the executable binary, or installed along-side). A migrations library then checks the target database for a dedicated table which holds the last "migration script number" applied, and then runs all the scripts with a number greater than that in order, effectively applying all of the changes in turn.
While this approach is simple to implement, and is favored among several popular frameworks (Ruby Rails, Entity Framework), it has a number of significant short-comings . Firstly, there is no single source view of all database objects (i. e. tables, stored procedures, etc), they are sprinkled through the multiple migration scripts. It is not clear which of the scripts contains which of the modifications. One has to "replay" them all to generate a database, and then look directly in the database (rather than source code).
Secondly, the migration scripts number becomes the "version" of the database, which is different from the software package version number for the rest of the application. This is somewhat confusing. In addition, this "version" does not really identify the state of the database, since a database can be changed outside an application without updating the "version". This may potentially break future installs, because migration scripts expect the database to be in a certain state to work.
Thirdly, developers have to be disciplined enough to follow the structure and apply ALL changes through migration scripts . Furthermore, when developing and debugging locally, one often has to go through several iterations before getting that table or store procedure change right. Yet only the final changes should make it into the migration script , meaning they have to be remembered and written manually. Otherwise, migration scripts would contain all of the intermediate changes made by all developers on the project. It is easy to see how that can grow out of proportion quickly.
Finally, there is an argument that migration scripts are a "history of changes", and it is a bit of a redundancy to store them in source control, which already is a "history" of code changes. We would be storing a history of a history . There's something philosophical about that.
Supported by some frameworks and libraries (Rails, DbUp, RoundHousE, EF Code First) Can work with any database Potentially high degree of control over SQL scripts.
Have to manually maintain all migration scripts Tracking changes through source control is difficult Not robust against target database out-of-band changes.
DB - SQL Compare.
Most often this is used in a manual approach, comparing a database between two environments (e. g. development vs test) to copy over the changes. We are considering an automated approach, suitable for the packaging and versioning strategies being discussed.
In source control, database is represent by a series of creation scripts (e. g. to create tables, stored procedures, triggers, etc), such that a new database with the right schema can be created from scratch. Usually each script file logically represents a corresponding object in the database, e. g. Table1.sql would be the create script for Table1 table. All of the scripts are included in the released package (sometimes even combined into a large single create script, by concatenating them).
The idea is that during automated package deployment a temporary fresh database copy is created, by running all of the creation scripts , and then a SQL Compare tool is executed to compare the pristine copy with the target database to generate a migration delta script on the fly.
The advantage of this approach is that it is robust against the target database out-of-band changes, since delta script is generated during deployment , rather than during development. SQL Compare tools (such a RedGate's SQLCompare or XSQL Compare) are sophisticated and mature enough tools that we can have some confidence in the generate SQL code. Each can be controlled by a multitude of options to fine-tune behavior with respect to renames, reordering columns, avoiding drops, etc.
In this case, target database is considered as a runtime environment , and we avoid having the issue of versioning it . Instead we version the package that contains all of the creation scripts , which is much easier, and use it to synchronize target database with what's expected in each version.
The big disadvantage of this approach is the difficulty of getting it right - there is no off-the-shelf framework that would support it, and it has to be developed. For SQL Server, read the next section for a better approach. For others, some day I may put together the set of scripts and logic necessary to achieve this, based on some of my prior work (unless someone else beats me to it).
Automatically detect and migrate changes, regardless of target DB state Only maintaining DDL (i. e. create) scripts in source control, meaning easy change tracking.
More difficult to setup, especially to be automated Having to create a temporary database during each deployment (need " create database " permission)
DB - DACPAC (SQL Server)
For SQL Server there is now a new recommended approach - DACPAC, and it can be produced by Visual Studio 2012 and above, if using the SQL Server database project. Really, this is a slick variation of the "SQL Compare" method above, just that Microsoft has done all the heavy lifting for you!
Essentially, DACPAC is a zip package which contains an XML schema model of what the target database should look like. It is compiled by Visual Studio based on the creation scripts in your project. In fact, it represents that temporary pristine database that we would have had to create manually. Only it is done automatically and the schema represented in an XML format. The real bonus is that a DACPAC can be versioned , i. e. its metadata supports storing a version number.
SQL Server Data Tools can be used to deploy a DACPAC package, which really performs a SQL Compare operation between the in-memory database model loaded from DACPAC and the target database. It does the same thing as SQL Compare, but avoids having to create the extra temporary database copy to do the comparison.
For applications having SQL Server as a back-end, a DACPAC can be included as one of the deployable packages, stamped with appropriate version generated during the build. Starting with SQL Server 2008 R2, database can be registered as a Data-Tier Application, and the latest DAC version is tracked in a system view that can be queried.
Can package the whole DB definition into a single package (or several packages) Can apply the same version to the package as the rest of the software system Same advantages as the SQL Compare method.
SQL Server only Need to treat lookup data in a special way (post-deploy MERGE script)
Build Auto-versioning.
Given the importance of consistent versioning discussed above, it makes sense to implement a strategy for automatically generating and stamping a version number during the software automated build process. We want the version number to be applied to the produced packages, and also applied to all the binaries generated through compilation.
There are several well-known and not so well-known ways of achieving this. We look at pros and cons of each.
Applying Build Number.
There are some who prefer to update the version number manually just before a release. I will argue that this is a bad practice. Firstly, it is easy to forget to do it, if you don't have an automated system for incrementing the version build number. And, if it is easy to forget, it will be forgotten at some point.
Secondly, without automatically updating build number, there will be multiple packages produced from the source code that have the same version number, but different functionality (as more commits are made to the source control). This will be confusing to say the least.
It is better to have a process, like ones described below, where version number build component is automatically updated whenever a non-local build is made.
Multiple Versions for Multiple Components.
If there are multiple software components, where each needs to have its own version number, then it is best to split them each into its own separate build. Don't mix multiple version numbers in the same build, as it unnecessarily increases the complexity, and raises a question about which of the build numbers should be used to label the build itself (in addition to having to tag each source sub-tree separately).
Developer vs Continuous vs Release Builds.
Release build is the one that will potentially be released to public or a particular environment - test, staging, production, etc. That's the build that needs to be consistently versioned to keep track of changes that are included and to link back to the source code at the time of compilation.
Note that the Release build can scheduled - it is popular to have a Daily or Nightly build. In most situations it should be the Release build, i. e. it should be versioned and packaged ready to be released.
Continuous Integration builds run whenever someone commits to the repository and are used to validate that the code compiles, and passes unit tests. There is no need to version this build, as it is not intended to be released.
Developers must also be able to do a Developer build , whether it is to test/fix the build process itself, or to generate shared software components to be used in development. Such builds are intended to be run locally only and should never be publicly released.
You can default the build part of the version number to "0". This will identify Developer builds, i. e. ones that are not supposed to be released. For Release builds pass the build number to your build scripts as a property. Have MSBuild stamp a version number on all generated assemblies and packages.
Tagging Source Control.
Since one of the primary reasons for having a version number is to be able to link back to source code used to build the software (see beginning of the article), it is important to create tags/labels in source control that identify the state of source code at the time that version was built.
Various systems call it differently - TFS has "Labels", Git has "tags". Tag should include the full version (including the build number) of the build, so that it can later be found, if needed.
Build Number - Version File Auto Increment.
Common technique is to record version number together with source code, usually in a separate file (e. g. "version. txt"). The build process then finds the file, reads the version, increments the build number portion, and commits the file back to repository.
If the commit message also includes the version number, e. g "Auto-increment: 1.3.156.0" , then it comes in handy when viewing commit history. You can see the changes that occurred between versions clearly by seeing the commits between the two "Auto-increment: . " messages.
This works fairly well, but has a few drawbacks. Mainly due to the fact that "version" becomes part of the source code. When merging changes between say release branch and main, you have to resort to "cherry-picking" (i. e. selecting just the code changesets) to avoid merging the modified version number. That requires being always careful, because you can accidentally change the versioning sequence of another branch just by merging the "version file" into it.
Control over the build number sequence (i. e. sequential) Can make it easy to see changes between versions in source control history.
Difficult to control merging between code branches in source control.
Build Number - External.
Overcoming the drawbacks of the auto increment approach, it is possible to track the build number outside of the source tree. Build server software such as CruiseControl or TFS Builds can do that - they track a build number internally for each "project" and are able to pass it as a parameter to MSBuild.
Version file is still used, but it records major and minor versions only, and doesn't have to change between each build. This makes it easier to merge changes from release branches back to main and others, since they will contain only code changes, without being intermingled with version increments. Major/minor version changes would occur early in the development cycle, when starting work on the next update, and are already set by the time release branch is created.
Not modifying source tree on every build makes merging between branches easier Versioned builds are forced to be built by a dedicated build server.
Relies on a build system that can supply a build number (e. g. CruiseControl, TFS Builds) Changing build number sequence can be difficult (e. g. TFS Builds)
Build Number - Derived from Date/Time.
A popular alternative is to derive build number for the date/time of the build. The advantage being that it carries more meaning (useful in diagnosis), and each build inherently should get a different build number (with later builds getting a higher number).
The trick, of course, is fitting all this into a 16-bit number, if using the standard 4-part Windows version number. While some solve it by using both, the build and revision components, I cannot recommend it, because revision cannot always be applied to external packages (like Windows Installer, or NuGet), which use only a 3-part version number.
This only allows only 4 unique builds per day, which is not a lot, unless all you want is a daily build .
Not depending on keeping track of the last build number Build number can be given more meaning, if it derives from a date.
Build number is not sequential (but it increases nevertheless) Limited to 16-bit (maximum 65535), so some overflow into revision (4th) number.
Build Number - Derived from Source Control.
A variation of the previous technique is to derive build number from a unique property in source control. With a centralized SCM like Subversion or TFS, a revision or changeset number is an ever increasing number that is tied directly to the source code. The big problem with it is that it can quickly overflow the 16-bit limit, meaning you may have to accept build numbers looping back to zero.
An alternative in distributed SCM, like Git, is to use the size of the commit history log as the build number. This will monotonously increase for any single branch, as new commits are made. It too can overflow the 16-bit limit, but goes a lot further than the global revision number.
Example: git rev-list HEAD --count.
Not depending on keeping track of the last build number No possibility of "forgetting" to update version file, or accidentally merge it to/from another branch.
Commit history size will grow beyond 65,535 at some point, overflowing the 16-bit build number.
Parallel Branches.
It's no secret that developing for multiple versions requires multiple branches in source control, each representing a "version" stream for the software. They can be roughly divided into:
Development branches - where unstable code for the next version lives, and where developers commit daily work Feature branches - veering off from development branches, encorporating larger feature development, that would otherwise disrupt other team members Release branches - representing versions of released software, or a release undergoing stabilization.
Each release branch needs to have an identifying version, and is usually named after it, e. g. "1.7" . A decision of whether to create a new release branch depends on how long it is expected that it will be in stabilization mode before releasing, and whether concurrent live versions are permitted (i. e. for packaged software). If you need to be able to maintain & hotfix the current released version, while a new version is being tested & stabilized, then create a new branch.
Development and feature branches need to have a version number that is above any of the existing release branches to avoid confusion. For example, if a 1.7 release branch is created, for the upcoming 1.7 release, then immediately update development branch version sequence to 1.8 .
Versioning feature branches is more difficult, since you don't want to start a new versioning sequence for every feature . Nothing should be "released" from feature branches, so this version is for internal purposes only. If using Semantic Versioning, attach a prerelease tag to clearly indicate this is a version for a feature branch, e. g. 1.8.781-dev-feature-x .
In any case, you wouldn't deploy anything built from a feature branch to the shared testing or production environment, or release a package from it. So it is acceptable to have version sequence overlap with that of development branch.
Finally, in the next section we look at how to version patches & hotfixes that are applied to release branches.
Handling Patches / Hotfixes.
Devising a system to handle patches depends heavily on the rest of the software development cycle, which is what many teams forget when searching for the "one, true way" of handling concurrent patching of the released/production software in parallel with working on the new version.
For example, having a short QA/test cycle, where most of the tests are automated, results in a more simplified and robust system, which does not have to deal with multiple parallel hotfixes "in test".
Overlapping hotfixes.
One difficulty that comes with managing parallel development is consistent versioning and deployment strategy that would overcome inherent conflicts. Consider following scenario: you have recently released a software package 1.5.167. Two urgent show-stopping issues have slipped past your QA process and now require a quick fix. You assign two developers to work on each one in parallel. How would they commit their fixes to minimize conflicts? How do you test each fix? How do you release one independent of the other?
This is a good example of the complexity of software release processes that can be encountered in real-world teams. It applies both to internal software and packaged software, but distribution of the hotfix might be slightly different for each one.
First, let's consider what happens if we remove concurrency . In the case where the two issues are worked one after the other , the solution becomes simple. The first fix gets committed into the maintenance/hotfix branch for 1.5 release stream, a new build is generated, with an incremented build number. Build goes through a quick QA cycle to make sure there is no regression, and then it is ready to be deployed. Same process repeats for the second fix.
The problem with concurrent approach is the time when development is in parallel, creating the entangled case where there is no build/package that contains only one of the fixes , i. e. independent of the other. This problem is magnified by a slow QA cycle , usually meaning there are no automated tests. While one fix is in test, if a commit for a second fix is made to the same branch, and a problem is discovered with the first one, it becomes very difficult to separate the two now.
The culprit here is, of course, the concept of a partial fix - the state where the fix is not complete. It has been committed, but has a problem with it, requiring further commits . This can easily create the case of a hotfix branch where the two fixes are "entangled" (quantum physics on the code level!).
Solution is to remove possibility of a partial hotfix .
This means that each hotfix has to be coded and tested in a separate code stream, independent of the other. Once tested, and ready for release, it is merged into the main hotfix release branch, where the automated build can create a new package and apply versioning (i. e. increment build number, for example, to 1.5.168).
Second hotfix, once tested, also has to be merged into the main hotfix release branch. But, because during the work on this second hotfix, the first hotfix got released, we first merge the first hotfix into the second hotfix's branch ! This ensures that we can test how the second hotfix operates, when applied on top of the first hotfix, and merge any code conflicts, if any.
In the end, you want a system with both hotfixes applied - that is the "next" version. So it makes sense that whatever hotfix is "second", it is applied on top of the "first" one. And creating a packaged release from the single hotfix release branch ensures that the version number is consistently incremented for the whole system.
Of course, above means that we must create a separate branch for each hotfix. Some version control systems, namely Git, make this very easy and part of the expected developer workflow. If you are using a version control system like TFS, then creating new branches for each hotfix is a bit more painful. In TFS, I suggest using named Shelvesets feature to emulate Git's process, and perform initial QA tests for a hotfix from a Shelveset-branch build. Then commit Shelveset into the hotfix branch to build the official hotfix package (and perform necessary merging).
What about the versioning of the interim hotfix builds ? The main hotfix release branch would have a standard versioning scheme applied (as discussed above), either incrementing a build number, or using a timestamp. Each new hotfix, applied on top of all previous hotfixes, gets an increased build number , and the software version keeps moving forward.
However, when building from the developer hotfix branch (or Shelveset in TFS), we also need to apply a version to distinguish it from other builds, and be able to deploy it into QA/test environment. We want to be able to test each hotfix in isolation, applied on top of an existing released version of the software system. This becomes problematic, if you have a single test environment .
You do not want to apply both hotfixes into one test environment, because there is no guarantee that they won't conflict or affect each other. If you are able to quickly spin up a test environment for a hotfix development branch, then you can truly parallelize team efforts. For a shared test environment, they have to be applied one at a time :
Force install latest release version (e. g. 1.5.168) to bring environment to a known state Install the hotfix version to be tested Perform the tests (preferably automated) For shared test environnments this is the bottleneck, since no other hotfixes can be tested at the same time (automation can help minimize the time spent in this step) Repeat 1-3, until tests are satisfactory.
What this means is that each hotfix has to have its build version number greater than the latest released version, the one it is being applied on top of. There are several ways to achieve that. If using a derived build number , this should just work out of the box. If incrementing or using external build numbers, then the easiest option is to simply force the build for hotfix development branch (or Shelveset) to use a number greater than latest released version (i. e. .168).
With Semantic Versioning, we can setup hotfix builds to use a "prerelease" tag that clearly marks it as a hotfix-test build. For example - 1.5.169-check14761 , where the trailing number could be a reference to the issue tracking system. This works especially well when using NuGet as the packaging mechanism.
Once tested, the changes can be merged into hotfix release branch, and an official build generated, with incremented build version number.
NOTE: Above process to resolve concurrent hotfixes is undoubtedly complicated. It is intended to solve a particular real-world scenario, but one that does not happen too often. If there are no concurrent fixes expected, you can simplify your life by applying fixes directly to the hotfix release branch.
Patching a large system.
If applying hotfixes to a large system, we don't want to upgrade the whole thing, which may involve a lot of different components - services, GUI applications, scheduled jobs, databases, etc. Instead, we want to apply the fix only to affected parts.
This is where splitting the system into multiple packages helps. Each corresponds to a logically contained piece of the system - for example, each service, application, database, etc is its own package. That means they can be patched independently by applying just that package .
Care must be taken about dependencies, if hotfix affects multiple packages at once. Although, in that case, ask yourself is it really a hotfix or a new minor version?
Patching for specific installation.
Some software shops may have developed the practice of patching the software for individual customers (for packaged software), in other words creating a "custom" version for just that installation, without including this fix in the rest of released software streams. This is one of the worst situations to be in, with regards to versioning, since it creates a large number of variations that have to be maintained separately.
Instead, release a general update , moving the overall software version forward for that release stream. Adopt a "feature" system , where parts of the software can be turned on & off based on configuration. If a specific fix is needed for a particular installation, then that code can be encapsulated behind a configuration switch which turns this section of the code on or off. That particular customer can turn it on , while the rest can have it off!
This is also a popular technique in web applications, of which only one installation exists (on the server), where various "features" can be enabled based on "configuration" for each user , or a set of users.
Patching the changes only.
There is often the temptation to simply patch in the changes to the live/production system by editing/replacing one file, or updating one table or stored procedure. The change is small, and it seems like the fastest way to solve the imminent issue, without changing anything else in the system.
While it seems like a smaller risk to make only the necessary updates directly, it makes it a whole lot harder to know the state of the system in the future. As more and more such "small" patches get applied, there is no longer any reliable way to link the running system back to the original source code, making further maintenance exponentially more complicated (and, ironically, increasing the risk).
Updating individual non-binary (e. g. config files) or altering database objects does not update any version number . That means it is difficult to tell which changes have been made to the system, leading to "maintenance hell" (a variation of the infamous "DLL Hell").
Rule of thumb: Any change to the system should change the version number.
NOTE : Windows Installer allows a so called "small update", where product version number does not have to change, used for small hotfix patches. I believe this creates too much confusion, and so I do not recommend it. Windows Installer does track each patch, through package code, so you always know which patches have been applied. But it means now having to track and remove patches on subsequent product updates, which complicates the process. It may work for Microsoft Windows and Microsoft Office, but I wouldn't recommend using it for any system.
Palavras finais.
This turned out to be a much longer article than I originally anticipated when I sat down to write about versioning . I am hoping it proves useful for software engineers out there looking for some guidance on how to apply these concepts in their own projects.
Still this seems like only a partial treatment of the topic.
Everything I wrote above has been learned through the painful process of trial & error over the years. If just a few readers have an "aha!" moment while reading this, then I have achieved my goal!
O recurso abre caminho para a grandeza.
Ou tarefa que se ramifica para lá. Ou liberar ramificações. Você escolhe.
Almost all version control systems today support branches–independent lines of work that stem from one central code base. Dependendo do seu sistema de controle de versão, o ramo principal pode ser chamado de mestre, mainline, padrão ou tronco. Os desenvolvedores podem criar seus próprios ramos da linha principal do código e trabalhar de forma independente ao lado dele.
Por que se preocupar com a ramificação?
Branching allows teams of developers to easily collaborate inside of one central code base. Quando um desenvolvedor cria um ramo, o sistema de controle de versão cria uma cópia da base de código naquele momento. As mudanças na filial não afetam outros desenvolvedores no time. This is a good thing, obviously, because features under development can create instability, which would be highly disruptive if all work was happening on the main code line. But branches need not live in solitary confinement. Os desenvolvedores podem facilmente reduzir as mudanças de outros desenvolvedores para colaborar em recursos e garantir que seus ramos privados não se divertem muito do mestre.
Branches aren't just good for feature work. Os ramos podem isolar o time de mudanças arquitetônicas importantes, como atualizações, bibliotecas comuns, etc.
Três estratégias de ramificação para equipes ágeis.
Os modelos de ramificação geralmente diferem entre equipes e são objeto de muito debate na comunidade de software. Um grande tema é a quantidade de trabalho que deve permanecer em um ramo antes de ser incorporado de novo ao mestre.
Liberação de ramificação.
A ramificação de lançamento refere-se à idéia de que uma versão está inteiramente contida em um ramo. This means that late in the development cycle, the release manager will create a branch from the master (e. g., “1.1 development branch”). All changes for the 1.1 release need to be applied twice: once to the 1.1 branch and then to the master code line. Trabalhar com dois ramos é trabalho extra para a equipe e é fácil esquecer de fundir os dois ramos. Release branches can be unwieldy and hard to manage as many people are working on the same branch. Nós sentimos a dor de ter que combinar muitas mudanças diferentes em um único ramo. Se você deve fazer um ramo de lançamento, crie o ramo o mais próximo possível da versão real.
A ramificação de lançamento é uma parte importante do suporte de software versionado no mercado. Um único produto pode ter vários ramos de libertação (por exemplo, 1.1, 1.2, 2.0) para suportar o desenvolvimento de sustentação. Tenha em mente que as mudanças em versões anteriores (ou seja, 1.1) podem precisar ser mescladas para ramos de liberação posteriores (ou seja, 1.2, 2.0). Confira nosso webinar abaixo para saber mais sobre como gerenciar os ramos de lançamento com o Git.
Característica ramificação.
Os ramos de recursos geralmente são acoplados com bandeiras de recurso e ndash; & quot; toggles & quot; que permitem ou desativam um recurso dentro do produto. Isso facilita a implantação do código em mestre e controle quando o recurso é ativado, facilitando a implantação do código antes do recurso ser exposto aos usuários finais.
Outro benefício das bandeiras de recursos é que o código pode permanecer dentro da compilação, mas inativo enquanto ele está em desenvolvimento. Se algo estiver errado quando o recurso estiver ativado, um administrador do sistema pode reverter o sinalizador de recurso e voltar para um bom estado conhecido em vez de ter que implantar uma nova compilação.
Ramificação de tarefas.
Na Atlassian, nos concentramos em um fluxo de trabalho de ramificação por tarefa. Toda organização tem uma maneira natural de quebrar o trabalho em tarefas individuais dentro de um rastreador de problemas, como o Jira Software. As questões então se tornam o ponto central de contato da equipe para esse trabalho. A ramificação de tarefas, também conhecida como ramificação de problemas, conecta diretamente esses problemas com o código-fonte. Cada questão é implementada em seu próprio ramo com a chave de problema incluída no nome da filial. É fácil ver qual código implementa qual questão: basta procurar a chave de problema no nome do ramo. Com esse nível de transparência, é mais fácil aplicar mudanças específicas ao mestre ou qualquer ramo de lançamento legado em execução.
Desde centros ágiles em torno de histórias de usuários, os ramos de tarefas parecem bem com o desenvolvimento ágil. Cada história de usuário (ou correção de bugs) vive dentro de seu próprio ramo, facilitando a visão de quais problemas estão em andamento e estão prontos para serem lançados. Para um mergulho profundo em uma ramificação de tarefas (às vezes chamado de ramificação de problemas ou ramificação por questão), pegue algumas pipocas e confira a gravação do webinar abaixo do & ndash, um dos nossos mais populares de todos os tempos.
Agora conheça o gêmeo malvado da ramificação: a fusão.
Todos nós sofremos a dor de tentar integrar vários ramos em uma solução sensata. Tradicionalmente, sistemas de controle de versão centralizados como o Subversion fizeram uma operação muito dolorosa. But newer version control systems like Git and Mercurial take a different approach to tracking versions of files that live on different branches.
Os ramos tendem a ser de curta duração, tornando-os mais fáceis de fundir e mais flexíveis em toda a base do código. Entre a capacidade de mesclar freqüentemente e automaticamente os ramos como parte da integração contínua (CI), e o fato de que os ramos de curta duração simplesmente contêm menos mudanças, "fundir o inferno" torna-se uma coisa do passado para equipes usando Git e Mercurial.
That's what makes task branching so awesome!
Validar, validar, validar.
A version control system can only go so far in affecting the outcome of a merge. Testes automatizados e integração contínua também são críticos. A maioria dos servidores CI pode automaticamente colocar novos ramos em teste, reduzindo drasticamente o número de "surpresas" na junção final a montante e ajudando a manter a linha principal do código estável.
A Agile teve um enorme impacto sobre mim tanto profissional como pessoalmente, como eu aprendi, as melhores experiências são ágeis, tanto no código como na vida. You'll often find me at the intersection of technology, photography, and motorcycling. Encontre-me no Twitter! danradigan.
Sign up for more articles.
Obrigado por inscrever-se!
Como fazer scrum com o Jira Software.
Um guia passo a passo sobre como conduzir um projeto de Scrum, priorizar e organizar seu backlog em sprints, executar cerimônias Scrum e muito mais, tudo dentro do Jira Software.
Git se ramifica para equipes ágeis.
Mudar para o Git abre um novo nível de agilidade para equipes de software. Aqui é como projetar esquemas de ramificação para envio tanto SaaS quanto produtos instalados.
No comments:
Post a Comment