Tutorial Git

  • Responsabil: Florian-Luis Micu
  • Data publicării: 15.11.2022
  • Data ultimei modificări: 12.11.2023

Descriere

Git-ul este o unealtă foarte utilă pentru orice programator întrucât vă oferă toatea avantajele unui tool de versionare. Mai exact, puteți reveni la o formă precedentă a proiectului vostru sau puteți să aveți o formă securizată a proiectului în cloud pe care o puteți descărca pe alt device cu foarte mare ușurință.

Pentru a învăța cum să folosiți tool-ul Git vă punem la dispoziție acest tutorial scurt despre funcționalitățile lui de baza.

În următoarele secțiuni vă vom explica anumite comenzi folosindu-ne de un repository demo.

  • Noțiunile legate de VSCode din tutorial pot fi foarte ușor aplicate si în IntelliJ, deoarece implementarea lor este foarte asemănătoare.
  • Dacă doriți să folosiți Git pe Windows vă recomandăm să descărcați acest software care vă pune la dispoziție un CLI de Linux.
  • GitHub este un site, Git este un tool.

Ca să înțelegeți mai ușor secțiunile următoare sau dacă doriți versiunea scurtă a acestui document, vă recomandăm să vă uitați la tutorialul video de mai sus care nu este mai lung de 12 minute.

Există un tutorial dedicat pentru integrarea tool-ului Git în IntelliJ și vi-l punem la dispoziție la acest link. Secțiunile următoare au rolul de a vă ajuta să înțelegeți mai bine cum funcționează Git-ul în realitate.

Crearea unui repository

Pentru a ne putea folosi de toate comenzile puse la dispozitie de tool-ul Git, trebuie mai întâi să creăm un repository care va reprezenta proiectul nostru.

După ce vă faceți cont pe GitHub puteți să apăsați butonul “Create a new repository” pentru a începe configurarea unui repo.

Conform pozei de mai sus, avem următoarele opțiuni:

  • Template: putem alege un template de repository dacă dorim sa respectăm un anumit blueprint (ex. să pornim direct cu un anumit README, .gitignore etc.).
  • Owner: se poate selecta proprietarul repository-ului care poate fi o organizație (ex. oop-pub) sau o persoană.
  • Repository name: numele repository-ului care va fi tradus fără spații (Shor Quantum Break → Shor-Quantum-Break)
  • Description: o scurtă descriere a repo-ului vostru.
  • Vizibilitate: public/privat, această opțiune o puteți modifica oricând.
  • README: puteți opta să aveți un README generat la crearea repo-ului.
  • .gitignore: puteți opta să aveți un fișier ”.gitignore” în care să specificați ce fișiere/foldere doriți să fie ignorate atunci când faceți un commit (ex. .idea, *.iml, *.jar, a.out etc.).
  • Licență: puteți să stabiliți clar care sunt drepturile legale pentru redistribuirea codului vostru folosind unul din template-urile valabile.

GitHub vă pune la dispoziție un template pentru .gitignore în funcție de limbajul în care doriți să lucrați.

Puteți să vă schimbați orice setare din secțiunea “settings” a repo-ului creat de voi (ex. puteți schimba vizibilitatea repo-ului).

După ce v-ați ales setările dorite, putem crea repository-ul care va arăta la început astfel:

În repo-ul creat avem următoarele secțiuni:

  • Code: codul propriu-zis scris de voi/echipa voastră.
  • Issues: puteți vedea și adăuga probleme pe care le are codul vostru, este util pentru a vedea ce erori trebuie adresate în următoarea versiune a soft-ului sau pentru a vedea statutul unei probleme.
  • Pull Requests: aici se află schimbările pe care le aveți pe anumite branch-uri și pe care doriți să le revizuiască echipa înainte de a le introduce pe branch-ul “main/master”.
  • Actions: aici puteți adăuga script-uri de testare și deployment (CI/CD pipelines).
  • Projects: puteți să vă organizați pull request-urile și problemele din cod mai bine prin folosirea unui spreadsheet.
  • Wiki: documentația proiectului vostru
  • Security: polițe de securitate, scanare de cod pentru vulnerabilități și alerte.
  • Insights: statistici legate de repo-ul vostru (numărul de commit-uri, trafic etc.)
  • Settings: setări avansate ale repository-ului.

