Artigo original: Git Pull Force – How to Overwrite Local Changes With Git

Quando você aprende a programar, mais cedo ou mais tarde vai aprender sobre Sistemas de Controle de Versão (ou Version Control Systems, VCS, em inglês). Embora existam muitas ferramentas concorrendo nesse setor, uma delas é o padrão que de fato é usado por quase todos os que trabalham com isso. Ele é tão popular que há empresas que usam seu nome junto da própria marca. Estamos falando do Git, logicamente.

Embora o Git seja uma ferramenta poderosa, seu poder fica bem oculto. Há alguns conceitos essenciais que você precisa compreender para se tornar verdadeiramente proficiente com o Git. A boa notícia é que, uma vez aprendidos os conceitos, você raramente terá problemas dos quais não conseguirá fugir.

O fluxo de trabalho típico

Em um fluxo de trabalho típico do Git, você usará um repositório local, um repositório remoto e um ou mais branches. Repositórios armazenam todas as informações sobre o projeto, incluindo o histórico completo e todos os branches. Um branch é, basicamente, uma coleção de alterações que vão de um projeto vazio até o estado atual.

Após a clonagem de um repositório, você trabalha em sua cópia local e insere novas alterações. Até que você faça o push das alterações locais para o repositório remoto, todo o seu trabalho está disponível apenas em sua máquina.

Ao terminar uma tarefa, é hora de sincronizar com o repositório remoto. Você quer pegar as alterações do repositório remoto para se manter atualizado com o andamento do projeto, e quer fazer o push das alterações locais para compartilhá-las com aqueles com quem você trabalha.

Alterações locais

Tudo funciona bem quando você e o resto de sua equipe estão trabalhando em arquivos totalmente separados. Não importa o que acontecer, vocês não atrapalharão uns aos outros.

No entanto, há vezes em que você e os membros de sua equipe introduzem alterações simultaneamente e no mesmo lugar. É geralmente aí que os problemas começam.

Você já executou um git pull e acabou vendo um famigerado error: Your local changes to the following files would be overwritten by merge:? Mais cedo ou mais tarde, todos passam por esse problema.

Mais confuso ainda é o fato de que você não quer fazer o merge de nada, apenas fazer um pull, certo? De fato, fazer um pull é um pouco mais complicado do que você pode imaginar.

Como exatamente funciona o Git Pull?

O pull não é uma única operação. Ele consiste em buscar os dados do servidor remoto e, então, fazer o merge das alterações no repositório local. Essas duas operações podem ser realizadas manualmente se você quiser:

git fetch
git merge origin/$CURRENT_BRANCH

A parte origin/$CURRENT_BRANCH quer dizer:

  • que o Git fará o merge das alterações do repositório remoto chamado origin (aquele de onde você fez um clone)
  • que elas foram adicionadas ao $CURRENT_BRANCH
  • que elas não estão presentes no branch local onde você fez o checkout

Como o Git só realiza merges quando não há alterações sem que seus respectivos commits tenham sido feitos, sempre que você executar um git pull com alterações assim, você poderá ter problemas. Por sorte, há maneiras de se livras desses problemas e escapar ileso da situação!

image-167
Foto de Sneaky Elbow/Unsplash

Diferentes abordagens

Quando você tem alterações locais sem ter o commit delas realizado e ainda assim deseja fazer o pull de uma nova versão do servidor remoto, seu caso de uso normalmente se enquadra em um dos seguintes cenários:

  • você não se importa com as alterações locais e quer sobrescrevê-las,
  • você se importa muito com as alterações locais e quer aplicá-las após as alterações remotas,
  • você quer fazer o download das alterações remotas sem aplicá-las ainda

Cada uma das abordagens requer uma solução diferente.

Você não se importa com as alterações locais

Neste caso, você só quer se livrar das alterações locais sem commit. Talvez você tenha modificado um arquivo para fazer uma experiência, mas não precisa mais da alteração. Tudo o que você quer é se manter atualizado com o que está no servidor remoto.

Isso significa adicionar um passo a mais antes de fazer o fetch das alterações remotas e de fazer o merge das alterações. Este passo redefinirá o branch para seu estado não modificado, permitindo assim que o git merge funcione.

git fetch
git reset --hard HEAD
git merge origin/$CURRENT_BRANCH

Se não quiser digitar o nome do branch sempre que executar esse comando, o Git tem um atalho interessante que aponta para o branch remoto: @{u}. Um branch remoto desses é o branch no repositório remoto do qual você quer fazer o push e o fetch.

É assim que os comandos acima parecem ao usar o atalho:

git fetch
git reset --hard HEAD
git merge '@{u}'

Colocamos o atalho entre aspas simples no exemplo para evitar que o shell o interprete.

Você se importa muito com as alterações locais

Quando as alterações das quais você ainda não fez o commit são importantes para você, existem duas opções. Você pode fazer o commit delas e realizar o git pull, ou deixá-las em stash.

Colocar em stash significa reservar as alterações para outro momento e trazê-las de volta mais tarde. Para ser mais exato, git stash cria um commit que não está visível no seu branch atual, mas que ainda é acessível por meio do Git.

Para trazer de volta as alterações salvas no último stash, você usa o comando git stash pop. Depois de aplicar com sucesso as alterações em stash, este comando também remove o commit em stash, já que ele não é mais necessário.

O fluxo de trabalho, então, terá essa aparência:

git fetch
git stash
git merge '@{u}'
git stash pop

Por padrão, as alterações do stash vão para a fase de stage. Se quiser removê-las do stage, use o comando git restore --staged (se estiver usando a versão do Git 2.25.0 ou alguma mais recente).

Você quer fazer o download das alterações remotas

O último cenário é um pouco diferente dos anteriores. Vamos supor que você está no meio de uma refatoração de código bastante confusa. Perder as alterações ou colocá-las em stash são opções ruins. Mesmo assim, você quer que as alterações remotas estejam disponíveis para contrastá-las executando um git diff.

Você já deve ter se dado conta disso, mas fazer o download das alterações remotas não necessita de fato de um git pull! git fetch já dá conta do recado.

Uma coisa que precisa ser observada é que, por padrão, git fetch somente trará as alterações do branch atual. Para obter todas as alterações de todos os branches, use git fetch --all. Se quiser limpar alguns dos branches que não existem mais no repositório remoto, git fetch --all --prune fará a limpeza para você!

image-166
Foto de Lenin Estrada/Unsplash

Um pouco de automação

Você já ouviu falar no git config? É um arquivo onde o Git armazena todas as configurações feitas pelos usuários. Ele fica no seu diretório principal, seja ele ~/.gitconfig ou ~/.config/git/config. Você pode editá-lo para adicionar alguns alias personalizados que serão compreendidos como comandos do Git.

Por exemplo, para ter um atalho equivalente a git diff --cached (que mostra a diferença entre o branch atual e os arquivos em stage), você adicionaria o seguinte:

[alias]
  dc = diff --cached

Depois disso, você pode executar git dc sempre que quiser revisar as alterações. Partindo dessa ideia, podemos configurar alguns alias relacionados aos casos de uso anteriores.

[alias]
  pull_force = !"git fetch --all; git reset --hard HEAD; git merge @{u}"
  pf = pull_force
  pull_stash = !"git fetch --all; git stash; git merge @{u}; git stash pop"

Desse modo, ao executar git pull_force, você sobrescreverá as alterações locais, enquanto git pull_stash as preservará.

O outro git pull force

As mentes curiosas já devem ter descoberto que existe, de fato, um git pull --force. No entanto, ele é bem diferente daquele que foi apresentado neste artigo.

Ele pode parecer algo que nos ajudaria a sobrescrever as alterações locais. Em vez disso, ele permite que façamos o fetch das alterações de um branch remoto para um branch local diferente. git pull --force somente modifica o comportamento da parte do fetch. Ele é, portanto, equivalente a git fetch --force.

Como ocorre com o git push, git fetch permite que especifiquemos em quais branches local e remoto queremos trabalhar. git fetch origin/feature-1:my-feature representa que as alterações no branch feature-1 do repositório remoto acabarão visíveis no branch local my-feature. Quando uma operação dessas modifica o histórico existente, isso não é permitido pelo Git sem um parâmetro --force explícito.

Assim como git push --force permite sobrescrever branches remotos, git fetch --force (ou git pull --force) permite sobrescrever branches locais. Ele sempre é usado com os branches de origem e de destino mencionados como parâmetros. Uma abordagem alternativa a sobrescrever as alterações locais usando git --pull force seria git pull --force "@{u}:HEAD".

Conclusão

O mundo do Git é vasto. Este artigo tratou apenas das facetas da manutenção dos repositórios: incorporar alterações remotas em um repositório local. Mesmo que esse cenário do dia a dia exija que examinemos com mais profundidade os mecanismos internos dessa ferramenta de controle de versão.

Aprender casos de uso reais ajuda você a entender melhor como o Git funciona por debaixo dos panos. Por sua vez, isso dará a você uma sensação de domínio do assunto sempre que você tiver problemas. Todos nós passamos por isso de tempos em tempos.