Tutorial de git/Treball lineal

Ja s'han presentat les comandes més bàsiques de git. Amb aquestes eines es pot Treballar normalment amb l'avantatge que es guarda un historial de canvis. Però el git permet fer moltes més coses.
En aquest apartat es presentaran les comandes per treure més suc del git. Es suposarà un tipus de treball «lineal»; és a dir, que cada canvi que es fa en el repositori és per afegir una nova funcionalitat al projecte en el que es treballa, i que no es comença una altra funcionalitat abans de no haver acabat l'anterior.

Mirar l'estat del repositori[modifica]

Fins ara ja som capaços d'anar guardant els nostres canvis al git. Però moltes vegades estem treballant i no recordem quins fitxers hem canviat o si n'hem creat de nous, etc. La comanda git status ens permet veure l'estat.

Exactament aquesta comanda ens dona informació de quines diferències hi ha entre el que estem editant ara i l'últim commit que hem fet.

Quan acabem de fer un commit i fem un git status la sortida és:

  # On branch master
  nothing to commit (working directory clean)

Això vol dir que ens trobem a la branca master (per ara no te importància, només saber que és la branca per defecte) i que no hi ha cap canvi.

Si editem el fitxer prova.txt i fem un git status la sortida serà

  # On branch master
  # Changed but not updated:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  #       modified:   prova.txt
  #
  no changes added to commit (use "git add" and/or "git commit -a")

En aquí lo important és on diu: # Changed but not updated. Això vol dir que el fitxer ha estat modificat, però que encara no l'hem afegit.

Si ara afegim el fitxer git add prova.txt i fem un git status:

  # On branch master
  # Changes to be committed:
  #   (use "git reset HEAD <file>..." to unstage)
  #
  #       modified:   prova.txt
  #

Ara ens diu que hi ha canvis preparats per fer el commit # Changes to be committed:, per tant si fessim un git commit ja quedaria registrat al nostre repositori.

Què passa si creem un fitxer nou? Imaginem que comencem a editar el fitxer fitxer2.txt si ara fem un git status ens diu:

  # On branch master
  # Untracked files:
  #   (use "git add <file>..." to include in what will be committed)
  #
  #       fitxer2.txt
  nothing added to commit but untracked files present (use "git add" to track)

Aquí ens diu que hi ha fitxers als quals el git no "segueix la pista". Si volem que el git li segueixi la pista a aquest fitxer haurem de fer un git add fitxer2.txt. Si en canvi no volem veure quins canvis hi fem doncs els deixem així tal qual.

Mirar la història del repositori[modifica]

Tal com hem dit, la gran aportació que ens ofereix el git fins ara és el tenir una història del nostre repositori.

Per tal de conèixer la història del que hem anat fent fins ara farem servir la comanda git log. Aquesta comanda ens ensenya el log de la història del repositori.
Es pot obtenir més informació afegint paràmetres. La comanda git log --stat --summary es un bon terme mig.

 --stat ens dona una visió de les diferències dels fitxers entre commits
 --summary resum la informació de quins fitxers s'han afegit, s'han esborrat o s'han reanomenat

Si ens fixem en la sortida del git log, podem veure que cada commit va seguit d'un número molt llarg; això és el seu identificador (ID). El git crea la ID de cada commit fent una suma dels continguts. Concretament fa servir l'algoritme SHA-1 (anglès). El ID veurem més endavant que el podrem fer servir com una manera de referir-nos a un commit concret. Per referir-nos-hi no ens cal posar tots els 40 caràcters. Només amb 6 o 7 acostuma a ser suficient per referir-se a un commit concret del nostre repositori.

La comanda git log te més paràmetres com el --pretty-format que et permet dir quin format vols fer servir, a la documentació trobaràs molta informació sobre com funciona.
També pots afegir un control de temps del que vols veure. Per exemple la comanda git log @{yesterday} et fa un log de tots els commits fins ahir. Pots definir rangs amb l'operador .., per exemple git log @{"1\ week\ ago"}..@{yesterday} et fa un log de tot el que ha passat des de la setmana passada fins ahir; o la comanda git log @{"4\ days\ ago"}.. et fa un log de tot el que ha passat a partir de fa 4 dies. En els rangs es pot fer servir qualsevol tipus de referència.
També pots dir directament quants commits enrere vols anar. Per exemple @{2} es refereix a dos commits enrere i @{5} es refereix a 5 commits enrere. Aquestes dues maneres de referenciar són locals del repositori on treballem i per tant només es recomana fer-los servir a l'hora de mirar la història o qualsevol mena de informació del repositori, però no per fer operacions on el ID o les referències que explicarem a continuació sí que són vàlides. Més endavant ho explicarem més detalladament.

