Meu escritório está tentando descobrir como lidamos com as divisões e mesclagens de filiais e encontramos um grande problema.
Nosso problema é com sidebranches de longo prazo - do tipo em que você tem algumas pessoas trabalhando em uma sidebranch que se separa do master, desenvolvemos por alguns meses e, quando atingimos um marco, sincronizamos os dois.
Agora, IMHO, a maneira natural de lidar com isso é, esmagar o sidebranch em um único commit. master
continua progredindo; como deveria - não estamos retroativamente lançando meses de desenvolvimento paralelo na master
história da empresa. E se alguém precisar de uma melhor resolução para a história do sidebranch, é claro que ainda está tudo lá - simplesmente não está presente master
, está no sidebranch.
Aqui está o problema: trabalho exclusivamente com a linha de comando, mas o resto da minha equipe usa o GUIS. E descobri que o GUIS não tem uma opção razoável para exibir o histórico de outros ramos. Portanto, se você chegar a um commit de squash, dizendo "esse desenvolvimento esmagado do ramo XYZ
", é uma enorme dor ver o que está acontecendo XYZ
.
No SourceTree, até onde eu posso encontrar, é uma enorme dor de cabeça: se você está ligado master
e deseja ver o histórico master+devFeature
, precisa dar uma olhada master+devFeature
(tocando em cada arquivo diferente), ou então percorra um log exibindo TODAS as ramificações do seu repositório em paralelo até encontrar o lugar certo. E boa sorte para descobrir onde você está lá.
Meus colegas de equipe, com toda a razão, não querem ter um histórico de desenvolvimento tão inacessível. Então eles querem que essas grandes e longas sidebranches de desenvolvimento sejam mescladas, sempre com um commit de mesclagem. Eles não querem nenhum histórico que não seja imediatamente acessível a partir da ramificação principal.
Eu odeio essa ideia; significa um emaranhado infindável e inevitável da história do desenvolvimento paralelo. Mas não estou vendo que alternativa temos. E eu estou bem perplexo; isso parece bloquear quase tudo o que sei sobre um bom gerenciamento de filiais e será uma frustração constante para mim se não encontrar uma solução.
Temos alguma opção aqui além de mesclar constantemente sidebranches em master com commit de mesclagem? Ou existe uma razão pela qual o uso constante de consolidação de mesclagem não seja tão ruim quanto eu temo?
fonte
git log master+bugfixDec10th
ser o ponto de ruptura: - /merge --no-ff
que você quer? Pormaster
si só, você tem um commit que descreve o que mudou no branch, mas todos os commits ainda existem e são pais de HEAD.Respostas:
Mesmo usando o Git na linha de comando - tenho que concordar com seus colegas. Não é sensato compactar grandes alterações em um único commit. Você está perdendo a história dessa maneira, não apenas tornando-a menos visível.
O objetivo do controle de origem é rastrear o histórico de todas as alterações. Quando o que mudou por quê? Para esse fim, cada confirmação contém ponteiros para confirmações pai, um diff e metadados como uma mensagem de confirmação. Cada confirmação descreve o estado do código-fonte e o histórico completo de todas as alterações que levaram a esse estado. O coletor de lixo pode excluir confirmações inacessíveis.
Ações como rebasing, cherry picking ou squashing excluem ou reescrevem o histórico. Em particular, as confirmações resultantes não fazem mais referência às confirmações originais. Considere isto:
[1]: Alguns servidores Git permitem que as filiais sejam protegidas contra exclusão acidental, mas duvido que você queira manter todas as suas ramificações de recursos por toda a eternidade.
Agora você não pode mais procurar esse commit - ele simplesmente não existe.
A referência a um nome de filial em uma mensagem de confirmação é ainda pior, pois os nomes das ramificações são locais para um repo. O que está
master+devFeature
no seu checkout local pode estardoodlediduh
no meu. As ramificações estão apenas movendo rótulos que apontam para algum objeto de confirmação.De todas as técnicas de reescrita de histórico, o rebasing é o mais benigno, porque duplica as confirmações completas com todo o histórico e apenas substitui uma confirmação pai.
Que a
master
história inclua a história completa de todos os ramos que foram fundidos nela é uma coisa boa, porque isso representa a realidade. [2] Se houve desenvolvimento paralelo, isso deve estar visível no log.[2]: Por esse motivo, também prefiro confirmações de mesclagem explícita do que o histórico linearizado, mas, em última instância, falso, resultante do rebaseamento.
Na linha de comando,
git log
tenta simplificar o histórico exibido e manter todos os commits exibidos relevantes. Você pode ajustar a simplificação do histórico para atender às suas necessidades. Você pode ficar tentado a escrever sua própria ferramenta de log git que percorre o gráfico de confirmação, mas geralmente é impossível responder "esse commit foi originalmente comprometido neste ou naquele ramo?". O primeiro pai de uma consolidação de mesclagem é o anteriorHEAD
, ou seja, a consolidação no ramo no qual você está mesclando. Mas isso pressupõe que você não fez uma mesclagem reversa do mestre na ramificação do recurso e depois avançou o mestre rapidamente para a mesclagem.A melhor solução para ramificações de longo prazo que encontrei é impedir ramificações que são mescladas somente após alguns meses. A fusão é mais fácil quando as alterações são recentes e pequenas. Idealmente, você mesclará pelo menos uma vez por semana. A integração contínua (como na Extreme Programming, não como em “vamos configurar um servidor Jenkins”) até sugere várias mesclagens por dia, ou seja, para não manter ramificações de recursos separadas, mas compartilhar uma ramificação de desenvolvimento como uma equipe. A mesclagem antes do controle de qualidade de um recurso exige que o recurso fique oculto atrás de um sinalizador de recurso.
Em troca, a integração frequente torna possível detectar problemas em potencial muito mais cedo e ajuda a manter uma arquitetura consistente: alterações de longo alcance são possíveis porque essas alterações são rapidamente incluídas em todas as ramificações. Se uma alteração quebrar algum código, ela quebrará apenas alguns dias de trabalho, e não alguns meses.
A reescrita do histórico pode fazer sentido para projetos realmente grandes quando existem vários milhões de linhas de código e centenas ou milhares de desenvolvedores ativos. É questionável por que um projeto tão grande teria que ser um único repositório git em vez de ser dividido em bibliotecas separadas, mas nessa escala é mais conveniente se o repositório central contiver apenas "liberações" dos componentes individuais. Por exemplo, o kernel do Linux emprega squash para manter o histórico principal gerenciável. Alguns projetos de código aberto exigem que os patches sejam enviados por email, em vez de uma mesclagem no nível git.
fonte
git bisect
, apenas para que ele chegue a um uber-commit de 10.000 linhas a partir de um ramo lateral.Gosto da resposta de Amon , mas senti que uma pequena parte precisava de muito mais ênfase: você pode simplificar facilmente o histórico enquanto visualiza os logs para atender às suas necessidades, mas outros não podem adicionar histórico enquanto visualiza os logs para atender às suas necessidades. É por isso que é preferível manter a história como ocorreu.
Aqui está um exemplo de um de nossos repositórios. Usamos um modelo de solicitação de recebimento, para que cada recurso se pareça com suas ramificações de longa duração no histórico, mesmo que normalmente executem apenas uma semana ou menos. Às vezes, desenvolvedores individuais optam por esmagar seu histórico antes de mesclar, mas geralmente combinamos recursos, o que é relativamente incomum. Aqui estão os poucos commits
gitk
, o gui que vem junto com o git:Sim, um pouco de confusão, mas também gostamos porque podemos ver exatamente quem teve o que muda a que horas. Reflete com precisão nossa história de desenvolvimento. Se queremos ver uma visualização de nível superior, uma solicitação pull é mesclada por vez, podemos observar a seguinte visualização, que é equivalente ao
git log --first-parent
comando:git log
tem muito mais opções projetadas para fornecer exatamente as visualizações que você deseja.gitk
pode usar qualquergit log
argumento arbitrário para criar uma visualização gráfica. Tenho certeza que outras GUIs têm recursos semelhantes. Leia os documentos e aprenda a usá-lo corretamente, em vez de aplicar suagit log
visão preferida a todos no momento da mesclagem.fonte
merge --no-ff
- não use fusões fast-forward, em vez sempre criar uma mala comprometer de modo que--first-parent
tem algo para trabalhar comMeu primeiro pensamento é - nem faça isso, a menos que seja absolutamente necessário. Suas fusões devem ser um desafio às vezes. Mantenha os galhos independentes e com a menor duração possível. É um sinal de que você precisa dividir suas histórias em pedaços de implementação menores.
Caso você precise fazer isso, é possível mesclar no git com a opção --no-ff para que os históricos sejam mantidos distintos em seu próprio ramo. As confirmações ainda aparecerão no histórico mesclado, mas também podem ser vistas separadamente na ramificação do recurso, para que pelo menos seja possível determinar em qual linha de desenvolvimento eles fizeram parte.
Eu tenho que admitir que quando comecei a usar o git, achei um pouco estranho que o commit do ramo aparecesse no mesmo histórico que o branch principal após a mesclagem. Foi um pouco desconcertante porque não parecia que esses commits pertenciam a essa história. Mas, na prática, não é algo realmente doloroso, se considerarmos que o ramo de integração é exatamente isso - todo o seu objetivo é combinar os ramos do recurso. Em nossa equipe, não fazemos squash e fazemos commits de mesclagem freqüentes. Usamos --no-ff o tempo todo para garantir que seja fácil ver o histórico exato de qualquer recurso, caso desejemos investigá-lo.
fonte
git merge --no-ff
git merge --no-ff
. Se você usar gitflow, isso se tornará o padrão.Deixe-me responder seus pontos direta e claramente:
Você geralmente não deseja deixar suas filiais sem sincronização por meses.
Sua ramificação de recurso ramificou-se de algo, dependendo do seu fluxo de trabalho; vamos chamá-lo
master
por uma questão de simplicidade. Agora, sempre que você se comprometer com o domínio, você pode e devegit checkout long_running_feature ; git rebase master
. Isso significa que suas ramificações estão, por design, sempre sincronizadas.git rebase
também é a coisa correta a fazer aqui. Não é um truque ou algo estranho ou perigoso, mas completamente natural. Você perde um pouco de informação, que é o "aniversário" do ramo de recursos, mas é isso. Se alguém achar que isso é importante, ele poderá ser fornecido salvando-o em outro lugar (no seu sistema de tickets ou, se a necessidade for grande, em umgit tag
...).Não, você absolutamente não quer isso, deseja um commit de mesclagem. Uma consolidação de mesclagem também é uma "consolidação única". De alguma forma, ele não insere todas as confirmações individuais da ramificação "no" mestre. É um único commit com dois pais - a
master
cabeça e a cabeça do ramo no momento da mesclagem.Certifique-se de especificar a
--no-ff
opção, é claro; mesclar sem--no-ff
deve, no seu cenário, ser estritamente proibido. Infelizmente,--no-ff
não é o padrão; mas acredito que há uma opção que você pode definir que o faça. Veja ogit help merge
que--no-ff
faz (em resumo: ele ativa o comportamento que descrevi no parágrafo anterior), é crucial.Absolutamente não - você nunca está despejando algo "na história" de algum ramo, especialmente não com um commit de mesclagem.
Com um commit de mesclagem, ele ainda está lá. Não no mestre, mas no sidebranch, claramente visível como um dos pais da fusão, e mantida por toda a eternidade, como deveria ser.
Viu o que eu fiz? Todas as coisas que você descreve para o seu commit de squash estão ali com o
--no-ff
commit de mesclagem .(Observação: também trabalho quase exclusivamente com a linha de comando (bem, isso é mentira, eu geralmente uso o emacs magit, mas isso é outra história - se eu não estiver em um local conveniente com a configuração individual do emacs, prefiro o comando linha também). Mas, por favor, faça um favor e tentar pelo menos
git gui
uma vez. é por isso muito mais eficiente para escolher linhas, pedaços etc. para adicionar / ruína completa.)Isso porque o que você está tentando fazer é totalmente contra o espírito de
git
.git
constrói a partir do núcleo em um "gráfico acíclico direcionado", o que significa que muitas informações estão no relacionamento pai-filho de confirmações. E, para mesclagens, isso significa que a verdadeira mesclagem se compromete com dois pais e um filho. As GUIs de seus colegas ficarão bem assim que você usar osno-ff
commits de mesclagem.Sim, mas isso não é um problema da GUI, mas do squash commit. Usar uma abóbora significa deixar a cabeça do ramo do recurso pendente e criar uma confirmação totalmente nova
master
. Isso quebra a estrutura em dois níveis, criando uma grande bagunça.E eles estão absolutamente certos. Mas eles não são "fundidos em ", eles estão apenas se fundiram. Uma mesclagem é uma coisa verdadeiramente equilibrada, não possui um lado preferido que é mesclado "no" outro (
git checkout A ; git merge B
é exatamente o mesmo quegit checkout B ; git merge A
exceto por pequenas diferenças visuais, como as ramificações sendo trocadasgit log
etc.).O que é completamente correto. No momento em que não há recursos unificados, você teria um único ramo
master
com um histórico rico que encapsulava todas as linhas de confirmação de recursos que existiam, voltando aogit init
commit desde o início dos tempos (observe que eu especificamente evitei usar o termo " branches "na parte final desse parágrafo porque o histórico naquele momento não é mais" branches ", embora o gráfico de confirmação seja bastante ramificado).Então você sente um pouco de dor, já que está trabalhando contra a ferramenta que está usando. A
git
abordagem é muito elegante e poderosa, especialmente na área de ramificação / fusão; se você fizer o que é correto (como mencionado acima, especialmente com--no-ff
), é aos trancos e barrancos superado por outras abordagens (por exemplo, a confusão do subversion de ter estruturas de diretórios paralelas para ramificações).Sem fim, paralelo - sim.
Inavegável, emaranhado - não.
Por que não trabalhar como o inventor
git
, seus colegas e o resto do mundo, todos os dias?Nenhuma outra opção; não é tão ruim.
fonte
O esmagamento de uma sidebranch de longo prazo faria você perder muitas informações.
O que eu faria é tentar refazer o master no sidebranch de longo prazo antes de fundi-lo no master . Dessa forma, você mantém todos os commit no master, enquanto torna o histórico do commit linear e mais fácil de entender.
Se eu não pudesse fazer isso facilmente em cada commit, deixaria não linear , para manter o contexto de desenvolvimento claro. Na minha opinião, se eu tiver uma mescla problemática durante a reformulação do master na sidebranche, isso significa que a não linearidade tinha significado no mundo real. Isso significa que será mais fácil entender o que aconteceu, caso eu precise investigar a história. Eu também recebo o benefício imediato de não ter que fazer uma rebase.
fonte
rebase
. Se é a melhor abordagem aqui (eu pessoalmente não tive grandes experiências com ela, embora não a tenha usado muito), definitivamente parece ser a coisa mais próxima do que o OP realmente parece querer - especificamente, um compromisso entre um despejo de histórico fora de ordem e ocultar completamente esse histórico.Pessoalmente, prefiro fazer meu desenvolvimento em um fork, depois puxar solicitações para mesclar no repositório primário.
Isso significa que, se eu quiser refazer minhas alterações sobre o master ou esmagar algumas confirmações WIP, eu posso fazer isso totalmente. Ou posso apenas solicitar que toda a minha história seja mesclada também.
O que eu gosto de fazer é fazer meu desenvolvimento em uma ramificação, mas frequentemente me refiro ao master / dev. Dessa forma, recebo as alterações mais recentes do mestre sem ter um monte de confirmações de mesclagem em minha ramificação, ou sem precisar lidar com toda uma carga de conflitos de mesclagem quando é hora de mesclar com o master.
Para responder explicitamente à sua pergunta:
Sim - você pode mesclá-los uma vez por ramificação (quando o recurso ou correção estiver "completo") ou, se não gostar da consolidação da mesclagem em seu histórico, basta fazer uma mesclagem de avanço rápido no master após uma nova refazer final.
fonte
O controle de revisão é entrada e saída de lixo.
O problema é que o trabalho em andamento no ramo de recursos pode conter muitos "vamos tentar isso ... não que não funcionou, vamos substituí-lo por isso" e todos os commits, exceto o final "that" acabam poluindo inutilmente a história.
Por fim, o histórico deve ser mantido (alguns poderão ser úteis no futuro), mas apenas uma "cópia limpa" deve ser mesclada.
Com o Git, isso pode ser feito ramificando a ramificação do recurso primeiro (para manter todo o histórico), depois (interativamente) reorganizando a ramificação da ramificação do recurso do mestre e mesclando a ramificação reestruturada.
fonte
git
e são locais. O que é nomeadofoo
em um repositório pode ser nomeadobar
em outro. E eles devem ser temporários: você não deseja desorganizar seu repositório com milhares de ramos de recursos que precisam ser mantidos vivos para evitar perder o histórico. E você precisaria manter essas ramificações para manter o histórico como referência, poisgit
, eventualmente, excluirá qualquer confirmação que não seja mais alcançável por uma ramificação.