Chapters ▾ 2nd Edition

5.2 Git Distribuído - Contribuindo com um Projeto

Contribuindo com um Projeto

A principal dificuldade em descrever como contribuir para um projeto são as inúmeras variações sobre como fazer isso. Como o Git é muito flexível, as pessoas podem e trabalham juntas de muitas maneiras, e é problemático descrever como você deve contribuir — cada projeto é um pouco diferente. Algumas das variáveis envolvidas são a contagem de contribuidores ativos, o fluxo de trabalho escolhido, o seu acesso de commit e possivelmente o método de contribuição externa.

A primeira variável é a contagem de contribuidores ativos — quantos usuários estão contribuindo ativamente com código para este projeto e com que frequência? Em muitos casos, você terá dois ou três desenvolvedores com alguns commits por dia, ou possivelmente menos para projetos um tanto inativos (dormant). Para empresas ou projetos maiores, o número de desenvolvedores pode ser na casa dos milhares, com centenas ou milhares de commits chegando a cada dia. Isso é importante porque, com cada vez mais desenvolvedores, você se depara com mais problemas para garantir que o seu código se aplique de forma limpa ou possa ser facilmente mesclado. As alterações que você envia podem se tornar obsoletas ou gravemente corrompidas por trabalhos que são mesclados enquanto você estava trabalhando ou enquanto as suas alterações estavam aguardando para serem aprovadas ou aplicadas. Como você pode manter o seu código consistentemente atualizado e os seus commits válidos?

A próxima variável é o fluxo de trabalho em uso para o projeto. Ele é centralizado, com cada desenvolvedor tendo igual acesso de gravação à linha de código principal? O projeto tem um mantenedor ou gerente de integração que verifica todos os patches? Todos os patches são revisados por pares e aprovados? Você está envolvido nesse processo? Existe um sistema de tenentes (lieutenant) em vigor, e você tem que submeter o seu trabalho a eles primeiro?

A próxima variável é o seu acesso de commit. O fluxo de trabalho necessário para contribuir para um projeto é muito diferente se você tem acesso de gravação ao projeto do que se você não tem. Se você não tem acesso de gravação, como o projeto prefere aceitar o trabalho contribuído? Ele sequer tem uma política? Quanto trabalho você está contribuindo de uma vez? Com que frequência você contribui?

Todas essas questões podem afetar como você contribui de forma eficaz para um projeto e quais fluxos de trabalho são preferidos ou estão disponíveis para você. Abordaremos aspectos de cada uma delas em uma série de casos de uso, passando do simples para o mais complexo; você deve ser capaz de construir os fluxos de trabalho específicos de que precisa na prática a partir desses exemplos.

Diretrizes de Commit

Antes de começarmos a examinar os casos de uso específicos, aqui vai uma nota rápida sobre mensagens de commit. Ter uma boa diretriz para criar commits e cumpri-la torna o trabalho com o Git e a colaboração com outras pessoas muito mais fáceis. O projeto Git fornece um documento que expõe várias boas dicas para criar commits a partir dos quais enviar patches — você pode lê-lo no código-fonte do Git, no arquivo Documentation/SubmittingPatches.

Primeiro, as suas submissões não devem conter erros de espaço em branco (whitespace). O Git fornece uma maneira fácil de verificar isso — antes de fazer o commit, execute git diff --check, o qual identifica possíveis erros de espaço em branco e os lista para você.

Saída do `git diff --check`
Figure 56. Saída do git diff --check

Se você executar esse comando antes de fazer o commit, poderá saber se está prestes a commitar problemas de espaço em branco que podem irritar outros desenvolvedores.

