Desenvolvimento

5 jun, 2012

Dica Git da semana: Trackeando Branches

Publicidade

A dica Git desta semana é sobre como configurar o que acontece quando você faz o pull.

Na semana passada, escrevi sobre o comportamento de fazer o pull de branches trackeados; esta semana, vale a pena dar um mergulho para descobrir o que é isso.

Quando você utiliza Git inicialmente, você aprende que a atualização de itens do master envolve um git pull (ou git fetch). Ambos se aproximam do repositório remoto e obtêm conteúdo nos quais você está interessado, com a variante git pull fazendo também uma fusão ou um rebase, conforme o caso.

Mas como o Git vai saber o que extrair quando você invocar git pull? De onde deve extraí-lo? O que faz um branch que você tem verificado localmente se diferenciar do que você retirou a partir de um repositório remoto?

Remotes e Refspecs

Em primeiro lugar, um repositório git (local) pode possuir muitos remotes. Cada remote é um nome de um repositório em uma extremidade remota, o que corresponde a uma URL e a um refspec. (Na verdade, remotes podem ter uma segunda URL; uma é usada para fazer um fetch, enquanto a outra é usada para fazer um push – esta é usada para permitir busca anônima, mas impulsiona autenticação). Você precisa especificar, ao fazer um fetch e um pull, sobre qual repositório você está falando. Para repositórios remotos, este será o padrão para origin, se não especificado.

Você pode especificar o que o refspec é ao interagir com um repositório remoto. Este é o conjunto de branches que serão atualizados se você interagir com esse repositório. Esta é normalmente a forma refs/heads/master:refs/remotes/origin/master, onde refs/heads são os ponteiros para seus branches locais, e refs/remotes são os branches remotos.

