Laborator 00 (partea a II-a)

Sistemul de control al versiunilor GIT

Obiective

  • descrierea tipurilor de sisteme de control a codului sursă și a oportunității folosirii lor
  • familiarizarea cu principalele comenzi ale sistemului Git de versionare a codului sursă în vederea furnizării de contribuții în cadrul unui proiect la care lucrează mai mulți utilizatori

Cuvinte Cheie

VCS, Git, commit, working/staging area, Git directory, repository, remote, snapshot, tree, blob, checksum, SHA-1, checkout, merge, rebase, fetch, push, pull, master, HEAD, origin, tag, annotated, lightweight, signed, log, glob, branch, tracking

Materiale Ajutătoare

Sisteme de control a versiunilor

Ce este un sistem de control al versiunilor?

Un sistem de control al versiunilor (eng. VCS - Version Control System) este un mecanism prin intermediul căruia sunt gestionate fișiere / proiecte în dinamică (pe măsură ce sunt modificate), în scopul de a se putea realiza revenirea la o anumită stare în caz de eroare (restaurarea unei versiuni stabile) și pentru a permite colaborarea între mai multe echipe care lucrează concomitent la diferite funcționalități ale unui același proiect.

Deși în mod curent astfel de produse sunt folosite pentru dezvoltarea de aplicații (urmărindu-se o gestionare eficientă a codului sursă și a utilizatorilor care implementează anumite funcționalități sau corectează defecte), ele pot fi folosite și pentru alte tipuri de proiecte, ce implică lucrul cu fișiere binare, pentru care diferențele între diferite versiuni se realizează mai dificil.

Clasificarea sistemelor de control al versiunilor

În prezent, sunt folosite trei tipuri de sisteme de control a versiunilor, fiecare dintre acestea fiind adecvate unei anumite situații:

  1. sisteme locale de control a versiunilor, în cazul în care se dorește monitorizarea variantelor unui fișier exclusiv pe discul local; în baza de date, versiunile sunt reținute sub forma diferențelor (rezultatul comenzii diff) dintre versiuni succesive, astfel că se poate reveni oricând la o stare anterioară (exemplu: rcs);
  2. sisteme centralizate de control a versiunilor implică stocarea diferențelor dintre versiuni într-o bază de date rezidentă pe un server dedicat, la care au acces toți utilizatorii implicați, astfel încât fiecare poate consulta versiunea curentă (exemple: CVS, Subversion, Perforce);
    • avantaje: posibilitatea de colaborare între echipe care lucrează la același proiect, stabilirea de drepturi cu privire la fișierele ce pot fi încărcate pe server de fiecare utilizator în parte;
    • dezavantaje: în cazul când serverul dedicat pe care găsește baza de date cu tot istoricul versiunilor se defectează, există riscul de a se pierde toate aceste informații (dacă nu există copii de siguranță), păstrându-se doar versiunile salvate pe mașinile utilizatorilor; mai mult, într-o astfel de situație utilizatorii se află în incapacitatea de a mai transmite propriile modificări și de a consulta modificările celorlalți;
  3. sisteme distribuite de control a versiunilor în care consultarea stării actuale a unui fișier presupune și descărcarea, pe discul local, a întregului istoric de modificări astfel încât acesta să poată fi reconstituit în situații precum defectarea serverului; de asemenea, acestea au capacitatea de a gestiona mai multe depozite aflate la distanță, chiar în cazul în care acestea conțin același proiect, permițând stabilirea unor anumite fluxuri de transmitere a informației (exemple: Git, Mercurial, Bazaar, Darcs);

GIT

Istoric

Apariția Git în 2005 este strâns legată de dezvoltarea kernelului pentru Linux, proiect open-source la care lucra o comunitate destul de numeroasă de programatori. Dacă anterior actualizările erau distribuite sub forma unor arhive ce conțineau modificările (1991-2002), respectiv prin intermediul unui sistem distribuit de control al versiunilor denumit BitKeeper (2002-2005), pe măsură ce proiectul a devenit mai complex și din ce în ce mai multe persoane și-au exprimat disponibilitatea de a contribui la dezvoltarea acestuia, s-a pus problema conceperii unui produs care să satisfacă cerințele legate de viteză, arhitectură scalabilă, suport pentru dezvoltare non-liniară (numeroase ramificații la care se lucrează concomitent), distribuire totală, capacitate de a gestiona proiecte de dimensiuni mari.

Caracteristici

Git se diferențiază de alte sisteme de control al versiunilor prin câteva caracteristici:

  1. actualizările aduse fișierelor din cadrul proiectului nu se rețin sub forma unui set de diferențe între versiuni succesive, ci ca instantanee ale acestora la momentul respectiv (pentru eficiență, în situația în care nu există modificări ale unui fișier între versiuni succesive, se va reține o legătură către original); în acest fel, Git se aseamănă foarte mult cu un sistem de fișiere, peste care sunt implementate mai multe utilitare.

    Pentru fiecare operație de consemnare, Git stochează un obiect de tip commit ce conține o referință către părintele sau părinții săi (consemnarea / consemnările anterioare, din care a fost obținut, prin modificarea fișierelor) și o referință către instantaneul propriu-zis. De asemenea, în cadrul acestui obiect se rețin și informații despre dimensiunea totală și suma de control SHA-1, autor și contributor (nume și adresă de poștă electronică), data la care a fost realizată consemnarea, mesajul asociat.

    laborator00b_figura02.jpg
    Structura unui instantaneu este formată din:
    • un obiect de tip tree, o structură de date ce conține referințe spre fiecare resursă ce au fost modificată, aceasta fiind identificată prin denumire și sumă de control SHA-1; și în cazul unui astfel astfel de obiect se rețin dimensiunea totală și suma de control SHA-1;
    • mai multe obiecte de tip blob, corespunzătoare fiecărei resurse din cadrul consemnării respective; acestea sunt identificate prin denumire, dimensiune și suma de control SHA-1.
  2. majoritatea operațiilor se realizează local, astfel încât nu este necesară o conexiune la Internet pentru a putea consemnate anumite actualizări sau pentru a consulta istoricul modificărilor; acest fapt este posibil datorită stocării pe discul local a bazei de date care conține toate versiunile proiectului; se îmbunătățește astfel viteza, iar încărcarea/descărcarea informațiilor (de) pe server se realizează numai atunci când aceasta este posibilă;
  3. integritatea informațiilor este asigurată prin intermediul sumelor de control folosind algoritmul SHA-1; astfel, un fișier / director este reținut în baza de date printr-un șir de 40 de caractere hexazecimale calculat pe baza conținutului acestuia; în acest mod, este imposibil să nu se detecteze coruperea unui fișier / director, în situația în care aceasta se produce;
  4. majoritatea operațiilor implică în principiu adăugarea de informații în baza de date care conține toate versiunile proiectului, ceea ce face ca probabilitatea de a pierde fișiere să fie extrem de redusă, odată ce acestea au fost consemnate;
  5. cele trei stări în care se pot găsi datele monitorizate prin sistemul de control al versiunilor Git sunt:
    • consemnat (eng. committed): modificările aduse au fost stocate în directorul GIT (eng. GIT directory/repository); în acesta sunt reținute toate metadatele și obiectele bazei de date locale conținând istoricul tuturor versiunilor, elemente preluate de fiecare dată când sunt descărcate actualizări ale proiectului;
    • modificat (eng. modified): modificările aduse nu au fost stocate în baza de date locală; astfel de fișiere se regăsesc în zona de lucru (eng. working area), de pe discul local, în care a fost descărcată o anumită versiune a proiectului, spre a fi modificată;
    • în așteptare (eng. staged): modificările aduse au fost consemnate spre a fi incluse într-un instantaneu ce va fi stocat în baza de date locală; acestea sunt reținute în zona de așteptare (eng. staging area), un fișier (denumit și index) din directorul GIT care conține toate modificările ce vor fi consemnate în următoarea versiune.

      laborator00b_figura01.jpg
      Utilizatorul poate alege să ignore anumite date generate (fișiere binare, executabile), care se regăsesc în zona de lucru, dar care nu vor fi monitorizate și nu vor fi consemnate în baza de date, chiar dacă se marchează întregul director din care fac parte în acest scop.