Em seguida, tente fazer de cada commit um conjunto de alterações (changeset) logicamente separado. Se puder, tente tornar as suas alterações digeríveis — não codifique um fim de semana inteiro sobre cinco issues diferentes e depois as envie todas como um único commit enorme na segunda-feira. Mesmo que você não faça commits durante o fim de semana, use a área de preparo (staging area) na segunda-feira para dividir o seu trabalho em pelo menos um commit por issue, com uma mensagem útil por commit. Se algumas das alterações modificarem o mesmo arquivo, tente usar git add --patch para preparar arquivos parcialmente (abordado em detalhes em Área de Stage Interativa). O snapshot do projeto na ponta do branch é idêntico quer você faça um ou cinco commits, desde que todas as alterações sejam adicionadas em algum momento, então tente facilitar as coisas para os seus colegas desenvolvedores quando eles tiverem que revisar as suas alterações.

Esta abordagem também torna mais fácil extrair ou reverter um dos conjuntos de alterações se você precisar mais tarde. A seção Reescrevendo o Histórico descreve uma série de truques úteis do Git para reescrever o histórico e preparar arquivos interativamente — use essas ferramentas para ajudar a criar um histórico limpo e compreensível antes de enviar o trabalho para outra pessoa.

A última coisa a ter em mente é a mensagem de commit. Adquirir o hábito de criar mensagens de commit de qualidade torna o uso e a colaboração com o Git muito mais fáceis. Como regra geral, as suas mensagens devem começar com uma única linha que não tenha mais de 50 caracteres e que descreva o conjunto de alterações de forma concisa, seguida por uma linha em branco, seguida por uma explicação mais detalhada. O projeto Git requer que a explicação mais detalhada inclua a sua motivação para a alteração e contraste a sua implementação com o comportamento anterior — esta é uma boa diretriz a seguir. Escreva sua mensagem de commit no imperativo: "Fix bug", e não "Fixed bug" nem "Fixes bug.". Aqui está um modelo (template) que você pode seguir, o qual adaptamos levemente de um originalmente escrito por Tim Pope:

Capitalized, short (50 chars or less) summary

More detailed explanatory text, if necessary.  Wrap it to about 72
characters or so.  In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body.  The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase will confuse you if you run the
two together.

Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
or "Fixes bug."  This convention matches up with commit messages generated
by commands like git merge and git revert.

Further paragraphs come after blank lines.

- Bullet points are okay, too

- Typically a hyphen or asterisk is used for the bullet, followed by a
  single space, with blank lines in between, but conventions vary here

- Use a hanging indent

Se todas as suas mensagens de commit seguirem este modelo, as coisas serão muito mais fáceis para você e para os desenvolvedores com quem você colabora. O projeto Git tem mensagens de commit bem formatadas — tente rodar git log --no-merges nele para ver como se parece o histórico de commits de um projeto bem formatado.

Note
Faça o que dizemos, não o que fazemos.

Para fins de concisão, muitos dos exemplos neste livro não têm mensagens de commit bem formatadas como esta; em vez disso, nós simplesmente usamos a opção -m com o git commit.

Em resumo, faça o que dizemos, não o que fazemos.

Equipe Pequena Privada

A configuração mais simples que você provavelmente encontrará é um projeto privado com um ou dois outros desenvolvedores. “Privado” (Private), neste contexto, significa código fechado (closed-source) — não acessível ao mundo exterior. Você e todos os outros desenvolvedores têm acesso de push ao repositório.

Neste ambiente, você pode seguir um fluxo de trabalho semelhante ao que faria ao usar o Subversion ou outro sistema centralizado. Você ainda obtém as vantagens de coisas como commits offline e ramificações e mesclagens muito mais simples, mas o fluxo de trabalho pode ser muito semelhante; a principal diferença é que os merges acontecem do lado do cliente em vez de no servidor na hora do commit. Vamos ver como isso pode ser quando dois desenvolvedores começam a trabalhar juntos com um repositório compartilhado. O primeiro desenvolvedor, John, clona o repositório, faz uma alteração e faz o commit localmente. As mensagens de protocolo foram substituídas por …​ nesses exemplos para encurtá-las um pouco.

# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'Remove invalid default value'
[master 738ee87] Remove invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

