Articolo originale: https://www.freecodecamp.org/news/git-pull-force-how-to-overwrite-local-changes-with-git/

Quando impari a programmare, prima o poi avrai a che fare con i sistemi di controllo di versione. E anche se ci sono molti strumenti disponibili in questo campo, soltanto uno di essi è lo standard usato da praticamente chiunque nell'industria. È così popolare che ci sono aziende che ne usano il nome nei loro marchi. Parliamo di Git, ovviamente.

Anche se Git è uno strumento potente, il suo potere è ben nascosto. Ci sono alcuni concetti essenziali che hai bisogno di capire per diventare competente con Git. La buona notizia è che una volta che li impari, ti capiterà raramente di incappare in guai da cui non puoi uscire.

La procedura tipo

In un tipico procedimento di Git, userai un repository locale, un repository remoto e uno o più branch. I repository immagazzinano le informazioni sul progetto, tra cui la sua storia completa e i suoi branch. Un branch è in pratica una collezione di cambiamenti, dal progetto vuoto allo stato attuale.

Dopo aver clonato un repository, lavori sulla copia locale e introduci i nuovi cambiamenti. Fino a che non fai il push dei cambiamenti che hai fatto in locale al repository remoto, tutto il tuo lavoro è disponibile solo sulla tua macchina.

Quando finisci una attività, è ora di sincronizzare il tuo repository remoto, facendo il pull dei cambiamenti in remoto per mantenerti in pari con i progressi del progetto, e facendo il push dei cambiamenti in locale per condividere il tuo lavoro con gli altri.

Cambiamenti in locale

Va tutto bene finché tu e il resto del team state lavorando su file completamente separati. Qualsiasi cosa accada, non vi pesterete i piedi.

Però, a volte capita che tu e il tuo team introduciate simultaneamente cambiamenti nello stesso posto. Ed è lì che iniziano i guai.

Hai mai eseguito un git pull solo per vedere il temuto error: Your local changes to the following files would be overwritten by merge:? Prima o poi, a tutti capita questo problema.

Quello che crea più confusione qui è che non vuoi fare merge, solo pull, giusto? In realtà, pull è un po' più complicato di quello che potresti pensare.

Come funziona esattamente Git Pull?

pull non è una singola operazione. In realtà, viene prima eseguito un fetch, ottenendo i metadati che descrivono i commit, e poi il merge aggiungendo quei commit al repository locale. Queste due operazioni possono essere eseguite manualmente se vuoi:

git fetch
git merge origin/$BRANCH_ATTUALE

La parte origin/$BRANCH_ATTUALE significa che:

  • Git aggiungerà al branch i cambiamenti dal repository remoto origin (quello da cui hai clonato il repository locale)
  • che sono stati apportati al branch $BRANCH_ATTUALE
  • che non sono già presenti nel branch locale di cui hai fatto il checkout

Visto che git fa il merge solo se non ci sono cambiamenti senza commit, ogni volta che fai git pull con cambiamenti che non hai aggiunto ad un commit potresti incappare in qualche guaio. Fortunatamente ci sono modi per tirartene fuori!

image-167
Photo by Sneaky Elbow / Unsplash

Approcci diversi

Quando hai cambiamenti locali senza commit e vuoi comunque fare il pull della nuova versione dal server remoto, la tua situazione in genere cade in uno dei seguenti scenari:

  • non ti interessano i cambiamenti locali e vuoi sovrascriverli,
  • ti importano tanto e vuoi applicarli dopo i cambiamenti dal remoto,
  • vuoi scaricare i cambiamenti da remoto ma non applicarli per ora

Ognuno di questi approcci richiede una diversa soluzione.

Non ti interessano i cambiamenti locali

In questo caso vuoi semplicemente annullare i cambiamenti locali. Forse hai cambiato i file per sperimentare ma non ti serve più tale modifica. Quello che ti interessa è essere in pari con l'upstream.

Questo significa che aggiungi uno step in più tra il fetch e il merge. Questo step resetta il branch al suo stato non modificato, permettendo a git merge di funzionare.

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

Se non vuoi scrivere il nome del branch ogni volta che lo esegui, Git ha una abbreviazione che punta al branch upstream: @{u}. Un branch upstream è il branch nel repository remoto a cui fai push e dai cui fai fetch.

Ecco come appare il comando precedente con l'abbreviazione:

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