Un scenariu tipic de utilizare a sistemului de versiune Git implică:

  1. descărcarea unei versiuni din directorul GIT în zona de lucru;
  2. modificarea fișierelor corespunzătoare din zona de lucru;
  3. marcarea informațiilor actualizate din zona de lucru ca fiind în așteptare, în vederea consemnării lor;
  4. consemnarea propriu-zisă a datelor din zona de așteptare înapoi în directorul Git.

Instalare & Configurare

Instrucțiunile pentru instalarea și configurarea Git sunt disponibile aici. Informații suplimentare cu privire la comenzile Git și sintaxa acestora pot fi obținute folosind paginile de manual:

aipi2014@ubuntu:~$ git help <command>
aipi2014@ubuntu:~$ git <command> --help
aipi2014@ubuntu:~$ man git-<command>

Moduri de Lucru

Local

În cazul în care se dorește monitorizarea unui proiect nou / existent prin sistemul de control al versiunilor Git, directorul în care se găsește acesta va trebui inițializat folosind comanda:

aipi2014@ubuntu:~$ git init

Astfel, se creează un director .git în care vor fi plasate toate versiunile fișierelor care sunt monitorizate. Inițial, acesta este vid.

Indicarea fișierelor care sunt monitorizate se face prin intermediul comenzii git add <file>, fiind permisă și folosirea de expresii regulate folosind măști pentru a indica conținutul unui întreg director. Prin intermediul acestei comenzi, fișierele sunt transferate din directorul de lucru în zona de așteptare.

Consemnarea propriu-zisă a fișierelor se face rulând comanda git commit -m "<message>", mesajul care o însoțește trebuind să fie relevant pentru modificările care au fost realizate. Se recomandă ca această operație să fie realizată cât mai des pentru actualizări de dimensiuni relativ reduse ale codului sursă. În acest moment, fișierele trec din zona de așteptare în directorul Git.

Pașii descriși vor fi repetați în situația în care aceleași fișiere care au fost consemnate într-o versiune anterioare sunt modificate din nou. Odată ce se realizează operația de consemnare, zona de așteptare devine goală și fișierele modificate nu vor fi prelate în directorul Git chiar dacă sunt marcate ca fiind monitorizate.

La Distanță

În situația în care utilizatorul vrea să lucreze pe un proiect găzduit pe un server la distanță, poate descărca întregul conținut în zona de lucru, inclusiv istoricul complet al versiunilor anterioare (care poate fi ulterior reconstituit după această copie, în cazul coruperii informațiilor stocate pe serverul la distanță), prin intermediul comenzii:

git clone <URL> [<local_directory>]

unde:

  • URL - reprezintă adresa serverului la distanță care găzduiește proiectul, putând fi utilizate în acest sens mai multe protocoale pentru transferul de informație
    • git
      aipi2014@ubuntu:~$ git clone git://github.com/aipi2014/Laborator00.git
    • https
      aipi2014@ubuntu:~$ git clone https://github.com/aipi2014/Laborator00
    • ssh
      aipi2014@ubuntu:~$ git clone git@github.com:aipi2014/Laborator00.git
  • local_directory (opțional) - denumirea directorului local în care va fi stocată versiunea curentă a proiectului (precum și istoricul din directorul Git), în cazul în care se dorește schimbarea acestuia

Fișierele astfel descărcate, aflate atât în zona de lucru cât și în directorul Git pot fi modificate în funcție de necesități și transferate, succesiv, în zona de așteptare (prin git add) și în baza de date locală (prin git commit -m).

Dacă este necesar ca fișierele modificate să fie încărcate pe serverul de unde au fost preluate, trebuie ca mai întâi să se actualizeze modificările care se vor fi produs pe acesta între timp (folosind comanda git pull --rebase) - rezolvând eventualele conflicte - și apoi să se transfere efectiv prin intermediul comenzii git push origin master.

Operații Git

Determinarea stării fișierelor din zona de lucru

Comanda git status furnizează informații cu privire la starea fișierelor aflate în zona de lucru, fie că este vorba de resurse deja monitorizate (care se găsesc în directorul Git) care au fost modificate între timp, fie că este vorba despre date care au fost adăugate (și care nu au fost marcate în mod explicit pentru a fi ignorate). De asemenea, comanda indică și ramificația (eng. branch) pe care se găsește utilizatorul în mod curent.