A segunda desenvolvedora, Jessica, faz a mesma coisa — clona o repositório e faz o commit de uma alteração:

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'Add reset task'
[master fbff5bc] Add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

Agora, Jessica faz o push do seu trabalho para o servidor, o que funciona muito bem:

# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

A última linha da saída acima mostra uma mensagem de retorno útil da operação push. O formato básico é <oldref>..<newref> fromref → toref, onde oldref significa a referência antiga, newref significa a nova referência, fromref é o nome da referência local sofrendo o push, e toref é o nome da referência remota sendo atualizada. Você verá saídas semelhantes a esta nas discussões a seguir, portanto, ter uma ideia básica do significado ajudará a entender os vários estados dos repositórios. Mais detalhes estão disponíveis na documentação do git-push.

Continuando com este exemplo, pouco tempo depois, John faz algumas alterações, faz o commit delas no seu repositório local e tenta fazer o push para o mesmo servidor:

# John's Machine
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

Neste caso, o push de John falha por causa do push anterior de Jessica com as alterações dela. Isso é especialmente importante de entender se você estiver acostumado com o Subversion, porque você notará que os dois desenvolvedores não editaram o mesmo arquivo. Embora o Subversion faça tal mesclagem (merge) automaticamente no servidor se arquivos diferentes forem editados, com o Git, você deve primeiro mesclar os commits localmente. Em outras palavras, John deve primeiro fazer o fetch das alterações upstream de Jessica e mesclá-las no seu repositório local antes de ter permissão para fazer o push.

Como um primeiro passo, John faz o fetch do trabalho de Jessica (isso apenas faz o fetch do trabalho upstream de Jessica, ainda não o mescla no trabalho de John):

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

Neste ponto, o repositório local de John se parece com isso:

Histórico divergente de John
Figure 57. Histórico divergente de John

Agora John pode mesclar o trabalho de Jessica que ele buscou (fetched) no seu próprio trabalho local:

$ git merge origin/master
Merge made by the 'recursive' strategy.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Desde que essa mesclagem local ocorra sem problemas, o histórico atualizado de John agora se parecerá com isso:

Repositório de John após mesclar origin/master
Figure 58. Repositório de John após mesclar origin/master

Neste ponto, John pode querer testar este novo código para garantir que nenhum dos trabalhos de Jessica afete o dele e, desde que tudo pareça bem, ele pode finalmente fazer o push do novo trabalho mesclado para o servidor:

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

No final, o histórico de commits de John ficará assim:

Histórico de John após o push para o servidor origin
Figure 59. Histórico de John após o push para o servidor origin

Enquanto isso, Jessica criou um novo branch de tópico chamado issue54, e fez três commits nesse branch. Ela ainda não fez o fetch das alterações de John, então o histórico de commits dela é este:

Branch de tópico de Jessica
Figure 60. Branch de tópico de Jessica

De repente, Jessica descobre que John fez o push de um novo trabalho para o servidor e ela quer dar uma olhada, então ela pode fazer o fetch de todo o conteúdo novo do servidor que ela ainda não tem, com:

# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

Isso puxa (pulls down) o trabalho que John fez o push enquanto isso. O histórico de Jessica agora está assim:

Histórico de Jessica após buscar as alterações de John
Figure 61. Histórico de Jessica após buscar (fetching) as alterações de John

Jessica acha que o seu branch de tópico está pronto, mas ela quer saber qual parte do trabalho que ela buscou de John ela tem que mesclar no trabalho dela para que ela possa fazer o push. Ela executa git log para descobrir:

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   Remove invalid default value

A sintaxe issue54..origin/master é um filtro de log que pede ao Git para exibir apenas os commits que estão no último branch (neste caso origin/master) e que não estão no primeiro branch (neste caso issue54). Abordaremos essa sintaxe detalhadamente em Intervalos (Ranges) de Commits.

A partir da saída acima, podemos ver que há um único commit que John fez que Jessica não mesclou em seu trabalho local. Se ela mesclar origin/master, esse é o único commit que modificará o seu trabalho local.