Mettiamo le virgolette attorno alla abbreviazione nell'esempio per prevenire che lo shell lo interpreti.

Ti importa tanto dei cambiamenti locali

Quando i tuoi cambiamenti locali sono importati per te, ci sono due opzioni: puoi salvarli in un commit e poi fare git pull, oppure puoi salvarli nello stash.

Salvarli nello stash significa mettere i cambiamenti da parte per un momento, per poi recuperarli più tardi. Per essere più precisi, git stash crea un commit che non è visibile nel tuo branch corrente, ma è comunque accessibile da Git.

Per recuperare i cambiamenti salvati nell'ultimo stash, puoi usare git stash pop. Dopo aver applicato i cambiamenti stashati, questo comando rimuove anche il commit stash che non è più necessario.

Il procedimento quindi appare così:

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

Come azione predefinita, i cambiamenti dallo stash entrano nell'area di staging. Se vuoi toglierli dall'area di staging, usa git restore --staged (se stai usando una versione di Git più recente di 2.25.0).

Vuoi solo scaricare i cambiamenti da remoto

L'ultimo scenario è un po' diverso dai precedenti. Diciamo che sei nel bel mezzo di un refactoring molto confusionario. Non sono delle opzioni né perdere i cambiamenti né stasharli. Mai, vuoi comunque avere i cambiamenti disponibili per eseguire git diff con le ultime novità come confronto.

Come hai probabilmente capito, ottenere i cambiamenti remoti non richiede assolutamente git pull! git fetch è sufficiente.

Una cosa da tenere a mente è che, come comportamento predefinito, git fetch prende i cambiamenti solo del branch corrente. Per ottenere i cambiamenti di tutti i branch usa git fetch --all. E se vuoi ripulire un po' i branch che non esistono più sul repository remoto, usa git fetch --all --prune per fare pulizie!

image-166
Photo by Lenin Estrada / Unsplash

Qualche automazione

Hai mai sentito di Git Config? È un file dove Git immagazzina le impostazioni configurate dall'utente. Risiede nella tua cartella home: come ~/.gitconfig o come ~/.config/git/config. Puoi modificarlo per aggiungervi alias personalizzati che saranno compresi come comandi Git.

Per esempio, per avere una abbreviazione uguale a git diff --cached (che mostra la differenza tra il branch corrente e i file nello stage), potresti aggiungere la seguente sezione:

[alias]
  dc = diff --cached

Dopo di ciò, puoi eseguire git dc ogni volta che vuoi revisionare i cambiamenti. Andando avanti così possiamo creare alcuni alias relativi ai casi precedenti.

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

In questo modo, se esegui git pull_force sovrascriverai i cambiamenti locali mentre git pull_stash li conserverà.

Git Pull Force

Menti curiose avranno già scoperto che esiste una cosa come git pull --force. Questa però è una bestia diversa da ciò che è stato presentato in questo articolo.

Può sembrare qualcosa che ci potrebbe aiutare a sovrascrivere i cambiamenti locali, invece ci permette di fare il fetch di cambiamenti da un branch remoto a un branch locale diverso. git pull --force cambia solo il comportamento del fetching. È equivalente a git fetch --force.

Come git push, git fetch permette di specificare su quali branch locale e remoto vogliamo lavorare. git fetch origin/feature-1:my-feature significa che il branch feature-1 del repository remoto sarà visibile sul branch locale my-feature. Quando tale operazione modifica la storia esistente, non è permessa da Git senza l'uso esplicito del parametro --force.

Allo stesso modo in cui git push --force permette di sovrascrivere branch remoti, git fetch --force (o git pull --force) permette di sovrascrivere branch locali. È sempre usato con i branch sorgente e destinazione menzionati come parametri. Un approccio alternativo per sovrascrivere i cambiamenti locali con git pull --force potrebbe essere git pull --force "@{u}:HEAD".

In conclusione

Il mondo di Git è vasto e questo articolo copre sono una delle sfaccettature legate alle manutenzione di un repository: incorporare cambiamenti remoti in un repository locale. Anche questa situazione quotidiana, ci ha richiesto un'analisi dettagliata del meccanismo interno di questo strumento di controllo versione.

Imparare quali sono le situazioni di utilizzo pratico aiuta a capire meglio come funziona Git. Questo, di conseguenza, ti permetterà di sentirti competente ogni volta che ti trovi nei guai. Capita a tutti.