Um prefixo + opcional no fetch refspecs indica se é ou não para fazer um fetch de commits non fast-forward automaticamente. E enquanto você não pode ter curingas parciais (como refs/for/qa*), você pode ter sub caminhos (como like refs/for/qa/*). Também é possível utilizar a referência HEAD para se referir ao commit de que o branch atual está ligado como uma fonte para o refspec, que pode ser útil para pushes.

Branches trackeados

No entanto, cada branch tem também o conceito do que é tracking. Bem como o branch que será afetado por um fetch/pull/push, o rastreamento diz qual branch está indo contra.

Normalmente, os branches com verificados de um repositório remoto são configurados automaticamente como branches trackeados. Se você verificar EGit, você terá um branch master que rastreia refs/remotes/egit/master (ou origin, se você não especificar um identificador de repositório padrão). Todas as alterações que puxam para o seu master virão (por padrão) do master do EGit.

No entanto, e se você quiser desmembrar outro branch para fins experimentais, e manter isso atualizado? Se você fizer git checkout -b experimental, que diverge de seu master local nesse ponto no tempo. Você nem precisa fazer o pull das mudanças por meio do master e, em seguida, rebase, ou se lembrar de onde era seu ponto de merge.

Em vez disso, você pode configurar seu branch experimental para trackear outro. Isso significa que você pode fazer fetch e pull, como se estivesse extraindo de um repositório remoto, e consumir alterações dos movimentos branch em curso. Isso é útil se você tiver um branch UAT long-running que precisa ser atualizado periodicamente a partir de um alvo em movimento; configurá-lo como um branck trackeado significa que a única coisa que você precisa fazer para ficar atualizado é git pull.

Então, como você montou um branch trackeado? Bem, quando você verificar um branch de um master remoto, ele será configurado automaticamente. Na verdade, tudo o que um tracked branch é é aquilo que está explicitamente mencionado no arquivo .git/config, uma vez que lista o que é seu remoto e de onde é feito o merge:

$ git clone upstream clone
Cloning into clone...
done.
$ cd clone
$ tail .git/config
[branch "master"]
remote = origin
merge = refs/heads/master

A forma de ler isso é que o master é um branch local, que rastreia refs/heads/master sobre a origin remota. Quaisquer pulls que acontecerem no master irão resultar em um merge (ou rebase) de refs/heads/master.

E se quiséssemos montar o nosso branch experimental? Se nós apenas fizermos git checkout -b  experimental, ele não vai ser rastreado:

$ git checkout -b experimental
Switched to a new branch 'experimental'
$ grep branch .git/config
[branch "master"]

Podemos assinalá-lo como trackeado usando a opção –track de git checkout (ou seu alias mais curto -t):

$ git checkout master
Switched to branch 'master'
$ git branch -d experimental
Deleted branch experimental (was 4a3fa88).
$ git checkout --track -b experimental
$ tail -3 .git/config
[branch "experimental"]
remote = .
merge = refs/heads/master
$ git pull
From .
* branch master -> FETCH_HEAD
Already up-to-date.

Espera aí, o que o remoto = . está fazendo aqui? Bem, isso é uma mãozinha especial dando sentido a esse repositório, como diretório dá sentido no acesso do sistema de arquivos. O que temos aqui é o master trackeado experimental, e não origin/master; em outras palavras, é um branch local monitorando outro. Há momentos em que isso é útil, mas e se você quiser rastrear o remote diretamente. em vez de fazer um pull de uma cópia local?

$ git checkout master
Switched to branch 'master'
$ git branch -d experimental
Deleted branch experimental (was 4a3fa88).
$ git checkout -b experimental origin/master
Branch experimental set up to track remote branch master from origin.
Switched to a new branch 'experimental'
$ tail -3 .git/config
[branch "experimental"]
remote = origin
merge = refs/heads/master

Agora temos um branch experimental que está rastreando origin/master. Quando temos uma atualização no repositório upstream, e fazemos um pull, vemos ele atualizando tanto master, bem como experimental:

$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
From upstream
4a3fa88..55eb534 master -> origin/master
Updating 4a3fa88..55eb534
Fast-forward
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 third

A atualização mostra origin/master sendo atualizado para o novo valor. O passo seguinte é a atualização e fast-forward do branch local experimental. Mas e quanto ao branch master local?

$ git log --oneline experimental
55eb534 Third
4a3fa88 Second
ff8536c Start
$ git log --oneline master
4a3fa88 Second
ff8536c Start

Assim, embora ambos experimental e master estejam rastreando o mesmo branch upstream, eles podem ser atualizados e processados de forma independente. Isso é útil quando você quer avançar o estado de um branch (talvez para fins de experimentação), mas não quer mudar o estado local de um branch.

É possível adicionar informações de rastreamento uptream de um branch local existente após o fato, nas versões recentes do git. Se tivéssemos verificado o branch experimental como na primeira etapa, e não quiséssemos apagar/recriá-lo (talvez porque tenhamos feito algumas alterações locais), então você pode adicioná-lo mais tarde:

$ git checkout master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
$ git checkout -b experimental2
$ git checkout -b experimental2
Switched to a new branch 'experimental2'
$ git branch --set-upstream experimental2 origin/master
Branch experimental2 set up to track remote branch master from origin.
$ tail -3 .git/config
[branch "experimental2"]
remote = origin
merge = refs/heads/master

Assim, mesmo se você já tiver branches existentes, é possível conectá-los para serem branches trackeados após o fato. Você também pode usar essa opção se deseja alterar qual branch está rastreando (por exemplo, trocar um branch local por um remoto ou vice-versa) ao executar o comando novamente.

Também vale a pena mencionar que há uma opção –no-track de git checkout, que pode ser usada para evitar o rastreamento de branches após a verificação se isso for desejado. Isso às vezes é útil se você está usando um branch com uma feature ou um branch de bugfix e você não quer/precisa fazer um pull dele no futuro.

Por fim, tudo isso é configurado com a opção de configuração branch.autosetupmerge. Se essa opção for false, então branches nunca seItálicorão rastreados por padrão. Se a opção for true, então branches serão rastreados se forem remotos, e não serão rastreados se eles forem locais. Se a opção é always, então branches estão sempre configurados como branches trackeados, independentemente de serem locais ou remotos. Estes especificam efetivamente os padrões, mas eles podem ser substituídos em uma base branch por branch, usando as flags de linha de comando –no-track e –track dos comandos git checkout ou git branch.

?

Texto original disponível em http://alblue.bandlem.com/2011/07/git-tip-of-week-tracking-branches.html