Pe lângă aceste secțiuni avem și câteva etichete care ne arată metrici ale repo-ului:

  • Branch-ul curent
  • Numărul total de branch-uri
  • Numărul total de watchers*
  • Numărul total de fork-uri*
  • Numărul total de star-uri*

  • watch → un utilizator poate selecta opțiunea “watch” a unui repo pentru a primi notificări legate de schimbări.
  • fork → un utilizator poate selecta opțiunea “fork” pentru a face o copie a unui repo în care poate face schimbări fără să afecteze repo-ul original.
  • star → un utilizator poate selecta opțiunea “star” pentru a arăta aprecierea lui față de acel proiect (similar conceptului de “like”).

Clone/Init

După ce creați un repository pe GitHub-ul vostru personal va trebui să îl clonați și local pentru a putea face modificări în acesta. Se pot modifica fișiere și din interfața grafică provizionată de GitHub, dar aceste schimbări din GUI au o funcționalitate limitată, din acest motiv vă recomandăm să vă clonați repo-ul local în cazul în care vreți să faceți schimbări mai ample.

Pentru a clona un repo, folosim comanda “git clone adresa_https_repo [cale_repo]“.

student@student:~$ git clone https://github.com/username/Test-Git.git
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (5/5), 4.73 KiB | 4.73 MiB/s, done.

Există posibilitatea să vă faceți un repo direct din folder-ul din care lucrați folosind comanda “git init”.

student@student:~$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: 	git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: 	git branch -m <name>

  • Mesajul de output al comenzii “init” vă precizează că s-a inițializat folder-ul ”.git” și că s-a ales un branch default numit “master” pe care îl puteți redenumi voi conform comenzilor din output.
  • După ce vă clonați/inițializați repository-ul o să aveți un folder ascuns numit ”.git” care reține date despre repository și toate schimbările voastre locale.

Dacă alegeți să faceți un repository local folosind comanda “git init”, va fi nevoie să îl legați pe acesta la un repository din cloud pentru a putea avea proiectul vostru sincronizat și disponibil pe mai multe device-uri. Înainte să începeți procesul de legare, trebuie să vă creați un repository, urmând ca apoi să rulați comenzile de mai jos cu URL-ul repository-ului creat de voi.

student@student:~$ git remote add origin https://github.com/github_id/test_repo.git

Pentru a explica mai bine ce face comanda de mai sus o vom sparge în mai multe bucăți:

  1. git remote → dorim să lucrăm cu conexiuni la alte repository-uri
  2. add → dorim să adăugăm o conexiune externă
  3. origin → alias pentru un repository aflat în cloud
  4. URL → link-ul către repository-ul pe care dorim să îl urmărim

După ce am creat conexiunea către repository-ul nostru din cloud, când dorim să aducem modificările locale în cloud (acțiune asociată comenzii “git push”) va trebui să specificăm care este branch-ul nostru local main (sau master) și care este branch-ul repository-ului nostru main (sau master). După ce “conectăm” branch-urile noastre, putem rula direct comanda “git push” pentru a trimite modificările noastre și în cloud. Pentru mai multe detalii legate de comanda “git push” și despre “branch-uri” vă rugăm să citiți secțiunile de mai jos.

student@student:~$ git push --set-upstream origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 205 bytes | 205.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/luis6156/wow.git
 * [new branch]      master -> master
branch 'master' set up to track 'origin/master'.

Dacă folosiți comanda “init” va trebui să specificați neapărat URL-ul repository-ului vostru atunci când veți rula comanda “push”.

  • În cadrul temelor la POO noi o să verificăm existența folder-ului ”.git” atunci când încărcați arhiva pe VmChecker. Pentru a vă verifica commit-urile o să rulăm comanda git log.
  • Folder-ul ”.git” se crează automat atunci când rulați comanda “init”.
  • Dacă nu vreți să folosiți comanda “init”, puteți să vă creați un repository nou pe GitHub după care să vă incărcați scheletul pentru temă direct în repo folosind interfața grafică (selectați butonul “add files”). După ce ați încărcat scheletul puteți să vă clonați repository-ul pe device folosind comanda “clone”, urmând ca folder-ul ”.git” să fie generat automat.
  • Dacă ați ales să rulați comanda “init” în cadrul folderului unde există scheletul vă recomandăm să vă faceți și un repository privat pe care să îl legați la folder-ul vostru local ”.git” conform informațiilor de mai sus.

Commit/Add