Una altra manera és mitjançant una referència al HEAD. El HEAD és l'últim commit de la branca on ens trobem. Si a darrera del HEAD afegim un accent circumflex (^) ens referim al pare del HEAD; podem posar més d'un ^, per exemple HEAD^^ fa referència al "avi" del HEAD. En general el pare d'un commit és el commit anterior. Però això no sempre és així, i més endavant veurem la utilitat més gran d'aquesta manera de referenciar.
Una altra manera és amb una titlla i un número ~N (HEAD~N) fa referència a N commits abans del head; HEAD~2 fa referència a dos commits abans del HEAD i HEAD~3 fa referència a 3 commits abans del HEAD. Això també ho podríem referenciar com HEAD^^^, tot i que jo recomano fer servir la titlla.

Ignorar fitxers[modifica]

Tal com hem vist en fer servir el git status, el git ens dona informació sobre els fitxers que estan en el directori però que ell no els fa un seguiment. Moltes vegades aquests fitxers no t'interessa que estiguin en el repositori, per tant no vols que cada vegada que facis un git status t'apareguin, i simplement vols que el git els ignori.

Hi ha dos maneres de fer que el git ignori aquests fitxers. Una de local al repositori en el que treballem, i una altra global, que sempre que el repositori sigui clonat, o copiat continuarà ignorant els fitxers.

El mètode local serveix per aquells fitxers que genera el software que utilitzem nosaltres localment i que no te perquè fer servir algú altre. Per exemple el vim, cada cop que editem un fitxer genera un fitxer (amb el mateix nom que el que estem editant) que acaba amb l'extensió .swp. Aquest fitxer està lligat amb el nostre entorn de treball i no en el projecte en si. Per tant aquest tipus de fitxers els hem d'ignorar localment.

Per tal d'ignorar un fitxer localment s'ha d'afegir en el fitxer .git/info/exclude. En aquest fitxer pots introduir noms de fitxers o expressions regulars. Tots els fitxers que coincideixin amb els continguts de .git/info/exclude seran ignorats pel git.

D'altres vegades vols que el repositori sempre ignori els fitxers. Per exemple quan treballes en un projecte LaTeX, es generen els fitxers .out, .aux, etc. Aquests fitxers són generats pel LaTeX i no donen cap valor al repositori, però tothom que tingui una còpia del repositori els tindrà. Per ignorar-los has de crear un fitxer .gitignore, on posaràs el nom del fitxer (o expressió regular) que vols ignorar, i afegiràs el fitxer .gitignore en el repositori. Així tothom que cloni el repositori també tindrà el .gitignore

Afegir canvis al repositori (índex)[modifica]

En aquest apartat explicarem com funciona això d'afegir canvis al repositori, i perquè després els has de confirmar. Segur que fins ara t'ho has estat preguntant.

Primer de tot farem un petit experiment per tal de poder entendre què està passant. Imaginem que estem treballant en un fitxer. Fem uns quants canvis, i aleshores fem un git add continuem treballant amb el fitxer i fem un git commit. Si ara fem un git status, ens adonem que aquest fitxer encara te canvis. Si fem un git diff nom_fitxer veurem les diferències. Si ens fixem tot el que hem editat després de fer el git commit no s'ha afegit. Això és deliberat.

Després de fer aquest petit experiment és força evident que el git diferencia 3 àrees:

  • L'àrea de treball
  • L'índex
  • El repositori

Quan treballem en el nostre directori ens trobem a l'àrea de treball. En aquí és on fem la nostra edició, on hi ha els fitxers ignorats i és el que veu el sistema de fitxers del nostre sistema operatiu.

Un cop fem un git add el que fem és passar els canvis a l'índex. L'índex és l'àrea on es guarden els canvis que aniran al repositori quan fem un git commit

Finalment el repositori és on hi ha tota la història dels nostres fitxers. És el que ens permet anar endavant i endarrere en la història.