Agora, Jessica pode mesclar o seu trabalho de tópico no seu branch master, mesclar o trabalho de John (origin/master) no seu branch master e, em seguida, fazer o push de volta para o servidor novamente.

Primeiro (tendo feito o commit de todo o trabalho no seu branch de tópico issue54), Jessica muda de volta para o seu branch master em preparação para integrar todo esse trabalho:

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

Jessica pode mesclar tanto origin/master quanto issue54 primeiro — ambos são upstream, então a ordem não importa. O snapshot final deve ser idêntico, não importa qual ordem ela escolha; apenas o histórico será diferente. Ela escolhe mesclar o branch issue54 primeiro:

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

Nenhum problema ocorre; como você pode ver, foi uma simples mesclagem fast-forward. Jessica agora conclui o processo de mesclagem local mesclando o trabalho de John buscado (fetched) anteriormente que está no branch origin/master:

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

Tudo se mescla de forma limpa, e o histórico de Jessica agora fica assim:

Histórico de Jessica após mesclar as alterações de John
Figure 62. Histórico de Jessica após mesclar as alterações de John

Agora origin/master pode ser alcançado a partir do branch master de Jessica, então ela deve ser capaz de fazer o push com sucesso (supondo que John não tenha feito o push de ainda mais alterações nesse meio tempo):

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

Cada desenvolvedor fez alguns commits e mesclou o trabalho do outro com sucesso.

Histórico de Jessica após o push de todas as alterações de volta para o servidor
Figure 63. Histórico de Jessica após o push de todas as alterações de volta para o servidor

Esse é um dos fluxos de trabalho mais simples. Você trabalha por um tempo (geralmente em um branch de tópico), e mescla esse trabalho no seu branch master quando ele estiver pronto para ser integrado. Quando você quiser compartilhar esse trabalho, você faz o fetch e o merge do seu master a partir do origin/master se ele tiver mudado, e finalmente faz o push para o branch master no servidor. A sequência geral é algo assim:

Sequência geral de eventos para um fluxo de trabalho Git simples com múltiplos desenvolvedores
Figure 64. Sequência geral de eventos para um fluxo de trabalho Git simples com múltiplos desenvolvedores

Equipe Gerenciada Privada

Neste próximo cenário, você analisará os papéis dos contribuidores em um grupo privado maior. Você aprenderá como trabalhar em um ambiente onde pequenos grupos colaboram em recursos, após o qual essas contribuições baseadas em equipe são integradas por outra parte.

Digamos que John e Jessica estejam trabalhando juntos em um recurso (vamos chamá-lo de “featureA”), enquanto Jessica e uma terceira desenvolvedora, Josie, estão trabalhando em um segundo (digamos, “featureB”). Neste caso, a empresa está usando um tipo de fluxo de trabalho de gerente de integração onde o trabalho dos grupos individuais é integrado apenas por certos engenheiros, e o branch master do repositório principal pode ser atualizado apenas por esses engenheiros. Neste cenário, todo o trabalho é feito em branches baseados em equipe e puxados (pulled) juntos pelos integradores mais tarde.

Vamos seguir o fluxo de trabalho de Jessica enquanto ela trabalha nos seus dois recursos, colaborando em paralelo com dois desenvolvedores diferentes neste ambiente. Supondo que ela já tenha o seu repositório clonado, ela decide trabalhar em featureA primeiro. Ela cria um novo branch para o recurso e trabalha um pouco nele lá:

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'Add limit to log function'
[featureA 3300904] Add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

Neste ponto, ela precisa compartilhar o seu trabalho com John, então ela faz o push dos commits do seu branch featureA para o servidor. Jessica não tem acesso de push ao branch master — apenas os integradores têm — então ela tem que fazer o push para outro branch para colaborar com John:

$ git push -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Jessica envia um e-mail para John para avisá-lo que ela fez o push de algum trabalho em um branch chamado featureA e que ele pode dar uma olhada agora. Enquanto ela espera pelo feedback de John, Jessica decide começar a trabalhar no featureB com Josie. Para começar, ela inicia um novo branch de recurso, baseando-o no branch master do servidor:

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

Agora, Jessica faz alguns commits no branch featureB:

$ vim lib/simplegit.rb
$ git commit -am 'Make ls-tree function recursive'
[featureB e5b0fdc] Make ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'Add ls-files'
[featureB 8512791] Add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

O repositório de Jessica agora está assim:

Histórico inicial de commits de Jessica
Figure 65. Histórico inicial de commits de Jessica

Ela está pronta para fazer o push do seu trabalho, mas recebe um e-mail de Josie dizendo que um branch com algum trabalho inicial de “featureB” já sofreu push no servidor como o branch featureBee. Jessica precisa mesclar essas alterações com as suas próprias antes que ela possa fazer o push do seu trabalho para o servidor. Primeiro, Jessica faz o fetch das alterações de Josie com git fetch:

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

Supondo que Jessica ainda esteja no seu branch featureB verificado (checked-out), ela agora pode mesclar o trabalho de Josie nesse branch com git merge:

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

Neste ponto, Jessica quer fazer o push de todo esse trabalho “featureB” mesclado de volta para o servidor, mas ela não quer simplesmente fazer o push do seu próprio branch featureB. Em vez disso, como Josie já iniciou um branch upstream featureBee, Jessica quer fazer o push para esse branch, o que ela faz com:

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

Isso é chamado de refspec (especificação de referência). Consulte Refspec para uma discussão mais detalhada sobre refspecs do Git e coisas diferentes que você pode fazer com eles. Observe também a flag -u; esta é uma abreviação para --set-upstream, que configura os branches para facilitar pushes e pulls (puxar) posteriormente.

De repente, Jessica recebe um e-mail de John, que lhe diz que ele fez o push de algumas alterações para o branch featureA no qual eles estão colaborando, e ele pede que Jessica dê uma olhada nelas. Novamente, Jessica executa um simples git fetch para buscar todo o conteúdo novo do servidor, incluindo (claro) o trabalho mais recente de John:

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

Jessica pode exibir o log do novo trabalho de John comparando o conteúdo do branch featureA recém-buscado (fetched) com a sua cópia local do mesmo branch:

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    Increase log output to 30 from 25

Se Jessica gostar do que vê, ela pode mesclar o novo trabalho de John no seu branch featureA local com:

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

Por fim, Jessica pode querer fazer algumas pequenas alterações em todo esse conteúdo mesclado, então ela é livre para fazer essas alterações, fazer o commit delas no seu branch featureA local e fazer o push do resultado final de volta para o servidor:

$ git commit -am 'Add small tweak to merged content'
[featureA 774b3ed] Add small tweak to merged content
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

O histórico de commits de Jessica agora está mais ou menos assim:

Histórico de Jessica após fazer um commit num branch de recurso
Figure 66. Histórico de Jessica após fazer um commit num branch de recurso

Em algum momento, Jessica, Josie e John informam aos integradores que os branches featureA e featureBee no servidor estão prontos para integração na linha principal (mainline). Depois que os integradores mesclam esses branches na linha principal, um fetch trará o novo commit de mesclagem (merge commit), deixando o histórico assim:

Histórico de Jessica após mesclar os seus dois branches de tópico
Figure 67. Histórico de Jessica após mesclar os seus dois branches de tópico

Muitos grupos mudam para o Git devido a essa capacidade de ter várias equipes trabalhando em paralelo, mesclando as diferentes linhas de trabalho no final do processo. A capacidade de subgrupos menores de uma equipe de colaborarem via branches remotos, sem necessariamente ter que envolver ou impedir a equipe inteira, é um benefício enorme do Git. A sequência para o fluxo de trabalho que você viu aqui é algo assim:

Sequência básica deste fluxo de trabalho de equipe gerenciada
Figure 68. Sequência básica deste fluxo de trabalho de equipe gerenciada