Commit-urile reprezintă milestone-uri pe care le-au atins modificările făcute de voi. Într-un commit puteți avea mai multe schimbări adăugate cum ar fi redenumiri, ștergeri sau adăugări de fișiere.

Să presupunem că am creat o nouă clasă “Engine” pe care vrem să o avem valabilă și în regim remote.

class Engine {
   private int pistons;
   private String shape;
 
   public Engine(int pistons, String shape) {
      this.pistons = pistons;
      this.shape = shape; 
   }
 
   public void turnEngineOn() {
      System.out.println("Engine started.");
   }
 
   public void turnEngineOff() {
      System.out.println("Engine shut down.");
   }
}

Tot ce trebuie să facem este să selectăm fișierele care reprezintă milestone-ul nostru folosind comanda “git add nume_fișier” sau “git add .” dacă vrem să adăugăm recursiv toate fișierele de la calea curentă.

student@student:~$ git status
On branch main
Your branch is up to date with 'origin/main'.
 
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	Engine.java
 
nothing added to commit but untracked files present (use "git add" to track)
 
student@student:~$ git add Engine.java
student@student:~$ git status
On branch main
Your branch is up to date with 'origin/main'.
 
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   Engine.java

Folosim comanda “git status” ca să vedem schimbările curente din perspectiva tool-ului Git.

Acum avem fișierul selectat pentru commit-ul pe care vrem să îl creăm, deci vom rula comanda «git commit -m “mesaj”».

student@student:~$ git commit -m "added Engine class"
[main a339de3] added Engine class
 1 file changed, 17 insertions(+)
 create mode 100644 Engine.java
 
student@student:~$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)
 
nothing to commit, working tree clean

Mesajele pe care le folosiți pentru commit-uri trebuie să fie scurte și descriptive. Scopul lor este ca ceilalți membrii ai echipei voastre să înțeleagă pe scurt ce modificări ați făcut. Mesajele de tipul “commit 1” nu ajută deloc la depanarea problemelor.

Push/Pull

Toate modificările pe care le-ați făcut până acum au fost doar locale, dacă vrem ca aceste modificări să fie disponibile în Cloud trebuie să le “împingem” și pe remote folosind comanda “git push”.

student@student:~$ git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 10 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 496 bytes | 496.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/luis6156/Test-Git.git
   3499c09..a339de3  main -> main
 
student@student:~$ git status
On branch main
Your branch is up to date with 'origin/main'.
 
nothing to commit, working tree clean

Acum modificările noastre apar și pe GitHub.

Pentru ca ceilalți membrii ai echipei să poată vedea aceste modificări și local trebuie să “tragă” informațiile de pe remote folosind comanda “git pull”.

student@student:~$ git pull
Already up to date.

  • E bine să dați un “pull” cel puțin o dată pe zi ca să fiți siguri că aveți ultimele modificări.
  • Dacă nu puteți face push, cel mai probabil aveți modificări pe remote care nu sunt prezente și pe local, deci rulați mai întâi comanda “git pull”.

Restore

Dacă faceți o greșeală în momentul în care adăugați un fișier folosind comanda “git add nume_fișier” puteți folosi comanda “git restore --staged nume_fișier”.

Să presupunem că am adăugat din greșeală clasa “InlineEngine”, putem face restore astfel:

class InlineEngine extends Engine {
   private Boolean isInverted;
 
   public InlineEngine(int pistons, String shape, Boolean isInverted) {
      super(pistons, shape);
      this.isInverted = isInverted;
   }
 
   public Boolean getIsInverted() {
      return isInverted;
   }
}
student@student:~$ git add InlineEngine.java
student@student:~$ git status
On branch main
Your branch is up to date with 'origin/main'.
 
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   InlineEngine.java
 
student@student:~$ git restore --staged InlineEngine.java
student@student:~$ git status
On branch main
Your branch is up to date with 'origin/main'.
 
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	InlineEngine.java
 
nothing added to commit but untracked files present (use "git add" to track)

Mai există un caz în care putem folosi restore. Să presupunem că am făcut o schimbare în clasa “Engine”.

class Engine {
   private int pistons;
   private String shape;
 
   public Engine(int pistons, String shape) {
      this.pistons = pistons;
      this.shape = shape;
   }
 
   public void turnEngineOn() {
      System.out.println("Engine started.");
   }
 
   public void turnEngineOff() {
      System.out.println("Engine shut down.");
   }
 
