Hoje tive que resolver um problema interessante: um cliente nosso está usando TFS Source Control com TFS 2013 (infelizmente ainda não usam Git). Nós da Lambda3 adoramos Git e usamos em todos os projetos. O cliente não tinha problema algum que mantivéssemos nosso projeto no nosso TFS com Git, desde que o repositório dele ficasse atualizado com o exato mesmo código. Como resolver sem ficar manualmente copiando arquivos?
O primeiro desafio é a barreira TFVC e Git. Pra isso há o gittf, um plugin que permite que você use git para trabalhar com um repo TFVC. Você comita localmente, e pode usar seus commits para gerar um changeset no TFVC, podendo usar todos os benefícios que o Git te oferece. Então o primeiro passo é clonar o repositório TFVC com git e gittf. Essa parte foi fácil.
O segundo desafio é que o repositório do cliente já existia. Nós estamos trabalhando em um diretório separado, não precisamos integrar nosso código com código deles, apenas enviar o que fizemos pra eles verem. E a estrutura de diretórios do repositório deles no TFVC não tem nada a ver com a que estamos usando no nosso projeto (com Git).
Descobri o “git format-patch”. Ele gera arquivos de patch que você pode importar em outro repositório com o comando “git am”. Só que isso não funcionou direito. O comando format-patch é feito para commits lineares, quando as pessoas estão trocando alterações entre si por e-mail (o comando em si tem opções para envio por e-mail e trabalha com outro comando git para enviar o e-mail diretamente, bem interessante), não para esse tipo de integração. Ele gera diversos arquivos, um para cada commit, que o “am” aplica. No entanto, se há um merge no meio do histórico, o “am” falha. O “am” precisa que os arquivos do repositório e o patch encaixem perfeitamente, ele não é tão esperto quanto um comando de merge. E por causa dos merges que existiam nos nossos repositórios, que mudavam os locais das linhas de código de alguns commits, ele falhava.
Nossa sorte é que o cliente não exige todo nosso histórico. Eu posso gerar um único commit pra eles, e tudo bem, desde que o código esteja atualizado. Lembrei então que é possível você fazer merge e, a partir de diversos commits, gerar um só, com a opção “–squash”. Assim, bastaria eu criar um branch no meu repositório para o cliente apontando pro primeiro commit, e então fazer um merge de master (nosso branch de desenvolvimento) com squash. Com isso, os merges desaparecerão, e ficará só o estado final, do qual posso gerar os patches limpos com “format-patch”, e importar depois com o “am”.
Depois de importado, precisei apenas empurrar com o comando “git tf checkin”.
Aqui como eu fiz.
No meu repositório de trabalho (diretório GIT) eu crio um branch temporário para o cliente, a partir do primeiro commit (esse 347ea61), os comandos começam após “GIT$” que informa o diretório:
GIT$ git branch clienteTemp 347ea61
Checkout do branch “clienteTemp”:
GIT$ git checkout clienteTemp
Merge com Squash (gera alterações no working directory):
GIT$ git merge master --squash
Isso gera alterações no working directory, mas não faz um commit. Aqui o commit:
GIT$ git commit -am "meta: <meta da sprint>"
Gerando os arquivos de patch. Isso vai gerar 2 arquivos no diretório c:\temp\patches, um para o primeiro commit, e outro para todos os outros, que foram squashed:
GIT$ git format-patch --root HEAD -o C:\tmp\patches\
O trabalho de gerar os patches terminou. Vamos limpar tudo.
Volto para o branch master:
GIT$ git checkout –b master
Agora apago o branch temporário (ele não serve mais, já gerei os patches):
GIT$ git branch -d clienteTemp
Crio um branch do cliente a partir de master para indicar de onde foi o último ponto que fizemos a integração dos repositórios:
GIT$ git checkout –b cliente
Mando o branch do cliente para o meu remote (a partir da segunda vez não precisaria do “–set-upstream”).
$GIT git push --set-upstream origin cliente
Agora trabalhando no repositório TFVC. Primeiro clono o repo TFS do cliente com gittf (diretório TFS):
TFS$ git tf clone https://<tfs.cliente>/tfs/<collection> $/<collection>
Aí usando “git am” importo os patches. Fiz isso no cygwin porque no Powershell o Git não estava encontrando os patches no diretório “c:\temp\patches”. Note o argumento “–directory” com o qual informo o caminho onde os patches deverão ser aplicados. Ele é contado a partir da raiz do projeto Git, e não de onde o Current Working Directory, fique atento com isso. Esse comando deve rodar sem erros, já que fizemos o squash dos commits anteriores.
TFS$ git am --directory=OutroSubdir/OutroSubSubDir /c/tmp/patches/*
Nesse ponto já tenho os commits do repositório original no novo repositório. Falta mandar eles de volta para o TFS. Faço assim:
TFS$ git tf checkin --deep
O argumento “–deep” informa o gittf de que deve manter o histórico, ou seja, cada commit git vira um commit do tfvc. De outra forma ele faria outro squash e mandaria um único commit pro tfvc.
A solução funcionou bem, e a partir de agora vai ficar fácil manter o cliente atualizado. A partir da segunda vez fica um pouco diferente porque o ponto de início não é mais o primeiro commit, mas o branch master, que indica o último ponto de integração. Fica assim:
Faço um checkout do branch do cliente (último commit já enviado):
GIT$ git checkout cliente
Crio o branch temporário a partir de branch do cliente:
GIT$ git checkout –b clienteTemp
Merge com Squash a partir de master, vai gerar alterações no working directory contendo todas as alterações de todos os commits, de master até o branch cliente (e portanto clienteTemp, que está no mesmo ponto):
GIT$ git merge master --squash
Novamente o commit após o squash:
GIT$ git commit -am "meta: <meta da sprint>"
Gerando os arquivos de patch:
GIT$ git format-patch -1 HEAD -o C:\tmp\patches\
Volto para o branch cliente:
GIT$ git checkout cliente
Agora apago o branch temporário:
GIT$ git branch -D clienteTemp
E faço merge de master no branch cliente para ele andar (fast-forward):
GIT$ git merge master
Atualizo o branch do cliente para o meu remote.
$GIT git push origin cliente
Agora a parte de trazer o patch para o repositório do TFS é igualzinha, basta importar com git am, e enviar com git tf checkin (não precisa mais do git tf clone). Talvez você precise atualizar o seu repositório antes com “git tf pull”.
Como o processo é um pouco complexo fica aqui para ajudar alguém que precise, ou eu mesmo no futuro (olá Giovanni do futuro, já temos carros que se dirigem sozinhos?).
E volto a dizer: git rulz!
Giovanni Bassi
Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.