Si ens fixem quan fem un git commit -a el que fem és que passem tots els canvis que hi ha en l'àrea de treball directament al repositori sense passar per l'índex.

Per què existeix l'índex?

Imaginem que estem treballant amb un fitxer i tenim pensat de fer un commit en acabar, però en acabar no recordem de fer el commit i continuem treballant amb un altre fitxer. Quan acabem els dos canvis ens n'adonem que no hem fet el commit dels canvis del primer fitxer. Llavors el que podem fer és: git add fitxer1; git commit -m "Canvis en el fitxer1" per fer el primer commit que ens hem oblidat; i després ja podem fer el segon commit simplement amb un git commit -am "Canvis en el fitxer2".

Això està molt bé! Però i si els dos canvis els hem fet en el mateix fitxer i volem fer dos commits diferents perquè així a la nostra història podem diferenciar fàcilment quin canvi correspon a cada cosa?

La manera de poder fer això és amb un add interactiu: git add -i. No és una eina molt intuïtiva, però amb l'opció patch podem afegir trossos a l'índex i deixar trossos sense afegir. Així a continuació en fer el commit passem només els canvis que volem.

Una altra situació en la que pots necessitar fer-ho és si abans d'anar a dormir comences a afegir una nova funcionalitat, vas a dormir, i quan et lleves no recordes que ho vas deixar a mitges i comences per una altra funcionalitat totalment diferent. Quan vas a fer el commit del matí te n'adones que tens la funcionalitat del dia abans a mig fer. Doncs en aquí també tenim el git add -i al rescat!

Això també es pot fer amb la eina git gui que més endavant explicarem.

Modificar l'últim commit[modifica]

Moltes vegades just després de fer un commit ens adonem que ens hem oblidat alguna cosa. Potser no hem afegit algun fitxer a l'índex, potser hi ha alguna falta d'ortografia (o de sintaxi del llenguatge de programació) que és fàcil de corregir, i volem ser capaços de modificar el últim commit sense haver de fer res més.

El git ens proporciona una manera per modificar l'últim commit. El que hem de fer és fer els canvis, afegir els fitxers a l'índex (o fer servir el paràmetre -a en la crida a commit) i aleshores fer un commit amb el paràmetre --amend. Això farà que corregim el últim commit sense fer un commit nou.

Tornar a un estat anterior[modifica]

Moltes vegades ens adonem que l'últim commit no només és incomplet com en el apartat anterior sinó que és totalment inservible. En aquest apartat explicaré com podem tornar a un estat anterior i començar des d'aquell punt.

La comanda de git per aconseguir-ho és git reset. Hi ha tres paràmetres molt importants que varien el comportament de la comanda totalment. A continuació els expliquem:

  • --hard És el més simple. Et deixa l'àrea de treball i l'índex exactament com el teníem quan vam fer el commit al que fem referència, i borra tot el que s'havia fet a partir d'aquest commit del repositori
  • --mixed És el que surt per defecte. No et toca l'àrea de treball igual com la tens ara, t'esborra tot el que hi ha a l'índex i deixa la història com estava en aquell punt
  • --soft Només esborra la història del repositori fins aquell commit, però deixa l'àrea de treball i l'índex tal com el teníem

En resum, --hard t'esborra la història, l'índex i l'àrea de treball; --mixed la història i l'índex; i --soft només la historia.

La sintaxi bàsica és git reset --hard HEAD^ En aquest cas esborrem el últim commit i ho deixem tot tal com estava. En lloc de HEAD^ podem fer servir HEAD~3 si volem esborrar els tres últims commits, o podem posar la ID del commit on ens volem quedar.

Aquesta comanda també va molt bé quan el que tenim a l'àrea de treball és totalment inservible. Si fem un git reset HEAD ens torna a deixar l'àrea de treball tal com la teníem a l'últim commit, però sense tocar lo que ja està a l'índex. Si fem un git reset --hard HEAD ens deixa l'àrea de treball tal i com la teníem a l'últim commit i l'índex buit.

Desfer els canvis en un fitxer[modifica]

A vegades estem treballant amb una nova funcionalitat que implica tocar 3 o 4 fitxers. I quan ja la tenim gairebé tota feta en un fitxer la comencem a liar i ho deixem en un estat molt dolent, però la resta de fitxers són correctes. Per tant la solució de l'apartat anterior no ens va bé.