   // NEW CODE ADDED HERE, PLS DELETE AFTER DEMO
   public Boolean helloBrother() {
      System.out.println("Hello brother, you may rest here.");
   }
}

Dacă vrem să revenim la ultima variantă a codului prezentă pe remote putem să folosim comanda “git restore nume_fișier”.

student@student:~$ git status
On branch main
Your branch is up to date with 'origin/main'.
 
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   Engine.java
 
no changes added to commit (use "git add" and/or "git commit -a")
 
student@student:~$ git restore Engine.java
student@student:~$ git status
On branch main
Your branch is up to date with 'origin/main'.
 
student@student:~$ cat Engine.java
class Engine {
   private int pistons;
   private String shape;
 
   public Engine(int pistons, String shape) {
      this.pistons = pistons;
      this.shape = shape;
   }
 
   public void turnEngineOn() {
      System.out.println("Engine started.");
   }
 
   public void turnEngineOff() {
      System.out.println("Engine shut down.");
   }
}

Puteți folosi comanda “git restore .” dacă doriți să restaurați toate fișierele recursiv din folder-ul curent.

Stash

Există situații în care vreți să reveniți la o formă precedentă a fișierului vostru, dar fără să vă pierdeți modificările curente. Pentru acest caz avem comanda “git stash” care ne permite sa reținem modificările noastre într-o stivă pe care o putem accesa oricând este nevoie.

Să presupunem că avem următoarele modificări în fișierul noastru și vrem să îl convertim la forma lui precedentă:

class Engine {
   private int pistons;
   private String shape;
 
   public Engine(int pistons, String shape) {
      this.pistons = pistons;
      this.shape = shape;
   }
 
   public void turnEngineOn() {
      System.out.println("Engine started.");
   }
 
   public void turnEngineOff() {
      System.out.println("Engine shut down.");
   }
 
   // NEW METHOD ADDED HERE
   public void makeSound() {
      System.out.println("Vroooooom vroom");
   }
}
student@student:~$ git status
On branch main
Your branch is up to date with 'origin/main'.
 
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   Engine.java
 
no changes added to commit (use "git add" and/or "git commit -a")
 
student@student:~$ git stash
Saved working directory and index state WIP on main: a339de3 added Engine class
 
student@student:~$ cat Engine.java
class Engine {
   private int pistons;
   private String shape;
 
   public Engine(int pistons, String shape) {
      this.pistons = pistons;
      this.shape = shape;
   }
 
   public void turnEngineOn() {
      System.out.println("Engine started.");
   }
 
   public void turnEngineOff() {
      System.out.println("Engine shut down.");
   }
}

Acum că avem fișierul la forma lui inițială, putem să readucem modificările în fișier folosind comanda “git stash pop”.

student@student:~$ git stash show
 Engine.java | 5 +++++
 1 file changed, 5 insertions(+)
(END)
 
student@student:~$ git stash pop
On branch main
Your branch is up to date with 'origin/main'.
 
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   Engine.java
 
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (93e0db694a99d8293a707b9c0021fc71b7d621a1)
 
student@student:~$ cat Engine.java
class Engine {
   private int pistons;
   private String shape;
 
   public Engine(int pistons, String shape) {
      this.pistons = pistons;
      this.shape = shape;
   }
 
   public void turnEngineOn() {
      System.out.println("Engine started.");
   }
 
   public void turnEngineOff() {
      System.out.println("Engine shut down.");
   }
 
   // NEW METHOD ADDED HERE
   public void makeSound() {
      System.out.println("Vroooooom vroom");
   }
}

Putem folosi comanda “git stash show” ca să vedem cum arată stiva noastră. Ca să ieșiți din view, apăsați tasta “q”.

Branch-uri

Branch-urile în Git încapsulează mai multe commit-uri, adică mai multe milestone-uri. Drept urmare, ele mai sunt denumite ca și snapshoot-uri. Într-un branch puteți să aveți încapsulat un singur feature sau bug fix, ceea ce vă permite să lucrați izolat la părți independente din proiect fără să afectați ramura principală, mai exact branch-ul “main”. În branch-ul default “main” e bine să aveți cod stabil si verificat de echipă, deoarece tot ce se află pe “main” este considerat “production ready”. În schimb, tot ce se află pe un branch diferit este considerat “experimental” sau “in development”.