Projeto Público Bifurcado (Forked)

Contribuir para projetos públicos é um pouco diferente. Como você não tem as permissões para atualizar diretamente os branches no projeto, tem que levar o trabalho aos mantenedores de alguma outra forma. Este primeiro exemplo descreve a contribuição por meio de bifurcação (forking) em hosts Git que suportam forking facilmente. Muitos sites de hospedagem suportam isso (incluindo GitHub, BitBucket, repo.or.cz e outros), e muitos mantenedores de projetos esperam esse estilo de contribuição. A próxima seção trata de projetos que preferem aceitar patches contribuídos por e-mail.

Primeiro, você provavelmente vai querer clonar o repositório principal, criar um branch de tópico para o patch ou série de patches com os quais planeja contribuir, e fazer o seu trabalho nele. A sequência se parece basicamente com isto:

$ git clone <url>
$ cd project
$ git checkout -b featureA
  ... work ...
$ git commit
  ... work ...
$ git commit
Note

Você pode querer usar rebase -i para esmagar (squash) o seu trabalho em um único commit ou reorganizar o trabalho nos commits para tornar o patch mais fácil para o mantenedor revisar — veja Reescrevendo o Histórico para obter mais informações sobre rebasing interativo.

Quando o trabalho do seu branch terminar e você estiver pronto para contribuir de volta aos mantenedores, vá para a página original do projeto e clique no botão “Fork”, criando o seu próprio fork gravável do projeto. Você então precisará adicionar a URL deste repositório como um novo remoto do seu repositório local; neste exemplo, vamos chamá-lo de myfork:

$ git remote add myfork <url>

Você então precisará fazer o push do seu novo trabalho para este repositório. É mais fácil fazer o push do branch de tópico em que você está trabalhando para o seu repositório originado de um fork, em vez de mesclar esse trabalho no seu branch master e fazer o push disso. O motivo é que, se o seu trabalho não for aceito ou for escolhido a dedo (cherry-picked), você não precisa voltar atrás no seu branch master (a operação cherry-pick do Git é abordada com mais detalhes em Fluxos de Trabalho de Rebase e Cherry-Pick). Se os mantenedores usarem merge, rebase ou cherry-pick no seu trabalho, você eventualmente o receberá de volta puxando (pulling) do repositório deles de qualquer maneira.

Em qualquer caso, você pode fazer o push do seu trabalho com:

$ git push -u myfork featureA

Depois que o seu trabalho for enviado para o fork do repositório, você precisará notificar os mantenedores do projeto original que tem um trabalho que gostaria que eles mesclassem. Isso costuma ser chamado de pull request (solicitação de pull), e você normalmente gera essa solicitação por meio do site — o GitHub tem o seu próprio mecanismo “Pull Request” que abordaremos em GitHub — ou você pode executar o comando git request-pull e enviar a saída resultante por e-mail para o mantenedor do projeto manualmente.

O comando git request-pull recebe o branch base no qual você deseja que o seu branch de tópico seja puxado (pulled) e a URL do repositório Git de onde você quer que eles o puxem, e produz um resumo de todas as alterações que você está solicitando. Por exemplo, se Jessica quiser enviar um pull request a John e tiver feito dois commits no branch de tópico que ela acabou de dar push, ela pode executar isto:

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):
        Create new function

are available in the git repository at:

  https://githost/simplegit.git featureA

Jessica Smith (2):
      Add limit to log function
      Increase log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

Esta saída pode ser enviada para o mantenedor — ela informa a ele de onde o trabalho foi ramificado (branched), resume os commits e identifica de onde o novo trabalho deve ser puxado.

