image

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!