Branch-urile pot fi create atât din GUI cât și din CLI.

Pentru a crea un branch din CLI avem comanda “git checkout -b nume_branch” care vă va și comuta pe acesta.

student@student:~$ git checkout -b "test_branch"
Switched to a new branch 'test_branch'
 
student@student:~$ git status
On branch test_branch
nothing to commit, working tree clean

Pentru a comuta pe un branch diferit putem folosi comanda “git switch nume_branch”.

student@student:~$ git branch
  main
* test_branch
(END)
 
student@student:~$ git switch main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
 
student@student:~$ git status
On branch main
Your branch is up to date with 'origin/main'.
 
nothing to commit, working tree clean

Puteți folosi comanda “git branch” pentru a vedea toate branch-urile locale, cel curent fiind marcat cu un “*” în față. De asemenea, puteți folosi comanda “git branch -r” pentru a vedea toate branch-urile remote.

Branch-urile pot fi de două tipuri: locale și remote. Cele locale sunt prezente doar pe mașina voastră, iar cele remote sunt prezente în cloud. Probabil ați observat că atunci când vă mutați pe branch-ul “main” aveți vă apare atât branch-ul “main” cât și branch-ul “origin/main”. Branch-urile prefixate de “origin/” reprezintă branch-uri remote. Ideal, un branch remote este sincronizat cu branch-ul local echivalent lui. Pentru comanda de mai sus noi am creat doar un branch local, dacă dorim ca branch-ul nostru să poată fi văzut și de ceilalți și să fie disponibil și în cloud, putem folosi comanda “git push –set-upstream origin nume_branch”.

student@student:~$ git status
On branch test_branch
nothing to commit, working tree clean
 
student@student:~$ git push
fatal: The current branch test_branch has no upstream branch.
To push the current branch and set the remote as upstream, use
 
    git push --set-upstream origin test_branch
 
To have this happen automatically for branches without a tracking
upstream, see 'push.autoSetupRemote' in 'git help config'.
 
student@student:~$ git push --set-upstream origin test_branch
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'test_branch' on GitHub by visiting:
remote:      https://github.com/luis6156/Test-Git/pull/new/test_branch
remote:
To https://github.com/luis6156/Test-Git.git
 * [new branch]      test_branch -> test_branch
branch 'test_branch' set up to track 'origin/test_branch'.
 
student@student:~$ git status
On branch test_branch
Your branch is up to date with 'origin/test_branch'.
 
nothing to commit, working tree clean

Acum branch-ul local “test_branch” este sincronizat cu branch-ul remote “origin/test_branch”. Putem verifica asta intrând pe GitHub.

Dacă folosiți metoda din CLI, nu puteți face push-uri pe remote până nu sincronizați branch-ul local cu cel remote conform pașilor de mai sus.

Pentru a crea un branch din GUI, mai exact din GitHub, putem să scriem direct numele branch-ului dorit și să selectăm “create branch”.

Ca să avem branch-ul remote prezent și local va trebui să “tragem” ultimele modificări folosind comanda “git pull”.

student@student:~$ git pull
From https://github.com/luis6156/Test-Git
 * [new branch]      test_branch_gui -> origin/test_branch_gui
Already up to date.
 
student@student:~$ git switch test_branch_gui
branch 'test_branch_gui' set up to track 'origin/test_branch_gui'.
Switched to a new branch 'test_branch_gui'
 
student@student:~$ git status
On branch test_branch_gui
Your branch is up to date with 'origin/test_branch_gui'.
 
nothing to commit, working tree clean

Dacă folosiți metoda din GUI, nu puteți să dați “switch” pe branch-ul creat remote până nu vă luați ultimele modificări conform pașilor de mai sus.

Branch-ul creat din GUI/CLI se va baza pe conținutul branch-ului curent. Exemplu: dacă ne aflăm pe branch-ul “test1” care are un fișier “TestClass.java” și creăm din GUI un branch nou “test2”, atunci și branch-ul “test2” va avea fișierul “TestClass.java”.

  • Dacă vrem să ștergem un branch local din CLI folosim comanda “git branch -d nume_branch”.
  • Dacă vrem să ștergem un branch local din CLI folosim comanda “git push origin –delete nume_branch”.

Pull requests

Un “pull request” este în esență o verificare înainte de a se face aduce commit-urile de pe un branch pe branch-ul “main”. Rolul acestuia este de a verifica corectitudinea codului scris înainte de a fi introdus pe branch-ul default, deoarece acesta trebuie să conțină cod cât mai bun.