Em um projeto do qual você não é o mantenedor, geralmente é mais fácil ter um branch como master que rastreie o origin/master e fazer o seu trabalho em branches de tópicos que podem ser facilmente descartados caso sejam rejeitados. Ter os temas de trabalho isolados em branches de tópicos também facilita o rebase do seu trabalho se a ponta (tip) do repositório principal se moveu nesse meio tempo e seus commits não se aplicam mais de forma limpa. Por exemplo, se você quiser submeter um segundo tópico de trabalho para o projeto, não continue trabalhando no branch de tópico que você acabou de dar o push — comece do zero a partir do branch master do repositório principal:

$ git checkout -b featureB origin/master
  ... work ...
$ git commit
$ git push myfork featureB
$ git request-pull origin/master myfork
  ... email generated request pull to maintainer ...
$ git fetch origin

Agora, cada um de seus tópicos está contido em um silo — semelhante a uma fila de patches — que você pode reescrever, fazer o rebase e modificar sem que os tópicos interfiram ou dependam uns dos outros, assim:

Histórico inicial de commits com o trabalho featureB
Figure 69. Histórico inicial de commits com o trabalho featureB

Digamos que o mantenedor do projeto puxou vários outros patches e tentou o seu primeiro branch, mas ele não se mescla mais de forma limpa. Neste caso, você pode tentar fazer o rebase desse branch no topo do origin/master, resolver os conflitos para o mantenedor, e então reenviar as suas alterações:

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

Isso reescreve o seu histórico para agora se parecer com Histórico de commits após o trabalho featureA.

Histórico de commits após o trabalho featureA
Figure 70. Histórico de commits após o trabalho featureA

Como você fez o rebase do branch, você tem que especificar o -f para o seu comando de push a fim de conseguir substituir o branch featureA no servidor por um commit que não é descendente dele. Uma alternativa seria fazer o push deste novo trabalho para um branch diferente no servidor (talvez chamado featureAv2).

Vamos analisar mais um cenário possível: o mantenedor olhou o trabalho no seu segundo branch e gostou do conceito, mas gostaria que você mudasse um detalhe de implementação. Você também aproveitará essa oportunidade para mover o trabalho e baseá-lo no branch master atual do projeto. Você inicia um novo branch baseado no branch origin/master atual, usa o squash nas alterações de featureB ali, resolve os conflitos, faz a alteração de implementação e, em seguida, faz o push disso como um novo branch:

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
  ... change implementation ...
$ git commit
$ git push myfork featureBv2

A opção --squash pega todo o trabalho do branch mesclado e usa squash num único changeset, produzindo o estado do repositório como se uma mesclagem (merge) real tivesse acontecido, sem na verdade criar um merge commit. Isso significa que o seu commit futuro terá apenas um pai e permite que você introduza todas as alterações de outro branch e então faça mais alterações antes de registrar o novo commit. Além disso, a opção --no-commit pode ser útil para atrasar o merge commit no caso do processo de merge padrão.

Neste ponto, você pode notificar o mantenedor que você fez as alterações solicitadas, e que eles podem encontrar essas alterações no seu branch featureBv2.

Histórico de commits após o trabalho featureBv2
Figure 71. Histórico de commits após o trabalho featureBv2

Projeto Público Grande

Muitos projetos maiores estabeleceram procedimentos para aceitar patches — você precisará verificar as regras específicas de cada projeto, porque elas serão diferentes. Como há vários projetos mais antigos e maiores que aceitam patches via uma lista de discussão de desenvolvedores, examinaremos um exemplo disso agora.

O fluxo de trabalho é semelhante ao caso de uso anterior — você cria branches de tópicos para cada série de patches em que trabalha. A diferença está em como você as submete ao projeto. Em vez de fazer o fork do projeto e fazer o push para a sua própria versão gravável, você gera versões de e-mail de cada série de commits e as envia por e-mail para a lista de discussão de desenvolvedores:

$ git checkout -b topicA
  ... work ...
$ git commit
  ... work ...
$ git commit

