Git Pull Force - Como sobrescrever mudanças locais com Git

Quando você aprender a codificar, mais cedo ou mais tarde também aprenderá sobre Sistemas de Controle de Versão. E embora existam muitas ferramentas concorrentes neste espaço, uma delas é o padrão de fato usado por quase todos na indústria. É tão popular que existem empresas que usam seu nome em sua marca. Estamos falando sobre Git, é claro.

Embora o Git seja uma ferramenta poderosa, seu poder está bem escondido. Existem alguns conceitos essenciais que você precisa entender para se tornar realmente proficiente no Git. A boa notícia é que, depois de aprendê-los, dificilmente terá problemas dos quais não possa escapar.

O Fluxo de Trabalho Típico

Em um fluxo de trabalho Git típico, você usará um repositório local, um repositório remoto e um ou mais branches. Os repositórios armazenam todas as informações sobre o projeto, incluindo todo o seu histórico e todas as filiais. Uma ramificação é basicamente uma coleção de alterações que levam de um projeto vazio ao estado atual.

Depois de clonar um repositório, você trabalha em sua cópia local e introduz novas mudanças. Até que você envie as alterações locais para o repositório remoto, todo o seu trabalho estará disponível apenas em sua máquina.

Quando você termina uma tarefa, é hora de sincronizar com o repositório remoto. Você deseja enviar as alterações remotas para acompanhar o andamento do projeto e deseja enviar as alterações locais para compartilhar seu trabalho com outras pessoas.

Mudanças Locais

Tudo está bem quando você e o resto de sua equipe estão trabalhando em arquivos totalmente separados. Aconteça o que acontecer, vocês não pisarão nos pés um do outro.

No entanto, há momentos em que você e seus colegas de equipe introduzem alterações simultaneamente no mesmo lugar. E geralmente é aí que os problemas começam.

Você já executou git pullapenas para ver o temido error: Your local changes to the following files would be overwritten by merge:? Mais cedo ou mais tarde, todos se depararão com esse problema.

O que é mais confuso aqui é que você não quer mesclar nada, apenas puxar, certo? Na verdade, puxar é um pouco mais complicado do que você pode imaginar.

Como exatamente o Git Pull funciona?

Puxar não é uma operação única. Consiste em buscar dados do servidor remoto e, em seguida, mesclar as alterações com o repositório local. Essas duas operações podem ser realizadas manualmente se você quiser:

git fetch git merge origin/$CURRENT_BRANCH

A origin/$CURRENT_BRANCHparte significa que:

  • Git irá mesclar as mudanças do repositório remoto chamado origin(aquele do qual você clonou)
  • que foram adicionados ao $CURRENT_BRANCH
  • que ainda não estão presentes em sua filial local de check-out

Como o Git só executa mesclagens quando não há mudanças não confirmadas, cada vez que você executa mudanças não confirmadas, você git pullpode ter problemas. Felizmente, existem maneiras de sair inteiros dos problemas!

Nós somos família

Abordagens diferentes

Quando você não confirma as alterações locais e ainda deseja obter uma nova versão do servidor remoto, seu caso de uso normalmente se enquadra em um dos seguintes cenários. Ou:

  • você não se preocupa com as mudanças locais e deseja substituí-las,
  • você se preocupa muito com as mudanças e gostaria de aplicá-las após as mudanças remotas,
  • você deseja baixar as modificações remotas, mas não aplicá-las ainda

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

Você não se importa com as mudanças locais

Nesse caso, você deseja apenas descartar todas as alterações locais não confirmadas. Talvez você tenha modificado um arquivo para experimentar, mas não precisa mais da modificação. Tudo o que importa é estar atualizado com o upstream.

Isso significa que você adiciona mais uma etapa entre buscar as alterações remotas e mesclá-las. Esta etapa irá redefinir a ramificação para seu estado não modificado, permitindo assim que git mergefuncione.

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

Se você não quiser digitar o nome da ramificação cada vez que você executar este comando, Git tem um bom atalho que aponta para o ramo a montante: @{u}. Um branch upstream é o branch no repositório remoto para o qual você envia e busca.

This is how the above commands would look like with the shortcut:

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

We are quoting the shortcut in the example to prevent the shell from interpreting it.

You Very Much Care About the Local Changes

When your uncommitted changes are significant to you, there are two options. You can commit them and then perform git pull, or you can stash them.

Stashing means putting the changes away for a moment to bring them back later. To be more precise, git stash creates a commit that is not visible on your current branch, but is still accessible by Git.

To bring back the changes saved in the last stash, you use the git stash pop command. After successfully applying the stashed changes, this command also removes the stash commit as it is no longer needed.

The workflow could then look like this:

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

By default, the changes from the stash will become staged. If you want to unstage them, use the command git restore --staged (if using Git newer than 2.25.0).

You Just Want to Download the Remote Changes

The last scenario is a little different from the previous ones. Let's say that you are in the middle of a very messy refactoring. Neither losing the changes nor stashing them is an option. Yet, you still want to have the remote changes available to run git diff against them.

As you have probably figured out, downloading the remote changes does not require git pull at all! git fetch is just enough.

One thing to note is that by default, git fetch will only bring you changes from the current branch. To get all the changes from all the branches, use git fetch --all. And if you'd like to clean up some of the branches that no longer exist in the remote repository, git fetch --all --prune will do the cleaning up!

Some Automation

Have you heard of Git Config? It's a file where Git stores all of the user-configured settings. It resides in your home directory: either as ~/.gitconfig or ~/.config/git/config. You can edit it to add some custom aliases that will be understood as Git commands.

For example, to have a shortcut equivalent to git diff --cached (that shows the difference between the current branch and the staged files), you'd add the following section:

[alias] dc = diff --cached

After that, you can run git dc whenever you wish to review the changes. Going this way, we can set up a few aliases related to the previous use cases.

[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"

This way, running git pull_force will overwrite the local changes, while git pull_stash will preserve them.

The Other Git Pull Force

Curious minds may have already discovered that there is such a thing as git pull --force. However, this is a very different beast to what's presented in this article.

It may sound like something that would help us overwrite local changes. Instead, it lets us fetch the changes from one remote branch to a different local branch. git pull --force only modifies the behavior of the fetching part. It is therefore equivalent to git fetch --force.

Like git push, git fetch allows us to specify which local and remote branch do we want to operate on. git fetch origin/feature-1:my-feature will mean that the changes in the feature-1 branch from the remote repository will end up visible on the local branch my-feature. When such an operation modifies the existing history, it is not permitted by Git without an explicit --force parameter.

Just like git push --force allows overwriting remote branches, git fetch --force (or git pull --force) allows overwriting local branches. It is always used with source and destination branches mentioned as parameters. An alternative approach to overwriting local changes using git --pull force could be git pull --force "@{u}:HEAD".

Conclusion

O mundo do Git é vasto. Este artigo cobriu apenas uma das facetas da manutenção do repositório: incorporar mudanças remotas em um repositório local. Mesmo esse cenário cotidiano exigia que examinássemos um pouco mais profundamente os mecanismos internos dessa ferramenta de controle de versão.

Aprender casos de uso reais ajuda a entender melhor como o Git funciona nos bastidores. Isso, por sua vez, fará com que você se sinta fortalecido sempre que se meter em problemas. Todos nós fazemos isso de vez em quando.