Să presupunem că avem un branch “test” pe care avem un fișier “test_file.txt”.

Conform pozei de mai sus, observăm un prompt de creare a unui “pull request”. În momentul în care se fac modificări pe un branch Git ne va oferi acel prompt pentru a aduce modificările și pe “main”.

După ce apăsăm pe prompt, putem să creăm un titlu sau o descriere pentru PR sau să adăugăm “reviewers” sau “assignees”.

În final avem și un panou dedicat unde putem să schimbăm setări sau să dăm review sau să vedem exact commit-uri/schimbări.

  • Puteți face commit-uri chiar dacă ați făcut pull request, iar aceste commit-uri vor fi și ele prezente în pull request chiar dacă sunt mai noi.
  • E bine să faceți mereu un pull request și să cereți un reviewer din GUI înainte de a da merge pe “main”. Nu dați “merge” până nu primiți approve. Pentru siguranță puteți seta ca orice PR să nu fie merged fără cel puțin un review.
  • Dacă aveți rolul de reviewer puteți în urma unui review să dați “approve”, “comment” sau “request changes” din secțiunea “files changes → review changes”

Merge/Rebase

Comenzile “merge” și “rebase” sunt folosite pentru a aduce commit-urile de pe un branch sursă pe un branch destinație. Deși scopul este același, ele diferă în execuție. Pentru a înțelege diferența dintre aceste comenzi ne vom folosi de poza de mai sus, unde pe branch-ul “main” se află commit-urile “1”, “2” și “3”, iar pe branch-ul “feature” se află commit-urile “A” și “B”.

De asemenea, vom presupune că avem un branch “test_src” care conține fișierul “InlineEngine.java” pe care vrem să-l aducem pe branch-ul “test_dst”.

student@student:~$ git switch test_dst
branch 'test_dst' set up to track 'origin/test_dst'.
Switched to a new branch 'test_dst'

Merge

Comanda de “merge” ia commit-urile “A” și “B” și le combină într-un singur commit, mai exact commit-ul “4”, păstrând log-urile intacte.

Avantaje:

  • Log-urile sunt foarte exhaustive și pot ajuta programatorii să înțeleagă mai bine ce s-a întâmplat în cadrul fiecărui merge.
  • Este ușor să găsești greșeli și să le rezolvi.

Dezavantaje:

  • Rezultatele din log-uri sunt foarte îngrămădite.
  • Nu este foarte user-friendly.

Ca și exemplu, așa poate arăta un log plin de merge-uri:

Pentru a face “merge”, vom folosi comanda “git merge branch_src”.

student@student:~$ git merge test_src
Updating a339de3..843f191
Fast-forward
 InlineEngine.java | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 InlineEngine.java
 
student@student:~$ ls
Engine.java       InlineEngine.java LICENSE           README.md

Rebase

Comanda de “rebase” ia commit-urile “A” și “B” și le adaugă liniar la branch-ul “main”, mai exact se crează commit-urile “4” și “5”, alterând log-urile. Rebase a fost creat pentru a combate limitările comenzii “merge”, mai exact pentru a crea lizibilitate în log-uri.

Avantaje:

  • Log-urile sunt liniare.
  • Este mai ușor să te miști prin proiect.

Dezavantaje:

  • Nu putem vedea unde și cum au fost făcute commit-urile când acestea au fost aduse pe branch-ul destinație.

Pentru a face “rebase”, vom folosi comanda “git rebase branch_dst”.

student@student:~$ git rebase test_src
Successfully rebased and updated refs/heads/test_src.
 
student@student:~$ ls
Engine.java       InlineEngine.java LICENSE           README.md

Când folosim merge/rebase

Ca să înțelegem când să folosim merge/rebase este important să observăm că un developer nu poate vedea în urma unui rebase de unde vin commit-urile “A” și “B”. Deci putem să tragem concluzia că este mai bine să folosim “merge” când log-urile sunt importante și când alți developeri au acces la branch-ul sursă, în schimb folosim “rebase” când log-urile nu sunt importante și când alți developeri nu au acces la branch-ul sursă.

Conflicte