Agora você tem dois commits que deseja enviar para a lista de discussão. Você usa git format-patch para gerar os arquivos no formato mbox que você pode enviar por e-mail para a lista — ele transforma cada commit em uma mensagem de e-mail com a primeira linha da mensagem de commit como o assunto e o resto da mensagem mais o patch que o commit introduz como o corpo. O lado bom disso é que aplicar um patch a partir de um e-mail gerado com format-patch preserva adequadamente todas as informações do commit.

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-increase-log-output-to-30-from-25.patch

O comando format-patch imprime os nomes dos arquivos de patch que ele cria. A opção -M diz ao Git para procurar por renomeações. Os arquivos acabam ficando assim:

$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] Add limit to log function

Limit log functionality to the first 20

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

Você também pode editar esses arquivos de patch para adicionar mais informações para a lista de e-mail que você não quer que apareçam na mensagem de commit. Se você adicionar texto entre a linha --- e o início do patch (a linha diff --git), os desenvolvedores poderão lê-lo, mas esse conteúdo é ignorado pelo processo de aplicação de patch.

Para enviar isso por e-mail a uma lista de discussão, você pode colar o arquivo no seu programa de e-mail ou enviá-lo através de um programa de linha de comando. Colar o texto muitas vezes causa problemas de formatação, especialmente com clientes “mais inteligentes” que não preservam as quebras de linha e outros espaços em branco adequadamente. Felizmente, o Git fornece uma ferramenta para ajudá-lo a enviar patches devidamente formatados via IMAP, o que pode ser mais fácil para você. Nós demonstraremos como enviar um patch via Gmail, que por acaso é o agente de e-mail que conhecemos melhor; você pode ler instruções detalhadas para diversos programas de e-mail no final do já mencionado arquivo Documentation/SubmittingPatches no código-fonte do Git.

Primeiro, você precisa configurar a seção imap no seu arquivo ~/.gitconfig. Você pode definir cada valor separadamente com uma série de comandos git config, ou você pode adicioná-los manualmente, mas no final seu arquivo de configuração deve se parecer com isso:

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = YX]8g76G_2^sFbd
  port = 993
  sslverify = false

Se o seu servidor IMAP não usar SSL, as duas últimas linhas provavelmente não são necessárias, e o valor do host será imap:// em vez de imaps://. Quando isso estiver configurado, você pode usar git imap-send para colocar a série de patches na pasta Drafts (Rascunhos) do servidor IMAP especificado:

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

Neste ponto, você deve ser capaz de ir até a sua pasta de Rascunhos, alterar o campo Para (To) para a lista de discussão para a qual você está enviando o patch, possivelmente colocar em cópia (CC) o mantenedor ou a pessoa responsável por aquela seção, e enviá-lo.

Você também pode enviar os patches por meio de um servidor SMTP. Como antes, você pode definir cada valor separadamente com uma série de comandos git config, ou pode adicioná-los manualmente na seção sendemail do seu arquivo ~/.gitconfig:

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

Depois que isso for feito, você pode usar git send-email para enviar os seus patches:

$ git send-email *.patch
0001-add-limit-to-log-function.patch
0002-increase-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

Então, o Git cospe um monte de informações de log parecidas com isto para cada patch que você está enviando:

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] Add limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK
Tip

Para obter ajuda sobre a configuração do seu sistema e e-mail, mais dicas e truques e um sandbox para enviar um patch de teste por e-mail, acesse git-send-email.io.

Resumo

Nesta seção, abordamos vários fluxos de trabalho e falamos sobre as diferenças entre trabalhar como parte de uma pequena equipe em projetos de código fechado em vez de contribuir para um grande projeto público. Você sabe verificar erros de espaço em branco antes de fazer um commit e sabe escrever uma ótima mensagem de commit. Você aprendeu como formatar patches e enviá-los por e-mail a uma lista de discussão de desenvolvedores. Lidar com mesclagens também foi abordado no contexto dos diferentes fluxos de trabalho. Agora você está bem preparado para colaborar em qualquer projeto.

A seguir, você verá como trabalhar do outro lado da moeda: mantendo um projeto Git. Você aprenderá como ser um ditador benevolente ou gerente de integração.