Per aquests casos el que podem fer servir és la comanda git checkout. Si al darrera de la comanda escrivim -- i el nom del fitxer on l'hem liat, ens tornarà a escriure el fitxer tal com el teníem en l'últim commit. Per exemple git checkout -- nom_fitxer ens torna a deixar el fitxer nom_fitxer tal com estava a l'últim commit.

Marcar punts importants amb etiquetes[modifica]

Hi ha punts importants en el nostre desenvolupament que volem conèixer. Per exemple quan ja hem acabat tots els objectius que ens han demanat, o quan li passem una versió al nostre cap, o simplement perquè li hem agafat carinyo.

El git ens permet crear fàcilment etiquetes en qualsevol punt del nostre desenvolupament. Els noms de les etiquetes poden contenir qualsevol caràcter excepte espais, titlla (~), dos punts(:), interrogants (?) o asterisc (*). Hi ha dos caràcters especials que són la barra (/) i el punt (.). Aquests es permeten però sempre que no siguin ni al començament ni al final. I no es poden posar dos punts seguits (..). Totes aquestes restriccions també fan referència a l'hora de crear noms de branques.

La manera més fàcil de marcar un punt de la història amb una etiqueta és amb la comanda git tag. Per exemple si volem marcar el commit que acabem de fer amb la etiqueta 0.1, tot el que hem de fer és git tag 0.1.

Això està molt bé, però potser després volem recordar perquè l'hem marcat com a 0.1. Potser és perquè és la primera vegada que li passem al client perquè li faci una revisió. Podríem haver triat una etiqueta més fàcil com per exemple revisio_01. Hi ha gent que li agrada fer servir etiquetes del tipus revisio/01, revisio/client/01 com si imitessin directoris. Tot i així hi ha vegades que necessitem donar més informació que només amb l'etiqueta no som capaços. Per això es poden fer etiquetes anotades.

Una etiqueta anotada no és res més que una etiqueta on se li pot afegir un missatge. La sintaxi de la comanda és git tag -a 0.1 -m "Primera versió per a revisar pel client"
El paràmetre -a vol dir que la etiqueta serà anotada.
El paràmetre -m (com en els commits) vol dir quin serà el missatge.
Si no posem el paràmetre -m se'ns obrirà un editor per tal de poder introduir el missatge.

Si volem afegir confiança a les etiquetes les podem signar (GPG). El paràmetre per fer-ho és el -s. Per exemple l'etiqueta anterior la podríem haver signat amb la comanda: git tag -s 0.1 -m"Primera versió per a revisar pel client"

A vegades el que ens passa és que hem de posar una etiqueta a un commit anterior. Per tal de indicar-li al git quin commit volem etiquetar podem fer servir qualsevol de les maneres que hem parlat en l'apartat #Mirar la història del repositori. La manera més comuna és afegir la ID del commit; recordem que no cal posar-la sencera.

Si mai volem esborrar una etiqueta tot el que hem de fer és: git tag -d 0.1.

A l'hora de fer etiquetes es recomanen anotades o signades. Així tindrem més informació al repositori.

Crear un fitxer comprimit d'un punt de la nostra història[modifica]

Motes vegades necessitem enviar la feina al nostre cap, o al client, o a la xicota... Per això hem de fer un fitxer comprimit del contingut del repositori en un moment determinat. Evidentment hi ha vegades que no cal passar el repositori i només enviant el pdf obtingut d'un LaTeX, o el binari resultant del nostre codi hi ha prou i no necessitem aquesta funcionalitat.

El git ens ofereix una eina per poder fer un arxiu comprimit de tots els fitxers del repositori (els fitxers ignorats no hi seran). La comanda és: git archive HEAD > archive.tar. Aquesta comanda agafa tot el repositori i el converteix en el fitxer archive.tar.

Si volem podem fer que la sortida sigui un fitxer zip mitjançant el paràmetre --format=zip. Amb el paràmetre --prefix= podem dir que en el fitxer comprimit hi posi un directori arrel. I al final podem indicar que només faci un comprimit d'una carpeta determinada. Per exemple:
git archive --prefix=projecte/ 0.1 | gzip > projecte.tar.gz
Ens crearia el fitxer projecte.tar.gz que tindria tot el contingut del repositori en el punt de la etiqueta 0.1 i hauria posat tot el repositori a dintre de la carpeta projecte/