Atunci când dorim să facem o operație de “merge” sau “rebase” putem să avem conflicte între branch-ul sursă și branch-ul destinație. Acest conflict poate apărea în situația în care noi modificăm un fișier pe un branch și un coleg modifică diferit același fișier pe alt branch și apoi dorim să îmbinăm cele două branch-uri.

Pentru a ilustra și rezolva această situație o să presupunem că avem branch-urile “test_src” și “test_dst” în care modificăm fișierul “InlineEngine.java”.

În branch-ul “test_dst” avem:

class InlineEngine extends Engine {
   private Boolean isInverted;
 
   public InlineEngine(int pistons, String shape, Boolean isInverted) {
      super(pistons, shape);
      this.isInverted = isInverted;
   }
 
   public Boolean getIsInverted() {
      return isInverted;
   }
 
   // METODA ADAUGATA IN AMBELE BRANCH-URI CU MICI DIFERENTE
   public void methodFromDST() {
      System.out.println("BarFoo");
   }
}

În branch-ul “test_src” avem:

class InlineEngine extends Engine {
   private Boolean isInverted;
 
   public InlineEngine(int pistons, String shape, Boolean isInverted) {
      super(pistons, shape);
      this.isInverted = isInverted;
   }
 
   public Boolean getIsInverted() {
      return isInverted;
   }
 
   // METODA ADAUGATA IN AMBELE BRANCH-URI CU MICI DIFERENTE
   public void methodFromSRC() {
      System.out.println("FooBar");
   }
}

Acum dacă încercăm să facem merge în “test_dst”:

student@student:~$ git status
On branch test_dst
Your branch is up to date with 'origin/test_dst'.
 
nothing to commit, working tree clean
 
student@student:~$ git merge test_src
Auto-merging InlineEngine.java
CONFLICT (content): Merge conflict in InlineEngine.java
Automatic merge failed; fix conflicts and then commit the result.
 
student@student:~$ git status
On branch test_dst
Your branch is up to date with 'origin/test_dst'.
 
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)
 
Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   InlineEngine.java
 
no changes added to commit (use "git add" and/or "git commit -a")

Git ne avertizează că au fost făcute modificări pe ambele branch-uri în același fișier și nu știe ce modificări să păstreze în urma operației de “merge”. Ca să rezolvăm această problemă trebuie să intrăm în fișierul problematic folosind orice editor text/IDE pentru a alege schimbările corecte.

student@student:~$ vim InlineEngine.java
class InlineEngine extends Engine {
   private Boolean isInverted;
 
   public InlineEngine(int pistons, String shape, Boolean isInverted) {
      super(pistons, shape);
      this.isInverted = isInverted;
   }
 
   public Boolean getIsInverted() {
      return isInverted;
   }
 
   // METODA ADAUGATA IN AMBELE BRANCH-URI CU MICI DIFERENTE
<<<<<<< HEAD
   public void methodFromDST() {
      System.out.println("BarFoo");
=======
   public void methodFromSRC() {
      System.out.println("FooBar");
>>>>>>> test_src
   }
}

Pentru a marca diferitele schimbări de pe ambele branch-uri “Git” a introdus niște delimitatori. De asemenea, acolo unde începe secțiunea “HEAD” avem textul care era deja prezent pe branch-ul curent (în acest caz “methodFromDST()”). Pentru a selecta segmentul corect, tot ce trebuie să facem este să ștergem delimitatorii și bucățile de cod care nu ne interesează.

class InlineEngine extends Engine {
   private Boolean isInverted;
 
   public InlineEngine(int pistons, String shape, Boolean isInverted) {
      super(pistons, shape);
      this.isInverted = isInverted;
   }
 
   public Boolean getIsInverted() {
      return isInverted;
   }
 
   // METODA ADAUGATA IN AMBELE BRANCH-URI CU MICI DIFERENTE
   public void methodFromSRC() {
      System.out.println("FooBar");
   }
}
:wq
 
student@student:~$ git add InlineEngine.java
student@student:~$ git commit -m "merged InlineEngine file"
[test_dst ab22900] merged InlineEngine file
 
student@student:~$ git push
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 214 bytes | 214.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/luis6156/Test-Git.git
   008128f..ab22900  test_dst -> test_dst

Acum avem modificările corecte în urma merge-ului atât pe local, cât și pe remote.

Referințe

poo-ca-cd/resurse-utile/tutorial-git.txt · Last modified: 2023/11/12 17:55 by florian_luis.micu
CC Attribution-Share Alike 3.0 Unported
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