O astfel de comandă poate întoarce mai multe rezultate:

  • toate fișierele din directorul de lucru se regăsesc întocmai și în directorul Git (nu au fost realizate modificări
    aipi2014@ubuntu:~$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    
    nothing to commit, working directory clean
  • există date în zona de lucru care nu sunt monitorizate (nu au făcut parte dintr-un instantaneu anterior), care vor fi consemnate numai în situația în care se specifică acest lucru în mod explicit; acest mecanism urmărește ca fișierele generate să nu fie incluse în mod eronat în directorul Git
    aipi2014@ubuntu:~$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    
            MiniShell/
    
    nothing added to commit but untracked files present (use "git add" to track)

    Monitorizarea acestor date se face prin intermediul comenzii git add <files/directory>, care suportă specificarea de expresii regulate desemnând măști pentru indicarea mai multor fișiere/directoare.

  • există informații în zona de așteptare care vor fi incluse în următorul instantaneu în momentul în care se va realiza consemnarea (trecute în secțiunea Changes to be committed); eventualele fișiere care au existat în instantanee anterioare și au fost modificate în directorul de lucru nu vor fi marcate ca făcând parte din zona de așteptare (trecute în secțiunea Changes not staged for commit) dacă versiunile respective nu vor fi incluse în mod explicit prin comanda git add <files/directory>
    aipi2014@ubuntu:~$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
    
            new file:   Minishell/.classpath
            new file:   Minishell/.project
            new file:   Minishell/.settings/org.eclipse.jdt.core.prefs
            new file:   Minishell/src/ro/pub/cs/aipi/lab00/applicationlogic/CommandParser.java
            new file:   Minishell/src/ro/pub/cs/aipi/lab00/applicationlogic/FileSystemOperations.java
            new file:   Minishell/src/ro/pub/cs/aipi/lab00/general/Constants.java
            new file:   Minishell/src/ro/pub/cs/aipi/lab00/main/MiniShell.java
    
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
            modified:   README.md

    Fișierele care nu au existat într-un instataneu anterior sunt marcate prin new file, iar cele care au fost modificate față de versiunile precedente sunt marcate prin modified.

Un același fișier se poate găsi simultan în secțiunile Changes to be committed, respectiv Changes not staged for commit în situația în care au mai fost realizate modificări asupra acestuia după rularea comenzii git add <files/directory>.

Transferul fișierelor din zona de lucru în zona de așteptare

Prin intermediul comenzii git add <files/directory> se specifică fișiere / directoare care vor fi transferate din zona de lucru în zona de așteptare, pentru a fi incluse în următorul instantaneu în momentul în care se va realiza consemnarea acestora.

La consemnare vor fi incluse fișierele / directoarele în starea în care acestea se aflau la momentul la care a fost emisă comanda git add. Dacă ele sunt modificate după precizarea acestei comenzi, actualizările nu vor fi consemnate în directorul Git decât dacă se rulează din nou comanda git add.
Dacă argumentul este un director, conținutul acestuia va fi inclus în mod recursiv.
aipi2014@ubuntu:~$ git add Minishell/*

În cazul în care un fișier este transferat din greșeală din zona de lucru în zona de așteptare, parcursul invers poate fi realizat prin intermediul comenzii git reset HEAD <file>.

Dacă se dorește eliminarea modificărilor realizate asupra unui fișier din zona de lucru, se poate folosi comanda git checkout -- <file> (disponibilă începând cu versiunea 1.6.1). O astfel de operație este totuși periculoasă, în sensul că actualizările respective sunt pierdute fără posibilitatea de a mai putea fi recuperate (întrucât nu au fost consemnate niciodată în directorul Git).

Alte operații care pot fi realizate asupra fișierelor din zona de lucru / zona de așteptare sunt mutarea (redenumirea), respectiv ștergerea acestora.

Comanda git mv <source> <target> este folosită pentru operația de mutare (redenumire) a unui fișier. Necesitatea sa este dată de faptul că Git nu detectează în mod automat fișierele care sunt redenumite pe discul local. Dacă se verifică starea fișierelor din zona de lucru / zona de așteptare, fișierele redenumite apar în zona Changes to be committed, în secțiunea renamed.

Comanda git mv <source> <target> este echivalentă cu aceeași succesiune de operații:
aipi2014@ubuntu:~$ mv <source> <target>
aipi2014@ubuntu:~$ git rm <source>
aipi2014@ubuntu:~$ git add <target>
aipi2014@ubuntu:~$ git mv README.md README.txt

Prin intermediul comenzii git rm <file/directory>, resursa specificată este eliminată nu numai de pe discul local, ci și din zona de așteptare, astfel încât atunci când se realizează operația de consemnare, acesta este eliminat din directorul Git și din lista fișierelor care sunt monitorizate. În cazul în care resursa este eliminată manual, doar de pe discul local, aceasta va apărea ca modificată în zona de lucru, dar nemarcată pentru a fi consemnată în zona de așteptare. Dacă se verifică starea fișierelor din zona de lucru / zona de așteptare, fișierele șterse apar în zona Changes to be committed, în secțiunea deleted.

În cazul în care fișierul a fost modificat înainte de a se încerca ștergerea sa, comanda trebuie rulată cu opțiunea -f, pentru a preveni eliminarea accidentală a unor resurse.
Dacă se dorește ștergerea unui director care nu este gol, comanda trebuie rulată cu opțiunea -r (recursiv).
Dacă se dorește ca resursa să fie eliminată doar din zona de aștepare (să nu mai fie monitorizată) dar păstrată pe discul local (dacă a fost inclusă într-o versiune din directorul Git, omițându-se specificarea sa în fișierul .gitignore), comanda trebuie rulată cu opțiunea --cached.
aipi2014@ubuntu:~$ git rm LICENSE
rm 'LICENSE'

Ignorarea unor (tipuri de) fișiere

Este recomandat ca fișierele generate să nu fie incluse în directorul Git (binare, jurnale), acestea fiind rezultatul procesului de compilare / rulare a proiectului. Mai mult, nu se dorește ca informații cu privire la modificarea lor să fie incluse în raportul rezultat ca rulare a comenzii git status.

Un astfel de comportament poate fi obținut prin specificarea acestui tip de fișiere în .gitignore, respectându-se următoarele reguli:

  • pe fiecare linie se poate specifica un anumit tip de fișier care va fi ignorat
  • liniile vide sau precedate de caracterul # nu sunt luate în considerare
  • pot fi folosite expresii regulate (standard) pentru a specifica un set de fișiere
    • * desemnează 0 sau mai multe caractere (trebuie precedat de \)
    • ** (suportat din versiunea 1.8.2) referă conținutul unui director
    • ? indică un singur caracter
    • între { și } sunt trecute colecții de șabloane
    • între [ și ] sunt trecute seturi de caractere (sau intervale între două caractere, separate prin -)
  • folosirea caracterului ! înaintea unui șablon îl neagă

La crearea unui proiect nou, GitHub oferă posibilitatea de a include în mod automat un fișier .gitignore în funcție de limbajul de programare utilizat.

Pentru Java, conținutul fișierului .gitignore generat este:

*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

Vizualizarea modificărilor

Prin intermediul comenzii git diff, utilizatorul are posibilitatea de a vizualiza în ce constau actualizările pentru fiecare fișier în parte.

  • rulată fără parametri, comanda indică diferențele pentru fișierele care au fost modificate, dar nu au fost marcate pentru a fi incluse în următorul instantaneu (cu alte cuvinte, sunt indicate diferențele dintre zona de lucru și zona de așteptare)
    aipi2014@ubuntu:~$ git diff
    
    diff --git a/README.md b/README.md
    index 6094ea3..84033c8 100644
    --- a/README.md
    +++ b/README.md
    @@ -1,2 +1,15 @@
    -Laborator00
    -===========
    +Minishell
    +=========
    +
    +Minishell is a platform-independent shell writen in Java (exploring the New I/O API), supporting commands (though without any options) to manipulate the content of the file system (files and directories).
    
    +
    +In the case of overwriting existing files or deleting non-empty directories, the user will be prompted whether he/she is sure to do so.
    +
    +There is no support at the moment for symbolic links.
    +
    +* v1.0
    +The commands supported so far are:
    + - cd <new_directory>: changes the current directory to the new_directory, which may denote a relative or an absolute path
    + - mkdir <new_directory> / md <new_directory>: creates a new directory within the current directory, if the parameter is a relative path, or at the exact location specified by an absolute path
    + - touch <new_file> / touch <existing_file>: creates a new file with the specified content; appends the specified content to an existing file; the content is to be added line by line until /quit is entered
    + - quit / exit: terminates the program

    <note>Dacă toate fișierele din zona de lucru au fost fie marcate pentru a fi incluse în următorul instantaneu, fie sunt ignorate, rezultatul comenzii git diff va fi vid.</note>

  • rulată cu parametrul --cached sau --staged (disponibil din versiunea 1.6.1, efectul este identic), comanda indică diferențele pentru fișierele care au fost marcate pentru a fi incluse în următorul instantaneu, față de situația existentă în directorul Git (cu alte cuvinte, sunt indicate diferențele dintre zona de așteptare și directorul Git)
    aipi2014@ubuntu:~$ git diff --staged
    
    diff --git a/Minishell/.settings/org.eclipse.jdt.core.prefs b/Minishell/.setting
    new file mode 100644
    index 0000000..3a21537
    --- /dev/null
    +++ b/Minishell/.settings/org.eclipse.jdt.core.prefs
    @@ -0,0 +1,11 @@
    +eclipse.preferences.version=1
    +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
    +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
    +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
    +org.eclipse.jdt.core.compiler.compliance=1.8
    +org.eclipse.jdt.core.compiler.debug.lineNumber=generate
    +org.eclipse.jdt.core.compiler.debug.localVariable=generate
    +org.eclipse.jdt.core.compiler.debug.sourceFile=generate
    +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
    +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
    +org.eclipse.jdt.core.compiler.source=1.8
    ...

Consemnarea modificărilor (transferul fișierelor din zona de așteptare în directorul Git)

Dacă toate fișierele din zona de lucru au fost marcate spre a fi incluse în următorul instantaneu (rezultatul comenzii git status nu conține nici un fișier în secțiunea Changes not staged for commit), ele pot fi consemnate, adică trecute din zona de așteptare în directorul Git, prin intermediul comenzii git commit.

Rulată fără parametru, comanda deschide editorul de text implicit (sau cel indicat de proprietatea core.editor) completat cu rezultat comenzii git status, care poate fi modificat pentru a constitui mesajul asociat fișierelor consemnate în directorul Git. Dacă se dorește ca în acest mesaj să se includă și rezulatul comenzii git diff, se poate utiliza parametrul -v.

Mesajul care însoțește consemnarea fișierelor poate fi inclusă direct în cadrul comenzii prin intermediul parametrului -m.

aipi2014@ubuntu:~$ git commit -m "initial commit: cd, mkdir, touch, quit"
[master 60a2501] initial commit: cd, mkdir, touch, quit
 7 files changed, 224 insertions(+)
 create mode 100644 Minishell/.classpath
 create mode 100644 Minishell/.project
 create mode 100644 Minishell/.settings/org.eclipse.jdt.core.prefs
 create mode 100644 Minishell/src/ro/pub/cs/aipi/lab00/applicationlogic/CommandParser.java
 create mode 100644 Minishell/src/ro/pub/cs/aipi/lab00/applicationlogic/FileSystemOperations.java
 create mode 100644 Minishell/src/ro/pub/cs/aipi/lab00/general/Constants.java
 create mode 100644 Minishell/src/ro/pub/cs/aipi/lab00/main/MiniShell.java

Se observă că în rezultatul acestei comenzi sunt indicate ramificația pe care se face consemnarea (master), suma de control SHA-1 a consemnării (60a2501), numărul de fișiere modificate (7) precum și statistici cu privire la numărul de linii adăugate (224), respectiv eliminate (0).

Consemnarea va transfera în directorul Git numai acele fișiere care au fost marcate în acest sens, existând în zona de așteptare. Dacă se dorește să se realizeze transferul direct din zona de lucru în directorul Git, fără a mai trece în zona de așteptare (doar pentru fișierele monitorizate care au fost modificate), comanda git commit -m "<message>" trebuie rulată și cu parametrul -a.
aipi2014@ubuntu:~$ git commit -a -m "initial commit: cd, mkdir, touch, quit"

Într-un astfel de caz, nu mai este necesar ca în prealabil să se ruleze comanda git add.

Dacă s-au omis resurse în cadrul celei mai recente versiuni transmise către directorul Git, se poate încerca consemnarea modificărilor respective prin intermediul comenzii git commit --amend (ulterioară marcării fișierelor în cauză în zona de așteptare) prin intermediul căreia se suprascrie varianta anterioară cu actualizările din zona de așteptare curentă. Ca și în cazul precedent, este afișat un editor de text care conține mesajul corespunzător versiunii consemnate, acesta putând fi actualizat.

Etichetarea versiunilor

Unei versiuni consemnate în directorul Git îi poate fi asociată o etichetă (eng. tag) prin care se desemnează, de regulă, o anumită funcționalitate.

Git oferă posibilitatea de a defini două tipuri de etichete, prin intermediul comenzii git tag:

  • etichete 'ușoare' (eng. lightweight) pentru care nu se rețin informații suplimentare, cu excepția sumei de control asociate consemnării respective; de regulă, sunt folosite pentru resurse temporare sau în situația în care informațiile asociate nu sunt necesare
    aipi2014@ubuntu:~$ git tag v1.0
  • etichete adnotate care sunt stocate ca obiecte de sine stătătoare în directorul Git, reținându-se numele și adresa de poștă electronică a utilizatorului care a realizat-o, un mesaj asociat (informații distincte de cele ale consemnării asociate etichetei) precum și suma de control
    aipi2014@ubuntu:~$ git tag -a v1.0 -m 'an annotated tag for the first release version'

    Etichetele de acest tip pot fi semnate folosind GPG (GNU Privacy Guard), dacă utilizatorul dispune de o cheie privată, acestea având avantajul că pot fi verificate

    aipi2014@ubuntu:~$ git tag -s v1.0 -m 'a signed tag for the first release version'

    Verificarea unei etichete semnate se face prin intermediul comenzii git tag -v <tag_name>, fiind necesară existența cheii publice a utilizatorului care a semnat-o.
    Așadar, opțiunile cu care se poate rulează comanda git tag în cazul unei etichete adnotate sunt:

    • -a: specifică faptul că este vorba despre o etichetă nesemnată
    • -s: specifică faptul că este vorba despre o etichetă semnată
    • -m: indică mesajul asociat etichetei (dacă se rulează comanda fără acest parametru, va fi deschis editorul de text pentru ca acesta să fie introdus)
    • -v: dacă se dorește verificarea unei etichete semnate

Lista tuturor etichetelor asociate unui proiect poate fi consultată dacă se rulează comanda git tag fără nici un parametru:

aipi2014@ubuntu:~$ git tag
v1.0

Aceasta suportă opțiunea -l <tag_mask> pentru a se afișa doar lista etichetelor care respectă o anumită expresie regulată.

Dacă se dorește consultarea conținutului unei etichete, se poate utiliza comanda git show <tag>, prin care sunt listate toate informațiile asociate versiunii respective.

aipi2014@ubuntu:~$ git show v1.0
commit 5e4b82a0783e5841e5c5241d44104e8b15e4270f
Author: aipi2014 <aipi2014@andreirosucojocaru.ro>
Date:   Tue Sep 30 01:28:08 2014 +0300

    Initial commit

În situația în care o versiune a fost consemnată fără a i se asocia o etichetă, o astfel de operație poate fi realizată și ulterior, adăugând la comanda git tag suma de control asociată respectivei versiuni (obținută ca rezultat al comenzii git log).

aipi2014@ubuntu:~$ git tag -a v1.1 -m 'version 1.1' e71c1c4b1b9818292b4cda084e47e25bfb573507
Acestei comenzi i se poate asocia și numai un fragment al sumei de control asociat versiunii, atâta timp cât acesta o identifică în mod unic.

De regulă, etichetele nu sunt transferate în cadrul depozitelor găzduite de serverele la distanță, fiind necesar ca acest lucru să fie realizat manual:

  • pentru o singură etichetă, se specifică denumirea acesteia git push <remote_name> <tag>;
  • pentru toate etichetele, se folosește comanda git push <remote_name> --tags.

Vizualizarea istoricului de versiuni

Istoricul versiunilor consemnate pentru un proiect poate fi consultat, în ordine invers cronologică (de la cele mai noi la cele mai vechi) rulând comanda git log. Pentru fiecare versiune a proiectului, vor fi afișate următoarele informații:

  • suma de control SHA-1 asociată consemnării
  • numele și adresa de poștă electronică a utilizatorului care a realizat consemnarea
  • data și ora la care a fost realizată consemnarea
  • mesajul care însoțește consemnarea

Cele mai folosite opțiuni ale acestei comenzi sunt:

OPȚIUNE FUNCȚIONALITATE
-p afișează diferențele (rezultatul comenzii git diff) realizate în consemnarea curentă față de consemnarea anterioară
--word_diff afișează diferențele între versiuni, la nivel de cuvânt (mai ales pentru fișiere text de dimensiuni mari, mai rar pentru cod sursă): cuvintele adăugate sunt cuprinse între {+ +}, iar cuvintele șterse între [- -]; dacă se dorește omiterea contextului în care apare diferența (linia precedentă și linia care o succede), se poate folosi opțiunea -U1 (se afișează doar linia curentă)
--stat afișează statistici cu privire la fiecare consemnare în parte: numărul de fișiere modificate, lista acestora și numărul de modificări (adăugări / ștergeri) - la nivel de linie - pentru fiecare dintre acestea
--shortstat afișează doar statistici generale: numărul total de fișiere modificate, adăugări și ștergeri (la nivel de linie)
--name-only afișează lista fișierelor modificate
--name-status afișează lista fișierelor modificate împreună cu natura actualizării (actualizat, adăugat, șters)
--abbrev-commit afișează numai câteva caractere (din cele 40) ale sumei de control SHA-1 asociată fiecărei consemnări
--relative-date afișează momentul la care a fost realizată consemnarea relativ la data curentă
--graph afișează un graf (în format ASCII) al ramificațiilor, ilustrând momentul la care acestea au fost combinate
--pretty modifică formatul în care sunt afișate informațiile despre versiuni:
oneline - informațiile despre fiecare consemnare sunt afișate pe o singură linie
short, full, fuller - controlează cantitatea de informație
format - permite personalizarea informațiilor, util pentru situația în care conținutul urmează să fie prelucrat în mod automat:
%H - suma de control, %h - suma de control prescurtată
%T - arborele sumei de control, %t - arborele sumei de control prescurtat
%P - suma de control a părintelui, %p - suma de control prescurtată a părintelui
%an - numele autorului, %ae - adresa de poștă electonică a autorului
%ad - data autorului, %ar - data autorului (relativă)
%cn - numele contributorului*), %ce - adresa de poștă electronică a contributorului
%cd - data contributorului, %cr - data contributorului (relativă)
%s - subiectul
--oneline afișează numai câteva caractere (din cele 40) ale sumei de control SHA-1 asociată fiecărei consemnări, pe o singură linie
prescurtare pentru --pretty=online abbrev-commit

*) Distincția dintre autor și contributor este următoarea:

  • autorul este utilizatorul care a implementat o anumită funcționalitate
  • contributorul este utilizatorul care a realizat cea mai recentă consemnare pentru funcționalitatea în cauză

În situația în care pentru un proiect au fost realizate foarte multe consemnări, există posibilitatea ca rezultatul comenzii git log să fie limitat doar la cele care sunt de interes (oricum, în mod implicit, se realizeză o paginare astfel încât este imposibil ca acestea să fie afișate toate dintr-o dată):

OPȚIUNE FUNCȚIONALITATE
-<n> afișează doar cele mai recente n consemnări
--since, --after afișează consemnările realizate după cu o anumită dată / oră absolută / relativă *)
--until, --before afișează consemnările realizate înainte de o anumită dată/oră absolută / relativă
--author afișează doar consemnările având un anumit autor
--committer afișează doar consemnările având un anumit contributor
--grep afișează doar consemnările având anumite cuvinte în mesajul asociat; dacă se dorește indicarea mai multor cuvinte, trebuie utilizată împreună cu opțiunea --all-match
--<path> afișează doar consemnările care au realizat modificări asupra fișierelor localizate în calea specificată; această opțiune trebuie inclusă întotdeauna ultima

*) Formatul în care se afișează data este specificat de opțiunea --date, care poate lua valorile iso (ISO 8601), rfc (RFC 2822), raw (număr de secunde de la 01/01/1970 UTC), local (în conformitate cu zona de timp), relative (raportat la momentul curent de timp).

Dacă se utilizează mai multe opțiuni, se vor lua în considerare consemnările care îndeplinesc toate criteriile specificate.

aipi2014@ubuntu:~$ git log --pretty=format:"%h - %an [%ae] - %ar -> %s" --graph
*   7224392 - Aplicatii Integrate pentru Intreprinderi 2014 [aipi2014@andreirosucojocaru.ro] - 6 minutes ago -> resolved conflicts: contents in delete branch included functionality of copy_move
|\  
| * 30fe2f3 - Aplicatii Integrate pentru Intreprinderi 2014 [aipi2014@andreirosucojocaru.ro] - 25 minutes ago -> added support for delete commands
* | 477dcd6 - Aplicatii Integrate pentru Intreprinderi 2014 [aipi2014@andreirosucojocaru.ro] - 29 minutes ago -> added support for copy and move commands
|/  
* d6c99c9 - andreirosucojocaru [andrei.rosucojocaru@gmail.com] - 42 minutes ago -> added support for touch command
* 3b6355b - Aplicatii Integrate pentru Intreprinderi 2014 [aipi2014@andreirosucojocaru.ro] - 77 minutes ago -> removed LICENSE
* c387a66 - Aplicatii Integrate pentru Intreprinderi 2014 [aipi2014@andreirosucojocaru.ro] - 78 minutes ago -> initial commit: support for cd, mkdir, md
* d438c81 - aipi2014 [aipi2014@andreirosucojocaru.ro] - 83 minutes ago -> Initial commit

Gestiunea depozitelor la distanță

Mai mulți utilizatori pot colabora în cadrul unui proiect Git aflat pe un server la distanță (eng. remote repository), pe care pot avea fie doar drepturi de citire fie atât drepturi de citire cât și de scriere. Operațiile pe care le pot realiza sunt descărcarea de cod sursă, respectiv încărcarea (în situația în care au drepturi suficiente).

Vizualizarea referințelor către depozitele la distanță (git remote)

Prin intermediul comenzii git remote, pot fi consultate depozitele la distanță cu care se lucrează în mod curent. În mod implicit, sunt afișate doar denumirile scurte asociate acestora. În cazul în care se dorește să se afișeze și URL-ul locației corespunzătoare fiecărui depozit la distanță, se va folosi opțiunea -v.

Dacă directorul pe care se lucrează în mod curent a fost obținut ca urmare a clonării unui depozit la distanță, acesta va fi afișat ca având denumirea origin.

aipi2014@ubuntu:~$ git remote -v
Laborator00_andreirosucojocaru  git@github.com:andreirosucojocaru/Laborator00.git (fetch)
Laborator00_andreirosucojocaru  git@github.com:andreirosucojocaru/Laborator00.git (push)
origin  https://github.com/aipi2014/Laborator00.git (fetch)
origin  https://github.com/aipi2014/Laborator00.git (push)

Utilizatorul va avea drepturi de scriere numai pe depozitele la distanță pentru care dispune de cheile SSH corespunzătoare.

Adăugarea unei referințe către un depozit la distanță

Pentru a putea referi un depozit la distanță prin intermediul unei denumiri (mai scurte) se va rula comenda git remote add:

git remote add <remote_name> <URL>

Astfel, nu va mai fi necesară introducerea întregului URL corespunzător locației la care se găsește depozitul la distanță (pentru comenzile de încărcare/descărcare, respectiv consultare a acestuia), fiind suficientă precizarea denumirii asociate.

aipi2014@ubuntu:~$ git remote add Laborator00_andreirosucojocaru git@github.com:andreirosucojocaru/Laborator00.git

Descărcarea de cod sursă de pe un depozit la distanță

Pentru a descărca cod sursă aflat într-un depozit găzduit de un server la distanță pe discul local există trei posibilități:

  • comanda git clone <URL> realizează o copie a datelor aflate la locația indicată de URL, inclusiv a tuturor ramificațiilor (eng. branches), ramificația master (în situația în care există) de pe server fiind monitorizată pentru modificări, astfel încât acestea să fie integrate automat (eng. merged) în codul sursă din directorul de lucru
    aipi@ubuntu:~$ git clone https://github.com/aipi2014/Laborator00.git
    Cloning into 'Laborator00'...
    remote: Counting objects: 30, done.
    remote: Compressing objects: 100% (19/19), done.
    remote: Total 30 (delta 3), reused 30 (delta 3)
    Unpacking objects: 100% (30/30), done.
    Checking connectivity... done.
  • comanda git fetch <remote_name> descarcă toate informațiile din depozitul de pe serverul la distanță care nu se regăsesc pe discul local, creându-se referințe către ramificația de la locația respectivă, care pot fi consultate pentru ca ulterior să fie integrate
    aipi@ubuntu:~$ git fetch Laborator00_andreirosucojocaru
    remote: Counting objects: 39, done.
    remote: Compressing objects: 100% (19/19), done.
    remote: Total 39 (delta 5), reused 39 (delta 5)
    Unpacking objects: 100% (39/39), done.
    From https://github.com/andreirosucojocaru/Laborator00
     * [new branch]      master     -> Laborator00_andreirosucojocaru/master
Comanda git fetch nu integrează modificările existente în depozitul existent pe serverul la distanță în mod automat, fiind necesar ca această operație să fie realizată manual.
  • comanda git pull descarcă modificările dintr-o ramificație monitorizată (de exemplu, ramificația master în situația în care depozitul este clonat) din depozitul de pe serverul la distanță, încercând să le integreze în mod automat în codul sursă din directorul de lucru.

Încărcarea de cod sursă pe un depozit la distanță

Transmiterea modificărilor operate asupra unui cod sursă pe un depozit găzduit de un server la distanță se face prin intermediul comenzii git push <remote_name> <branch>, care primește ca parametrii denumirea referinței către depozitul la distanță (aceasta este origin în situația în care proiectul a fost clonat) și ramificația pe care se găsește codul sursă care urmează a fi clonat.

aipi2014@ubuntu:~$ git push origin master
Counting objects: 31, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (16/16), 1.41 KiB | 0 bytes/s, done.
Total 16 (delta 5), reused 0 (delta 0)
Operația git push va eșua în situația în care depozitul din directorul de lucru local nu este actualizat, adică dacă pe serverul la distanță se găsesc modificări (încărcate de alți utilizatori) care nu au fost descărcate încă. În această situație, înainte de a se transmite propriile modificări, va trebui să se ruleze comanda git pull.

Consultarea conținutului unui depozit la distanță

Dacă se dorește obținerea de informații cu privire la conținutul unui proiect găzduit de un server la distanță, se poate rula comanda git remote show <remote-name>, care afișează:

  • URL-ul depozitului la distanță
  • ramificația curentă din depozitul la distanță
  • ramificația care este încărcată pe server atunci când se rulează comanda git push
  • ramificațiile descărcate de pe server în momentul în care se rulează comanda git pull
  • ramificațiile care au fost adăugate pe server dar nu se găsesc și în directorul de lucru local, ramificațiile care au fost șterse de pe server dar se mai găsesc încă în directorul de lucru local
aipi2014@ubuntu:~$ git remote show origin
* remote origin
  Fetch URL: https://github.com/aipi2014/Laborator00.git
  Push  URL: https://github.com/aipi2014/Laborator00.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

Redenumirea unei referințe către un depozit la distanță

În cazul în care se dorește să se schimbe denumirea asociată unei referințte către un depozit la distanță, acest lucru poate fi realizat prin comanda git remote rename <old_remote_name> <new_remote_name>.

aipi2014@ubuntu:~$ git remote rename Laborator00_andreirosucojocaru Lab00_arc
Comanda are impact și asupra denumirilor ramificațiilor asociate depozitului la distanță corespunzător referinței, care va fi prefixat de noua denumire.

Ștergerea unei referințe către un depozit la distanță

O referință către un depozit la distanță poate fi ștearsă în situația în care codul sursă nu mai este disponibil la adresa respectivă sau dacă utilizatorul respectiv nu mai lucrează pe proiectul în cauză. Comanda utilizată într-o astfel de situație este git remote rm <remote_name>.

aipi2014@ubuntu:~$ git remote rm Lab00_arc

Gestiunea ramificațiilor

O ramificație (eng. branch) marchează un punct din care dezvoltarea unui proiect se realizează în zone diferite (corespunzătoare unor anumite funcționalități sau unor anumite echipe care le implementează), în scopul obținerii unui nivel de izolare cât mai mare până la obținerea rezultatului dorit, când mai multe ramificații pot fi integrate împreună.

De regulă, există o ramificație corespunzătoare unei versiuni stabile, aflată în exploatare și o ramificație pentru versiunea de lucru a proiectului în care sunt integrate, după ce au fost suficient testate, ramificațiile aferente diferitelor funcționalități. Acestea vor constitui o propunere de actualizare care va fi inclusă în următoarea versiune stabilă atunci când se consideră necesar.

Modul în care Git implementează gestiunea ramificațiilor îl evidențiază printre celelalte sisteme de versionare a codului sursă, operațiile care le vizează realizându-se foarte rapid.

O ramificație în Git nu este altceva decât o referință (un pointer) către cea mai recentă consemnare (obiect de tip commit) pe care o urmează pe măsură ce sunt dezvoltate noi versiuni. Astfel, o ramificație este un fișier ce conține cele 40 de caractere ale sumei de control SHA-1 corespunzătoarele consemnării curente, motiv pentru care crearea și distrugerea acesteia se realizează foarte rapid (comparativ cu alte sisteme de versionare a codului sursă care copiau întregul proiect). De asemenea, referințele către consemnările părinte face ca operația de integrare a modificărilor să fie foarte rapidă, întrucât se poate identifica ușor care este consemnarea de bază (comună tuturor ramificațiilor implicate) peste care se pot aplica actualizările.

Datorită modului eficient în care sunt implementate ramificațiile în Git, se recomandă ca pentru fiecare funcționalitate care va fi implementată să se creeze o ramificație separată care va fi integrată doar după ce se rezolvă toate problemele identificate.

Implicit, ramificația curentă pe care se lucrează în Git poartă denumirea de master.

Ramificația pe care se găsește utilizatorul în mod curent este indicată de o altă referință, denumită HEAD.

Vizualizarea ramificațiilor

Pentru a se lista ramificațiile din cadrul proiectului curent, se rulează comanda git branch, fără nici un parametru:

aipi2014@ubuntu:~$ git branch
  copy_move
  delete
* master

În cadrul rezultatului, ramura pe care se găsește utilizatorul în mod curent este marcată prin caracterul *, al cărui conținut este reflectat de directorul de lucru și pe care urmează să se realizeze următoarea operație de consemnare.

Opțiunile cu care se poate rula această comandă sunt:

  • -v pentru a se indica cea mai recentă consemnare corespunzătoare fiecărei ramificații (se afișează o parte din suma de control și mesajul asociat)
  • --merged pentru a se afișa doar ramificațiile care au fost deja integrate în cadrul ramificației pe care utilizatorul se găsește în mod curent
Ramificațiile care au fost deja integrate în ramificația pe care utilizatorul se găsește în mod curent pot fi șterse întrucât conținutul lor există în mai multe locuri simultan.
  • --no-merged pentru a se afișa ramificațiile al căror conținut nu a fost încă integrat în cadrul ramificației pe care utilizatorul se găsește în mod curent
Sistemul Git nu va permite ștergerea ramificațiilor al căror conținut nu a fost încă integrat decât în situația în care se rulează comanda git branch -D <branch_name>, cu pierderea modificărilor realizate în cadrul acesteia.

Crearea unei ramificații

Comanda git branch <branch_name> are rolul de a crea o nouă ramificație în cadrul proiectului, având o denumirea dată. Inițial, aceasta va fi o referință către aceeași versiune ca și master, urmând ca pe măsură ce sunt realizate noi consemnări, să indice obiectele de tip commit corespunzătoare acestora.

aipi2014@ubuntu:~$ git branch copy_move
aipi2014@ubuntu:~$ git branch delete

Transferul între ramificații

Prin intermediul comenzii git checkout <branch_name> se realizează mutarea din ramificația curentă în ramificația indicată ca parametru, prin modificarea pointerului HEAD. Totodată, se actualizează și conținutul directorului de lucru de pe discul local, corespunzător cu structura ramificației pe care s-a trecut.

aipi2014@ubuntu:~$ git checkout copy_move
Switched to branch 'copy_move'
Este recomandat ca înainte de a se realiza mutarea pe o altă ramificație, să se consemneze toate modificările din directorul de lucru întrucât, în caz contrar, operația de transfer nu va fi permisă (restaurarea directorului de lucru la conținutul corespunzător ramificației respective va intra în conflict cu modificările realizate).

laborator00b_figura03.jpg

Operațiile de creare a unei noi ramificații și de mutare în cadrul acesteia (obținută prin rularea succesivă a comenzilor git branch și git checkout) pot fi realizate concomitent prin intermediul comenzii git checkout -b <branch_name>.

aipi2014@ubuntu:~$ git checkout -b copy_move
Switched to a new branch 'copy_move'

Integrarea conținutului a două ramificații și rezolvarea conflictelor

Integrarea modificărilor realizate pe o ramificație poate fi realizată în Git prin:

  • merge
  • rebase

Rezultatul celor două tipuri de operații este întotdeauna același, în sensul că versiunea obținută va avea același conținut, distincția constând în modul în care este consemnat istoricul: în cazul merge acesta reflectă exact ce s-a întâmplat (care este punctul din care proiectul a fost dezvoltat în paralel și care este momentul de timp în care modificările au fost integrate), fiind însă mai dificil de gestionat, în timp ce în cazul rebase, dezvoltarea proiectului apare ca fiind liniară, ceea ce face ca referințele către stările proiectului să poată fi mutate mai ușor, putând fi introduse însă probleme serioase în cazul în care această operație este realizată pentru o consemnare existentă pe un server la distanță (aceasta dispare de pe server, în timp ce poate să existe în directoarele de lucru de pe discul local al utilizatorilor).

Varianta MERGE

Integrarea modificărilor realizate pe o ramificație în varianta MERGE se face prin comanda git merge <branch_name>, după ce toate modificările din cadrul acesteia au fost consemnate și s-a trecut înapoi pe ramificația master.

aipi2014@ubuntu:~$ git checkout master
Switched to branch 'master'
aipi2014@ubuntu:~$ git merge copy_move
Updating d6c99c9..477dcd6
Fast-forward
 .../aipi/lab00/applicationlogic/CommandParser.java |   8 ++
 .../applicationlogic/FileSystemOperations.java     | 117 +++++++++++++++++++++
 .../ro/pub/cs/aipi/lab00/general/Constants.java    |   7 ++
 README.txt                                         |   8 ++
 4 files changed, 140 insertions(+)
aipi2014@ubuntu:~$ git merge delete
Auto-merging README.txt
CONFLICT (content): Merge conflict in README.txt
Auto-merging MiniShell/src/ro/pub/cs/aipi/lab00/general/Constants.java
CONFLICT (content): Merge conflict in MiniShell/src/ro/pub/cs/aipi/lab00/general/Constants.java
Auto-merging MiniShell/src/ro/pub/cs/aipi/lab00/applicationlogic/FileSystemOperations.java
CONFLICT (content): Merge conflict in MiniShell/src/ro/pub/cs/aipi/lab00/applicationlogic/FileSystemOperations.java
Auto-merging MiniShell/src/ro/pub/cs/aipi/lab00/applicationlogic/CommandParser.java
CONFLICT (content): Merge conflict in MiniShell/src/ro/pub/cs/aipi/lab00/applicationlogic/CommandParser.java
Automatic merge failed; fix conflicts and then commit the result.
  • operația poartă denumirea de fast-forward în situația în care obiectul commit corespunzător ramificației care este integrată referă ramificația cu care se integrează (urmărind pointerul părinte), întrucât în acest caz pointer-ii master și HEAD sunt pur și simplu mutați înainte;
  • în situația în care dezvoltarea proiectului s-a realizat prin ramificații paralele dintr-un anumit punct înainte și obiectul commit corespunzător ramificației care este integrată nu referă ramificația cu care se integrează (urmărind pointerul părinte), se procedează la următorul algoritm:
    • se identifică cel mai recent strămoș comun al celor două ramificații (urmărind pointerul părinte al celor două ramificații până se întâlnește punctul din care acestea au urmat căi independente de dezvoltare)

      laborator00b_figura04.jpg
    • se creează o nouă ramificație în care sunt integrate modificările din cele două ramificații, pornind de la codul sursă comun al celui mai recent strămoș comun, pointerul părinte acesteia indicând spre ambele ramificații din care a provenit

      laborator00b_figura05.jpg

      Există posibilitatea ca ambele ramificații să fi modificat aceleași resurse în același loc, ceea ce poate genera anumite conflicte de integrare. Într-o astfel de situație, procesul de creare al consemnării este oprit, iar fișierele în care sunt detectate astfel de anomalii nu vor fi marcate în zona de așteptare pentru a fi incluse în versiunea ce integrează cele două ramificații.
      Fișierele respective vor conține regiuni în care conflictele sunt delimitate prin conținutul versiunii master (între <<<<<<< HEAD și =======) și a versiunii care se dorește a fi integrată (între ======= și <<<<<<< <branch_name>).
      Rezolvarea conflictului poate fi realizată în două moduri, impunându-se însă realizarea consemnării propriu-zise după fiecare dintre acestea (cu un mesaj care explică în ce a constat rezolvarea conflictului, dacă nu este evident):
    1. manual, înlocuind secțiunea de cod sursă marcată ca reprezentând sursa de conflict cu varianta care se dorește a fi regăsită în versiunea finală, urmată de transferul fișierului din directorul de lucru în zona de așteptare prin git add;
    2. folosind un utilitar vizual (indicat de variabila de configurare merge.tool)
Varianta REBASE

Integrarea modificărilor realizate pe o ramificație în varianta REBASE se face prin comanda git rebase master, după ce toate modificările din cadrul acesteia au fost consemnate. În acest caz, se identifică modificările realizate în cadrul ramificației de la cel mai recent strămoș comun, acestea fiind aplicate versiunii indicate de ramificația master și introduse într-o consemnare care o va referi direct pe aceasta, ca părinte. După această operație, se poate trece înapoi pe ramificația master, integrându-se printr-o operație de tip fast-forward modificările din ramificația obținută (spre care va indica referința ramificației care a fost integrată).

laborator00b_figura06.jpg

aipi2014@ubuntu:~$ git checkout copy_move
aipi2014@ubuntu:~$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
aipi2014@ubuntu:~$ git checkout master
Switched to branch 'master'
aipi2014@ubuntu:~$ git merge copy_move

Dacă nu se dorește ca inițial să se treacă pe ramificația care urmează a fi integrată, se poate rula comanda git rebase <branch_to_integrate_in> <branch_to_be_integrated>. Prin aceasta, se trece pe ramificația ce urmează a fi integrată, realizându-se apoi operația de integrare a acesteia în ramura de bază, în care se face integrarea.

Astfel,

aipi2014@ubuntu:~$ git rebase master copy_move

este echivalentă cu

aipi2014@ubuntu:~$ git checkout copy_move
aipi2014@ubuntu:~$ git rebase master

În situația în care proiectul conține mai multe ramificații de dezvoltare în paralel, dorindu-se doar integrarea unora și omiterea (pe moment) a altora, se poate folosi comanda git rebase --onto <branch_to_integrate_in> <branch_to_be_ommitted> <branch_to_be_included>. Prin intermediul acestei comenzi, se identifică cel mai recent strămoș comun între ramificația care trebuie omisă și cea care trebuie inclusă, aplicându-se modificările realizate din acest punct asupra ramurii pe care se realizează integrarea.

Varianta REBASE se folosește de obicei atunci când se realizează contribuții pe depozite la distanță, astfel încât istoricul acestora să nu devină mai complex prin introducerea de ramificații suplimentare, existente doar în directorul de lucru local, integrarea constând într-o simplă operație de tip fast-forward.

Varianta REBASE nu trebuie folosită asupra versiunilor care se găsesc în depozite rezidente pe servere la distanță întrucât referințele către acestea nu vor mai exista, făcând dificilă integrarea modificărilor realizate de alți utilizatori care se bazau pe ea. De aceea, ea reprezintă o soluție doar pentru acele ramificații care nu au fost făcute publice niciodată.

Distrugerea unei ramificații

Ștergerea unei ramificaţii este necesară în momentul în care aceasta referă o consemnare spre care mai există și alte referințe, ale unor ramificaţii în care au fost integrate (de regulă master). O astfel de operaţie poate fi realizată prin comanda git branch -d <branch_name>.

aipi2014@ubuntu:~$ git branch -d copy_move
Deleted branch copy_move (was 477dcd6).
aipi2014@ubuntu:~$ git branch -d delete
Deleted branch delete (was 30fe2f3).

Lucrul cu ramificații la distanță

O ramificație la distanță este o referință către starea unei ramificații aflată într-un depozit găzduit de un server la distanță. Ele sunt stocate în directorul local, fiind descărcate în mod automat în momentul în care se realizează comunicații prin rețea. Nu se pot realiza nici un fel de operații asupra lor, cu excepția consultării, pentru a se realiza mai ușor comparația față de directorul de lucru de pe discul local.

Modul în care sunt referite ramificațiile la distanță sunt <remote-name> / <branch-name>.

Spre exemplu, atunci când se clonează un proiect, se reține pe discul local o referință către origin/master în mod automat.

În momentul în care se rulează comanda git fetch, sunt descărcate toate consemnările din depozitul la distanță, actualizându-se în mod corespunzător referințele către ramificații, așa cum se găsesc acolo.

Git nu realizează automat integrarea modificărilor din directorul Git local cu conținutul ramificațiilor la distanță, fiind necesar ca operația să fie realizată manual prin git merge <remote-name>/<branch-name> urmată de încărcarea versunii obținute pe server, după rezolvarea eventualelor conflicte.
Dacă se dorește crearea unei ramificații locale pe baza conținutului rezident pe serverul la distanță, se poate rula comanda git checkout -b <local_branch> <remote_name>/<remote_branch> (cu forma git checkout --track <remote_name>/<branch_name>, dacă denumirile ramificațiilor local și la distanță corespund).
Aceste obiecte poartă denumirea de ramificații de monitorizare, iar operațiile git push și git pull încarcă / descarcă informații în/din ramificația corespunzătoare de pe serverul la distanță în mod automat, fără a mai avea nevoie de alți parametri.

De regulă, ramificațiile create în directorul de lucru de pe discul local nu sunt vizibile la distanță. Dacă se dorește totuși să se partajeze o astfel de informație, se va rula comanda git push <remote_name> <local_branch>[:<remote_branch>]. În acest mod, modificările din ramificația local_branch din directorul de lucru de pe discul local vor fi încărcate pe ramificația remote_branch din depozitul de pe serverul la distanță (cu condiția să existe drepturi de acces pe server).

În situația în care o ramificație de pe serverul la distanță nu mai este necesară (modificările sale au fost integrate în altă ramificație de pe serverul la distanță, de exemplu master), se poate rula comanda git push <remote_name> :<remote_branch>.

În cazul în care denumirea ramificației din depozitul de pe serverul la distanță coincide cu ramificația din directorul de lucru de pe discul local, comanda ia forma git push <remote_name> <branch_name>.

Activitate de Laborator

  1. [5 puncte] Instalați git pe Linux sau pe Windows, urmând instrucțiunile date.
  2. [5 puncte] Accesați GitHub și creați-vă un cont, dacă nu aveți deja unul.
  3. [10 puncte] Realizați configurațiile globale, specificând informații precum user.name, user.email, core.editor, merge.tool. Verificați faptul că informațiile au fost introduse corect, prin două metode diferite.
  4. [10 puncte] Generați o cheie SSH (dacă nu există una pe sistemul de operare) și asociați-o contului GitHub. În acest mod, veți putea accesa contul fără a fi necesare parole.
  5. [5 puncte] Creați un depozit pe contul Github creat, denumit 'Laborator00'. Inițial, acesta trebuie să fie gol (nu trebuie să bifați nici adăugarea unui fișier README.md, nici a fișierului .gitignore și a a fișierului LICENSE).
  6. [10 puncte] Clonați în directorul de pe discul local conținutul depozitului la distanță de la https://www.github.com/aipi2014/Laborator00.git. În urma acestei operații, directorul Laborator00 va trebui să se conțină proiectul Eclipse denumit Minishell, fișierul README.txt și un fișier .gitignore care indică tipurile de fișiere (extensiile) ignorate.
  7. [5 puncte] Să se încarce conținutul descărcat în cadrul depozitului 'Laborator00' de pe contul Github personal.
  8. [15 puncte] În depozitul de pe serverul la distanță există o ramificație denumită list_directory_content.
    1. Să se creeze o ramificație în directorul de lucru de pe discul local pe baza conținutului de pe serverul la distanță clonat inițial, având același nume.
    2. Să se integreze modificările în cadrul ramificației master.
    3. Să se distrugă ramificația a cărei funcționalitate a fost integrată.
    4. Să se consemneze în cadrul depozitului 'Laborator00' de pe contrul Github personal conținutul directorului de lucru după realizarea integrării.
  9. [10 puncte] Să se consulte istoricul versiunilor sub formă de graf, afișând pentru fiecare versiune în parte, pe o singură linie, suma de control SHA-1 în varianta prescurtată, numele și adresa de poștă electronică a autorului, momentul de timp la care s-a realizat consemnarea (relativ la momentul de timp curent) precum și mesajul asociat.
  10. [15 puncte] Să se creeze o ramificație denumită pwd, derivată din master, pe care să se implementeze rezultatul comenzii cu același nume, care afișează directorul curent.
    1. Să se asocieze acestei versiunii o etichetă adnotată cu numele v4.0.1 și cu un mesaj care reflectă funcționalitatea sa.
    2. Să se încarce în depozitul asociat contului Github personal această ramificație.
  11. [15 puncte] Să se creeze o ramificație denumită cat, derivată din master, pe care să se implementeze rezultatul comenzii cu același nume, care afișează conținutul fișierului indicat ca parametru.
    1. Să se asocieze acestei versiunii o etichetă adnotată cu numele v4.0.2 și cu un mesaj care reflectă funcționalitatea sa.
    2. Să se încarce în depozitul asociat contului Github personal această ramificație.
  12. [15 puncte] Să se integreze funcționalitățile din ramificațiile pwd și cat în ramificația master, apoi să se elimine din depozitul la distanță asociat contului Github personal.

Resurse

Soluții

3. Specificarea variabilelor de configurare se realizează prin comenzile:

aipi2014@ubuntu:~$ git configure --global user.name "Some Name"
aipi2014@ubuntu:~$ git configure --global user.email somename@somedomain.com
aipi2014@ubuntu:~$ git configure --global core.editor gedit
aipi2014@ubuntu:~$ git configure --global merge.tool diff

Verificarea valorii pe care o au variabilele de configurare poate fi realizată în mai multe moduri:

aipi2014@ubuntu:~/GitHub$ git configure --list
aipi2014@ubuntu:~/GitHub$ cat .git/config
aipi2014@ubuntu:~/GitHub$ git config user.name
aipi2014@ubuntu:~/GitHub$ git config user.email
aipi2014@ubuntu:~/GitHub$ git config core.editor
aipi2014@ubuntu:~/GitHub$ git config merge.tool

6.

aipi2014@ubuntu:~/GitHub$ git init
aipi2014@ubuntu:~/GitHub$ git clone https://www.github.com/aipi2014/Laborator00.git

7.

aipi2014@ubuntu:~/GitHub$ cd Laborator00
aipi2014@ubuntu:~/GitHub/Laborator00$ git remote add Laborator00_somename git@github.com:somename/Laborator00.git
aipi2014@ubuntu:~/GitHub/Laborator00$ git push Laborator00_somename master

8. I.

aipi2014@ubuntu:~/GitHub/Laborator00$ git checkout --track origin/list_directory_content

II.

aipi2014@ubuntu:~/GitHub/Laborator00$ git checkout master
aipi2014@ubuntu:~/GitHub/Laborator00$ git merge list_directory_content

III.

aipi2014@ubuntu:~/GitHub/Laborator00$ git branch -d list_directory_content

IV.

aipi2014@ubuntu:~/GitHub/Laborator00$ git push Laborator00_somename master

9.

aipi2014@ubuntu:~/GitHub/Laborator00$ git log --pretty=format:"%h - %an [%ae] - %ar -> %s" --graph

10.

Constants.java
public interface Constants {
    // ...
    final public static String PRINT_WORKING_DIRECTORY_COMMAND	= "pwd";
    // ...
}
CommandParser.java
// ...
public Path analyze(String command, Path currentDirectory, Scanner scanner) {
    String[] parts = command.split(" ");
    FileSystemOperations fileSystemOperations = new FileSystemOperations();
    switch (parts[0]) {
        // ...
	case Constants.PRINT_WORKING_DIRECTORY_COMMAND:
	    fileSystemOperations.printWorkingDirectory(currentDirectory);
	    break;
	// ...
	default:
	    System.out.println("This command is not supported");
	    break;
    }
    return currentDirectory;
}
FileSystemOperations.java
// ...
public void printWorkingDirectory(Path currentDirectory) {
    System.out.println(currentDirectory.toString());
}
// ...

I.

aipi2014@ubuntu:~/GitHub/Laborator00$ git checkout -b pwd
// implementare functionalitate pwd
aipi2014@ubuntu:~/GitHub/Laborator00$ git add .
aipi2014@ubuntu:~/GitHub/Laborator00$ git tag -a v4.0.1 -m "added support for pwd command"

II.

aipi2014@ubuntu:~/GitHub/Laborator00$ git push Laborator00_somename pwd

11.

Constants.java
public interface Constants {
    // ...
    final public static String CATENATE	= "cat";
    // ...
}
CommandParser.java
// ...
public Path analyze(String command, Path currentDirectory, Scanner scanner) {
    String[] parts = command.split(" ");
    FileSystemOperations fileSystemOperations = new FileSystemOperations();
    switch (parts[0]) {
        // ...
	case Constants.CATENATE:
	    fileSystemOperations.catenate(parts[1], currentDirectory);
	    break;
	// ...
	default:
	    System.out.println("This command is not supported");
	    break;
    }
    return currentDirectory;
}
FileSystemOperations.java
// ...
public void catenate(String name, Path currentDirectory) {
    Path path = checkIfExists(name, currentDirectory);
    if (path != null && Files.isRegularFile(path) && Files.isReadable(path)) {
        Charset charset = Charset.forName("UTF-8");
	try (BufferedReader bufferedReader = Files.newBufferedReader(path, charset)) {
	    String line = null;
	    while ((line = bufferedReader.readLine()) != null)
	        System.out.println(line);
	} catch (IOException ioException) {
	    System.out.println("Operation could not be performed !"+ioException.getMessage());
	}
    }
    else
        System.out.format("Could not display the contents of file %s!%n",name);
}
// ...

I.

aipi2014@ubuntu:~/GitHub/Laborator00$ git branch cat
aipi2014@ubuntu:~/GitHub/Laborator00$ git checkout cat
// implementare functionalitate cat
aipi2014@ubuntu:~/GitHub/Laborator00$ git add .
aipi2014@ubuntu:~/GitHub/Laborator00$ git tag -a v4.0.2 -m "added support for cat command"

II.

aipi2014@ubuntu:~/GitHub/Laborator00$ git push Laborator00_somename cat

12.

aipi2014@ubuntu:~/GitHub/Laborator00$ git checkout master
aipi2014@ubuntu:~/GitHub/Laborator00$ git merge pwd
aipi2014@ubuntu:~/GitHub/Laborator00$ git push Laborator00_somename master
aipi2014@ubuntu:~/GitHub/Laborator00$ git checkout master
aipi2014@ubuntu:~/GitHub/Laborator00$ git merge cat
aipi2014@ubuntu:~/GitHub/Laborator00$ git push Laborator00_somename master
aipi2014@ubuntu:~/GitHub/Laborator00$ git mergetool
aipi2014@ubuntu:~/GitHub/Laborator00$ git add .
aipi2014@ubuntu:~/GitHub/Laborator00$ git commit -m "resolved conflicts between pwd and cat branches"
aipi2014@ubuntu:~/GitHub/Laborator00$ git push Laborator00_somename master
aipi2014@ubuntu:~/GitHub/Laborator00$ git push Laborator00_somename :pwd
aipi2014@ubuntu:~/GitHub/Laborator00$ git push Laborator00_somename :cat
laboratoare/laborator00b.txt · Last modified: 2014/10/19 15:04 by Andrei Roșu-Cojocaru
CC Attribution-Share Alike 4.0 International
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0