Laborator 04 - Dezvoltarea aplicațiilor

Introducere

Toate dispozitivele electronice pe care le folosim în viața de zi cu zi, de la latopuri sau calculatoare personale, până la telefoane mobile sau smart watch-uri, au în comun un lucru: rulează software. Având în vedere cât de variate sunt sistemele pe care rulează aplicațiile, trebuie să avem mereu în minte sistemul pentru care creăm o aplicație. De exemplu, dacă aplicația noastră este gândită pentru un ceas inteligent, atunci trebuie să ținem în minte că resursele vor fi limitate; apare astfel o constrângere din punctul de vedere al memoriei disponibile.

Astfel, trebuie să ținem cont de următoarele:

  • ce limbaj de programare folosim
  • cum scriem și compilăm codul sursă
  • cum depanăm codul sursă
  • cum versionăm codul sursă

Vom vorbi despre toate aceste aspecte în acest capitol.

Alegerea limbajului de programare

Există trei categorii de limbaje de programare, grupate în funcție de modul în care codul sursă scris de noi (de limbaj înalt) ajunge să fie rulat pe procesor: compilate, interpretate și hibride. Alegem limbajul de programare potrivit pentru dezvoltarea programului nostru în funcție de ce avem nevoie: să avem un program portabil sau să avem un program rapid sau să avem acces mai ușor la memorie.

În această carte vom folosi limbajul C ca să scriem codul sursă și compilatorul gcc pentru a compila codul sursă, însă mai jos descriem particularitățile fiecărui tip de limbaj de programare.

Limbaje de programare compilate

Programele scrise într-un limbaj compilat sunt analizate de către un program numit compilator înainte ca ele să poată fi rulate pe sistem. În general, limbajele compilate sunt mai rapide în execuție decât cele interpretate sau hibride, deoarece problemele de sintaxă (de scriere) sunt eliminate din faza compilării, înainte să rulăm programul. Neajunsul programelor compilate este faptul că programele NU sunt portabile, adică nu putem lua un program compilat pe calculator să îl rulăm pe un smartwatch; trebuie să recompilăm codul sursă.

Exemple de limbaje compilate sunt C, C++, Fortran, Rust, Go, D.

Limbaje de programare interpretate

Atunci când scriem un program într-un limbaj interpretat, programul este trecut printr-un alt program, numit interpretor care analizează și rulează fiecare linie de cod scrisă, pe rând. Dacă alegem să scriem un program într-un limbaj de programare interpretat, atunci programul nostru este portabil, adică putem să îl rulăm pe orice sistem pe care avem interpretorul instalat. Din cauza faptului că programele sunt analizate și rulate linie cu linie, execuția lor poate fi mai lentă.

Exemple de limbaje interpretate sunt PHP și Perl.

Limbaje de programare hibride

Programele scrise într-un limbaj hibrid trec print-un proces care îmbină etapa de compilare și etapa de interpretare, scopul fiind de a avea în final un program portabil al cărui timp de execuție este mai redus decât în cazul programelor interpretate.

Exemple de limbare hibride sunt Python, Java și C#.

Scrierea codului sursă

Atunci când spunem că dezvoltăm o aplicație, spunem, de fapt, că scriem codul sursă, îl compilăm, îl verificăm de erori. Pentru scrierea codului sursă putem să alegem editoare de text sau medii de dezvoltare integrate (Integrated Development Environment, IDE).

Editoarele de text sunt programe mai simple în care putem edita fișiere text, deci putem dezvolta programe. Ele pot permite instalarea de extensii care aduc funcționalități în plus, specifice pentru un anumit limbaj. Printre cele mai cunoscute editoare de text se numără GNU Nano, Vim, Sublime, Atom, Visual Studio Code.

IDE-urile au anumite funcționalități avansate, multe dintre ele fiind adaptate unui singur limbaj de programare. În plus, ele au integrat un compilator/interpretor pentru limbajul suportat. Astfel, la o simplă apăsare de buton programul este rulat. Printre IDE-uri se număra: Microsoft Visual Studio, Eclipse, IntelliJ, XCode.

În această carte vom folosi GNU Nano ca editor de text principal. Este un editor CLI și se pornește folosind comanda nano, așa cum a fost prezentat în capitolul Lucrul cu fișiere.

Compilarea unui fișier cod sursă C

În această secțiune urmărim să învățăm pașii pentru a compila un program, adică a-l aduce de la cod sursă la executabil. Pentru aceasta vom crea un program care verifică dacă un număr citit de la tastatură este prim sau nu. Vom scrie într-un nou fișier programul care implementează algoritmul, vom compila codul sursă folosind compilatorul GCC și vom testa că programul funcționează.

Crearea unui fișier cod sursă

Creăm un fișier nou cu numele is-prime.c cu implementarea algoritmului de verificare. Copiem codul sursă de mai jos și îl lipim în nano ca în imaginea de mai jos:

#include <stdio.h>
 
int check_if_prime(int n)
{
    int i;
 
    for (i = 2; i <= n / 2; i++) {
        if (n % i == 0) {
            return 0;
        }
    }
 
    return 1;
}
 
int main(void)
{
    int n;
 
    printf("Please gimme a number: ");
    scanf("%d", &n);
 
    if (check_if_prime(n)) {
        printf("%d is prime\n", n);
    } else {
        printf("%d is not prime\n", n);
    }
 
    return 0;
}

Mai multe detalii despre folosirea editorului de text nano găsim în capitolul Lucrul cu fișiere.

Compilarea codului sursă în executabilul a.out

Avem fișierul cod sursă is-prime.c și vrem să obținem un program pe care să-l rulăm pe sistemul nostru. Pentru aceasta, trebuie să compilăm fișierul is-prime.c. Acest program este de fapt un executabil (binar). Executabilele sunt fișiere care conțin instrucțiuni pe care sistemul de calcul le poate interpreta și rula.

Creăm un executabil din fișierul is-prime.c folosind comanda gcc:

student@uso:~$ gcc is-prime.c 
student@uso:~$ ls -l
total 16
-rwxr-xr-x 1 student student 8448 Oct 26 06:34 a.out
-rw-r--r-- 1 student student  406 Oct 26 06:17 is-prime.c

Așa cum vedem în rezultatul rulări comenzii ls -l de mai sus, executabilul se numește a.out. Acesta este numele implicit dat de GCC. Adică a.out va fi numele tuturor fișierelor executabile generate cu GCC, indiferent de fișierul cod sursă. Verificăm că fișierul a.out este într-adevăr un fișier executabil:

student@uso:~$ file a.out 
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=14553360a84b6dbe4dba5f287a665047572bde7f, not stripped

Acronimele ELF (Executable and Linkable Format) șiLSB* (Linux Standard Base) nu sunt relevante în această carte, dar reținem că atunci când vedem un fișier ELF, acesta este un fișier executabil.

Rulăm executabilul a.out în felul următor și introducem de la tastatură un număr:

student@uso:~$ ./a.out
Please gimme a number: 13
13 is prime

Programul funcționează: citește un număr de la tastatură și afișează dacă acesta este prim sau nu.

Compilarea codului sursă într-un executabil cu nume diferit

Numele a.out este implicit, deci toate programele compilate cu GCC se vor numi a.out. Putem configura un nume diferit pentru executabilul obținut.

Spre exemplu, pentru programul care verifică dacă un număr este prim sau nu, numim executabilul is-prime. Creăm un executabil cu numele is-prime din fișierul is-prime.c folosind opțiunea -o a comenzii gcc:

student@uso:~$ gcc -o is-prime is-prime.c 
student@uso:~$ ls -l
total 28
-rwxr-xr-x 1 student student 8448 Oct 26 06:34 a.out
-rwxr-xr-x 1 student student 8448 Oct 26 06:57 is-prime
-rw-r--r-- 1 student student  406 Oct 26 06:17 is-prime.c
student@uso:~$ file is-prime
is-prime: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=14553360a84b6dbe4dba5f287a665047572bde7f, not stripped

Opțiunea -o urmată de numele ales pentru program (is-prime) transmite compilatorului ca programul să se numească is-prime, și nu a.out. Rulăm executabilul is-prime în felul următor și introducem de la tastatură un număr:

student@uso:~$ ./is-prime
Please gimme a number: 13
13 is prime

Vedem că, deși au nume diferit, programele a.out și is-prime au același comportament. Acest lucru este normal deoarece ele sunt 2 fișiere executabile obținute din același fișier cod sursă obținute folosind același compilator: GCC.

Exerciții

  1. Creați un fișier cu numele is-palindrome.c care să conțină următorul conținut:
#include <stdio.h>
 
int check_if_palindrome(int n)
{
    int new_n = 0;
    int old_n = n;
 
    while (n > 0) {
        int r = n % 10;
        n /= 10;
        new_n = new_n * 10 + r;
    }
 
    return (new_n == old_n) ? 1 : 0;
}
 
int main(void)
{
    int n;
 
    printf("Please gimme a number: ");
    scanf("%d", &n);
 
    if (check_if_palindrome(n)) {
        printf("%d is a palindome\n", n);
    } else {
        printf("%d is not a palindrome\n", n);
    }
 
    return 0;
}
  1. Compilați fișierul is-palindrome.c într-un executabil cu numele a.out folosind gcc. Verificați funcționalitatea programului.
  2. Compilați fișierul is-palindrome.c într-un executabil cu numele is-palindrome folosind gcc. Verificați funcționalitatea programului.

Introducere în utilitarul Make și fișiere Makefile

În secțiunile anterioare, am compilat fișiere cod sursă C folosind compilatorul GCC. Dezvoltarea unui program este un proces continuu, nu scriem tot codul dintr-o singură iterație și de multe ori ajungem să îl modificăm pe parcurs. Vrem să testăm schimbările aduse în program. Pentru aceasta trebuie să recompilăm fișierul pe care l-am modificat și să creăm un nou executabil.

Automatizarea procesului de compilare ne ajută să fim eficienți atunci când dezvoltăm un proiect. În loc să dăm de fiecare dată toate comenzile pentru recompilarea fișierelor, putem să dăm o singură comandă care să le facă pe toate. Acest proces se numește build automation. Există mai multe soluții de build automation1). În această carte vom folosi utilitarul Make împreună cu fișiere Makefile ca să automatizăm procesul de compilare.

În secțiunile următoare vom folosi vedea cum funcționează utilitarul Make și cum arată un fișier Makefile.

Folosirea unui Makefile existent

Înainte să treceți mai departe, intrați în directorul ~/uso-lab și rulați comanda git pull.

În această secțiune vom compila programul Hangman folosind un fișier Makefile.

Întrăm în directorul ~/uso-lab/labs/04-appdev/support/simple-make folosind comanda cd:

student@uso:~$ cd ~/uso-lab/labs/04-appdev/support/simple-make
student@uso:~/uso-lab/labs/04-appdev/support/simple-make$ ls
hangman.c  Makefile

Avem în director un fișier cod sursă C, hangman.c, și un fișier Makefile. Ca să compilăm programul, folosim comanda make:

student@uso:~/uso-lab/labs/04-appdev/support/simple-make$ make
gcc -o hangman hangman.c
student@uso:~/uso-lab/labs/04-appdev/support/simple-make$ ls
hangman  hangman.c  Makefile

Comanda make a rulat, de fapt, comanda gcc -o hangman hangman.c, comandă prin care am creat fișierul executabil hangman.

Practic, scriind doar comanda make, am trecut fișierul hangman.c prin toate etapele compilării și am obținut executabilul final, așa cum am făcut în secțiunea Compilarea unui fișier cod sursă C pentru fișierul is-prime.c.

Rulăm executabilul hangman ca să vedem că funcționează, ca în imaginea de mai jos:

Înțelegerea formatului Makefile

În secțiunea anterioară, Folosirea unui Makefile existent, am folosit fișierul Makefile ca să compilăm programul Hangman. Ca să putem crea un Makefile pentru un proiect al nostru, trebuie să înțelegem formatul fișierului Makefile. În această secțiune vom folosi fișierul Makefile pe care l-am folosit anterior.

Fișierul Makefile folosit la programul Hangman are următorul conținut:

all: hangman

hangman: hangman.c
    gcc -o hangman hangman.c

clean:
    rm -rf hangman

Liniile din fișier sunt de două tipuri:

  1. Regulă, care are formatul regulă: <dependență> (all: hangman sau clean:).
  2. Comandă, care începe cu un Tab la începutul rândului, urmat de o comandă (gcc -o hangman hangman.c).

O regulă din fișierul Makefile este, de fapt, un nume asociat unei comenzi. Spunem că rulăm regula clean atunci când vrem să executăm comanda rm -rf hangman. În terminal, facem acest lucru folosind comanda make urmată de numele regulii, în acest caz make clean:

student@uso:~/uso-lab/labs/04-appdev/support/simple-make$ make clean
rm *.o hangman
student@uso:~/uso-lab/labs/04-appdev/support/simple-make$ ls
hangman.c   Makefile

Introducere în Git și GitHub

Vor fi cazuri când vom strica o versiune a codului și vom avea nevoie să revenim la o versiune corectă, caz în care un istoric de versiuni ne-ar fi de folos. Vom putea să lucrăm la un proiect de pe un alt sistem, în afară de al nostru, sau vom vrea să cerem feedback pe codul scris de noi.

Când vorbim despre un proiect software vrem să avem dezvoltatori, oameni care să lucreze împreună cu noi la proiect. Dezvoltatorii au nevoie de acces la codul sursă al proiectului software la care lucrăm. După ce le dăm acces, vrem ca fiecare dezvoltator să știe la ce a lucrat și la ce lucrează ceilalți; ca să nu se suprapună, ca să ajute și ca să ofere feedback.

Pentru a putea rezolva problemele de sincronizare între doi sau mai mulți colegi de echipă care lucrează la același proiect, ne ajută să avem un sistem de versionare a codului, adică să avem un istoric de modificări. Fiecare modificare înseamnă o nouă versiune a proiectului; avem astfel o listă de versiuni gestionată de sistemul de versionare a codului. Pe lângă rezolvarea problemelor de sincronizare, versionarea codului aduce și alte avantaje cum ar fi revenirea la o versiune mai veche a proiectului, găsirea rapidă a autorului unei secvențe de cod sau, pur și simplu, organizarea unui proiect.

Git este un sistem de management și versionare a codului sursă care permite lucrul eficient la un proiect software.

GitHub este o platformă online, bazată pe Git, pe care dezvoltatorii o pot folosi pentru a stoca și versiona codul lor sursă. Git este utilitarul folosit, iar GitHub este serverul și aplicația web pe care rulează acesta, locul în care păstrăm repository-ul remote.

Similar cu GitHub există și alte platforme precum Bitbucket sau GitLab. Comenzile pe care le vom studia se aplică pentru toate platformele care folosesc Git, doar interfața grafică diferă.

În această carte vom folosi GitHub ca suport. În mare parte, acesta nu diferă foarte mult de alte platforme.

Crearea unui cont pe GitHub (dacă nu aveți deja)

Înainte de toate, ne asigurăm că avem cont pe GitHub. Dacă aveți deja un cont pe GitHub, puteți trece la subsecțiunea următoare: Pregătirea inițială a mediului Git.

Dacă nu aveți cont, intrați pe GitHub. Pagina de pornire va arăta similar cu cea din imaginea de mai jos.

Introduceți un nume de utilizator (username), adresa voastră de e-mail și o parolă sigură pentru cont. Pentru validarea contului, accesați-vă căsuța de e-mail. Acolo veți găsi un e-mail în care vi se explică cum se poate valida noul cont creat. Verificați și căsuța spam în caz că nu ați primit nimic în inbox.

GitHub Student Pack

GitHub oferă studenților numeroase beneficii care în mod normal sunt contra cost (plătite). Găsiți mai multe detalii pe site-ul oficial.

Pregătirea inițială a mediului Git

Ca să utilizăm Git, facem în primă fază niște pași de configurare. Adică vom configura numele și e-mail-ul nostru, ca mai jos:

student@uso:~$ git config --global user.name "Prenume Nume"
student@uso:~$ git config --global user.email "adresa_de_email@example.com"

În listingul de mai sus “Prenume Nume” și ”adresa_de_email@example.com” sunt placeholdere. Le înlocuiți cu datele voastre. De exemplu, pentru autorul acestei secțiuni, comenzile rulate sunt:

student@uso:~$ git config --global user.name "Liza Babu"
student@uso:~$ git config --global user.email "lizababu@example.com"

Crearea primului repository

Pentru a lucra la un proiect software, creăm un repository software. Vom crea unul pe GitHub, unul local, după care le vom interconecta.

Repository software

Proiectul este stocat într-un repository software. Repository-ul conține fișierele proiectului: codul sursă, fișiere de configurare. De obicei acesta vine însoțit și de un fișier README.md în care se găsesc informații despre proiect: care este scopul proiectului, cum se compilează, pe ce platforme rulează.

Repository-urile sunt de două tipuri: locale și remote. Acestea pot fi interconectate și să refere de fapt același proiect. Repository-ul local este cel pe care îl avem la noi pe calculator, pe când cel remote este unul stocat pe un server (în cazul nostru GitHub). Este doar o diferență de perspectivă între cele două, ele nu diferă din punct de vedere tehnic. De obicei, într-un proiect Git / GitHub există un repository central (remote) și mai multe repository-uri secundare (locale), câte unul pentru fiecare dezvoltator din echipa proiectului.

Printre cele mai importante operații cu un repository sunt: init, fork, clone. Vom detalia aceste operații când le vom folosi în acest capitol.

Crearea unui repository gol pe GitHub

Ne autentificăm pe GitHub. Urmărim pașii prezentați în imaginea de mai jos și explicați imediat după.

  1. Apăsăm pe săgeată din meniul din dreapta sus și vedem ceva similar cu imaginea de mai sus.
  2. Apăsăm pe Your profile pentru a merge pe profilul nostru. Aici este locul în care vom putea vedea contribuțiile noastre pe GitHub. În partea de sus a ecranului vom vedea un meniu orizonatal care conține 4 opțiuni: Overview, Repositories, Projects și Packages.
  3. Apăsăm pe Repositories. Acum vom vedea întreaga listă de repository-uri pe care le avem. Pentru a crea unul nou, apăsăm pe butonul verde din dreapta sus pe care scrie New.
  4. Acum este momentul în care vom da un nume proiectului nostru, o descriere succintă a acestuia și vom putea decide dacă să fie public (vizibil tuturor utilizatorilor) sau privat (vizibil doar pentru noi și eventualii colaboratori ai proiectului). Ne va apărea un formular similar cu cel din imaginea de mai sus. Pentru acest tutorial vom crea un repository public. Este indicat ca numele repository-ului să descrie bine proiectul, în cazul nostru array-sorting-algorithms. Descrierea proiectului este opțională, dar e recomandat să o adăugăm pentru a fi ușor de înțeles pentru cei care vor ajunge la proiectul nostru.
  5. Apăsăm pe Create repository. Vor apărea câteva instrucțiuni pentru crearea unui repository local nou și conectarea celui nou cu cel remote. Acest lucru este acoperit în secțiunile următoare.

Acum avem un repository creat remote, pe GitHub, numit array-sorting-algorithms.

Crearea unui repository gol local

Creăm un director din ierarhia de fișiere în care vom inițializa repository-ul Git.

În acest tutorial creăm directorul array-sorting-algorithms în directorul home (adică /home/student sau ~), folosind comenzile de mai jos:

student@uso:~$ pwd
/home/student
student@uso:~$ mkdir array-sorting-algorithms
student@uso:~$ cd array-sorting-algorithms
student@uso:~/array-sorting-algorithms$ git init
Initialized empty Git repository in /home/student/array-sorting-algorithms/.git/
student@uso:~/array-sorting-algorithms$ ls -a
./    ../   .git/

Mai sus am inițializat repository-ul local prin comanda git init, dată în directorul ales (array-sorting-algorithms) din directorul home al utilizatorului student (/home/student).

Acum avem un repository creat local, numit array-sorting-algorithms.

Init

Operația init este una locală și are rolul de a inițializa un repository gol, local. Inițializarea repository-ului local înseamnă crearea, în directorul ales, a mediului pentru a putea lucra la un proiect software versionat Git. Această operare duce la crearea unui director numit .git în care se vor ține ulterior date suplimentare despre repository, numite metadatele repository-ului.

Am folosit opțiunea -a (ls -a) pentru a afișa și fișierele și directoarele ascunse. Directorul .git este un director ascuns.

Conectarea celor două repository-uri

Am creat până în acest moment un repository local și unul remote. Trebuie să le interconectăm pentru a lucra cu ele.

În cazul în care suntem mai mulți membri în echipă, fiecare membru va conecta repository-ul său local, la repository-ul remote. Pentru conectarea celor două repository-uri folosim comanda de mai jos, dată în directorul unde este repository-ul local Git (în cazul nostru /home/student/array-sorting-algorithms):

student@uso:~/array-sorting-algorithms$ git remote add origin https://github.com/{username}/array-sorting-algorithms.git

În comanda de mai sus {username} este numele utilizatorului nostru de pe GitHub. De exemplu, pentru autorul acestui capitol, {username} se înlocuiește cu lizababu.

Conectarea celor două repository-uri înseamnă setarea repository-ului origin, adică repository-ului remote la care se conectează cel local.

Imaginea de mai jos arată cum arată cum se contectează repository-urile remote și local. Sincronizarea lor se face prin intermediul operațiilor push și pull care sunt prezentate pe parcursul secțiunii app_dev_first_commits.

Primele commituri

Odată creat repository-ul, putem să începem să lucrăm la proiect.

Lucrul la proiect înseamnă să adăugăm și să ștergem fișiere sau să modificăm fișiere existente. De obicei este vorba de fișiere text (human-readable), cel mai des fișiere cod sursă2). Vrem să salvăm aceste adăugări și modificări; apoi să salvăm din nou alte modificări; și tot așa.

Salvarea acestor modificări înseamnă crearea unui commit în repository.

Același lucru îl fac și ceilalți colegi care lucrează la același proiect. Fiecare commit împachetează un set de adăugări și modificări realizate de un dezvoltator al proiectului. Având commiturile în repository putem să gestionăm mai ușor proiectul, adică:

  • să revenim la un commit anterior (adesea chiar ultimul) dacă modificările cele mai recente “strică” proiectul
  • să vedem cine este autorul anumitor modificări
  • să creăm o ramură de dezvoltare separată pornind de la un commit anterior, pe care să încercăm o funcționalitate nouă, fără a afecta restul proiectului

Git se ocupă de păstrarea și gestiunea istoricului repository-ului nostru prin păstrarea listei de commituri făcute. Adică Git păstrează un istoric de versiuni al proiectului.

Când facem un commit, acesta va fi reținut în repository-ul Git local, nu și în repository-ul Git remote.

Fără a actualiza și repository-ul remote, ceilalți colegi nu vor putea vedea schimbările făcute de noi. Vrem, așadar, ca modificările făcute local să se găsească și remote. Adică să publicăm commiturile din repository-ul local în repository-ul remote. Realizăm publicarea prin operația push.

Vom vedea în următoarele secțiuni care sunt pașii pentru a crea un commit și pentru a-l publica.

În următoarele secțiuni vom lucra în repository-ul array-sorting-algorithms creat în secțiunea Introducere în Git și GitHub. Vom crea, pas cu pas, un proiect software scris în limbajul de programare C, care conține mai mulți algoritmi de sortare a unui vector de elemente întregi.

Punctual, în această secțiune, vom crea fișierul README al proiectului și scheletul de cod pentru algoritmii de sortare Bubble Sort, Merge Sort și Radix Sort. Vom crea commituri în repository-ul local pentru fiecare schimbare, după care vom publica commiturile astfel încât schimbările să fie vizibile și pe GitHub, în repository-ul remote.

Adăugarea unui fișier README

Reamintire

O bună practică, prezentă în majoritatea proiectelor software, este să adăugăm un fișier README în care se află informații despre un proiect. Spre exemplu, în README se află informații despre ce funcționalități are proiectul nostru, cum se compilează un proiect, cum se rulează, pe ce tip de platforme poate fi rulat etc.

Un fișier README este un fișier text. Îl putem crea și îi putem adăuga titlul Sorting Algorithms for Beginners folosind un editor sau, mai simplu și mai direct, folosind comanda de mai jos:

student@uso:~/array-sorting-algorithms$ echo "# Sorting Algorithms for Beginners" > README.md
student@uso:~/array-sorting-algorithms$ ls -a
./         ../        .git/      README.md

Caracterul # din fața textului Sorting Algorithms for Beginners are rol de a formata textul sub formă de titlu. Nu intrăm în mai multe detalii aici pentru că nu face obiectul cărții.

Folosim extensia .md care semnalează un fișier de tip Markdown. Facem acest lucru deoarece pe GitHub fișierele README sunt afișate în format Markdown. Acest format este simplu de înțeles, însă nu face obiectul acestei cărți, deci nu vom insista pe înțelegerea lui.

Crearea primului commit

Acum avem în repository un nou fișier: README.md. Vrem să reținem acest lucru în repository prin crearea unui commit, primul.

Pașii creării unui commit sunt următorii:

  1. Verificăm repository-ului. Cu alte cuvinte, verificăm ce modificări au fost făcute în repository de la ultimul commit.
  2. Adăugăm fișierele pe care vrem să le împachetăm într-un commit în staging area, adică în lista de fișiere pe care Git le organizează.
  3. Alegem un mesaj de commit. Creăm commitul.
  4. Publicăm commitul și pe repository-ul remote.

Detaliem acești pași în continuare.

Verificarea stării repository-ului local

Prin starea repository-ului înțelegem forma la care am adus proiectul prin modificările noastre. Aceasta include ce fișiere am creat, modificat sau șters de la ultimul commit. Ne interesează întotdeauna starea repository-ului pe care lucrăm.

Pentru a verifica starea repository-ului folosim comanda git status:

student@uso:~/array-sorting-algorithms$ git status
On branch master
 
No commits yet
 
Untracked files:
(use "git add <file>..." to include in what will be committed)
 
    README.md

Prima linie afișată On branch master se referă la branch-ul master local. Vom discuta în secțiunea Lucrul pe branch-uri despre branch-uri.

A doua linie afișată No commits yet ne spune că nu am făcut până acum niciun commit, adică am pornit de la un repository gol.

În ultima parte a outputului se află o listă de fișiere untracked, adică lista fișierelor pe care Git le vede ca nou adăugate în repository-ul curent, dar pe care nu le monitorizează încă. Acest lucru înseamnă că, deocamdată, orice modificare vom face asupra acestor fișiere nu va fi urmărită (tracked) de Git. În cazul nostru, fișierul aflat în starea untracked este README.md.

Adăugarea unui fișier (în staging area)

Un commit va conține o listă de modificări: fișiere adăugate, fișiere șterse, conținut modificat. Un pas intermediar în crearea unui commit este pregătirea modificărilor ce vor fi parte din commit. Acest pas de pregătire înseamnă să adăugăm (add) aceste modificări într-o zonă de lucru pentru Git, numită staging area.

În cazul nostru, vrem să adăugăm fișierul README.md în staging area. Facem acest lucru folosind comanda git add:

student@uso:~/array-sorting-algorithms$ git add README.md

Verificăm ce s-a schimbat în urma adăugării fișierului README.md în staging folosind comanda git status:

student@uso:~/array-sorting-algorithms$ git status
On branch master
 
No commits yet
 
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
 
    new file:   README.md

Primele 2 mesaje afișate au rămas neschimbate. Partea interesantă apare la ultima parte a outputului. Vedem că mesajul a devenit Changes to be commited. Acest lucru înseamnă că acum Git urmărește noile modificări și așteaptă ca modificările să fie adunate într-un commit.

Crearea commitului local

Acum vrem ca modificările de mai sus să ajungă în repository. Pentru aceasta creăm un commit folosind comanda git commit:

student@uso:~/array-sorting-algorithms$ git commit -m "Add README file"
[master (root-commit) b2a590a] Add README.md
1 file changed, 1 insertion(+)
create mode 100644 README.md  
student@uso:~/array-sorting-algorithms$ git status
On branch master
nothing to commit, working tree clean  

Am folosit descrierea Add README file la comanda git commit drept mesaj de commit3). Aceasta este o descriere succintă a modificărilor făcute prin acest commit.

Crearea unui nou commit

În continuare vom adăuga scheletul de cod pentru algoritmul Bubble Sort în repository. Vom crea un nou fișier cod sursă C bubble-sort.c și vom scrie în el scheletul de cod pentru algoritm. Vom crea un nou commit care va conține fișierul bubble-sort.c. Pentru aceasta vom folosi un editor, precum nano, ca în imaginea de mai jos:

Mai sus am creat un commit cu fișierul bubble-sort.c urmând pași similari cu cei din secțiunea Crearea commitului local:

student@uso:~/array-sorting-algorithms$ git status
(...)
student@uso:~/array-sorting-algorithms$ git add bubble-sort.c
student@uso:~/array-sorting-algorithms$ git status
(...)
student@uso:~/array-sorting-algorithms$ git commit -m "Add Bubble Sort skeleton"
(...)

Conținutul fișierului bubble-sort.c este:

#include <stdio.h>
 
static void sort(void)
{
    // TODO: add bubble sort algorithm here
}
 
int main()
{
    return 0;
}
Exerciții
  1. Creați un nou fișier numit radix-sort.c cu următorul conținut:
#include <stdio.h>
 
static void sort(void)
{
    // TODO: add radix sort algorithm here
}
 
int main()
{
    return 0;
}
  1. Creați un commit care să conțină fișierul radix-sort.c. Folosiți următorul mesaj de commit: Add Radix Sort algorithm skeleton.
  2. Dați comanda de verificare git log. Detaliem outputul comenzii git log în subsecțiunea Verificarea istoricului de commituri.
  3. Creați un nou fișier numit merge-sort.c cu următorul conținut:
#include <stdio.h>
 
static void sort(void)
{
    // TODO: add merge sort algorithm here
}
 
int main()
{
    return 0;
}
  1. Creați un commit care să conțină fișierul merge-sort.c. Folosiți următorul mesaj de commit: Add Merge Sort algorithm skeleton.
  2. Dați comanda de verificare git log.

Crearea unui commit cu modificări în fișiere existente

Până acum am creat commituri care conțineau un fișier nou creat. În această secțiune vom modifica conțintului fișierului README.md și vom crea un nou commit, așa cum apare în comenzile de mai jos:

student@uso:~/array-sorting-algorithms$ echo "We implement 3 sorting algorithms for integer arrays." >> README.md
student@uso:~/array-sorting-algorithms$ git status
On branch master
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

no changes added to commit (use "git add" and/or "git commit -a")
student@uso:~/array-sorting-algorithms$ git add README.md
student@uso:~/array-sorting-algorithms$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

    modified:   README.md

student@uso:~/array-sorting-algorithms$ git commit -m "Update README with project explanation"
t explanation"
[master 247b87f] Update README with project explanation
1 file changed, 1 insertion(+)

Spre deosebire de secțiunea Crearea unui nou commit, unde comanda git status arăta că fișierul modificat (în acel caz, bubble-sort.c) este nou (new file), acum comanda git status arată că fișierul modificat (în acest caz, README.md) a fost modificat (modified). Deși apare această diferență în outputul comenzii git status, pașii pentru crearea unui commit care conține un fișier nou sau unul deja existent (dar modificat) sunt aceiași.

Exerciții
  1. Modificați continutul fișierului README.md în # Sorting Algorithm for Integer Arrays.
  2. Creați un commit care să conțină modificările la fișierul README.md. Folosiți următorul mesaj de commit: Update README title.

Verificarea istoricului de commituri

La orice pas al dezvoltării proiectului vrem să știm în ce stadiu ne aflăm ca să ne dăm seama ce am făcut deja și ce trebuie să mai facem în continuare. De aceea folosim un sistem de versionare a codului, în cazul nostru Git. Verificăm istoricul commiturilor folosind comanda git log:

student@uso:~/array-sorting-algorithms$ git log
commit 66b7c5fabb93b521326e6cd9ff219a06a3aec064 (HEAD -> master)
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 10:40:06 2020 -0700

    Update README title

commit 247b87f3f317816a204c4512f6fd9914527a03ad
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:50:30 2020 -0700

    Update README with project explanation

commit 3c835b0d8e7fc88ef45dfd3681867c21b75ed588
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:45:13 2020 -0700

    Add Merge Sort algotihm skeleton

commit b92a52c0fc5d66dce4b2562114cc84ea326b2763
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:44:36 2020 -0700

    Add Radix Sort algotihm skeleton

commit f65a7fbe8bbe1b36ba5c8e16607456a879bfb6fa
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:21:49 2020 -0700

    Add Bubble Sort algorithm skeleton

commit b2a590a8637f1eab96e557334dbd4be14bf95833
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:09:51 2020 -0700

    Add README file

Navigați prin outputul comenzii git log prin intermediul săgeților sus/jos. Apăsați tasta q când ați terminat de inspectat.

În cazul autorului acestui capitol, numele, prenumele și emailul sunt Liza Babu <lizababu@example.com>, așa cum apare în exemplul de mai sus: Author: Liza Babu <lizababu@example.com>.

Fiecare commit este identificat unic printr-un cod, numit cod hash4). Discutăm în continuare despre ultimul commit din listă. Acesta are codul hash 66b7c5fabb93b521326e6cd9ff219a06a3aec064 și mesajul de commit Update README title.

Acum vedem că repository-ul indică spre acest nou commit. Ne dăm seama de acest lucru pentru că HEAD se află în dreptul commitului tocmai făcut. HEAD ne indică starea repository-ului, adică ne arată care este ultimul commit pe care l-am făcut în repository.

Publicarea commiturilor în repository-ul remote

Vrem să publicăm pe GitHub toate schimbările făcute, pentru a fi vizibile și altor colaboratori ai proiectului. Publicăm commitul folosind comanda git push:

student@uso:~/array-sorting-algorithms$ git push origin master
Counting objects: 18, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (16/16), done.
Writing objects: 100% (18/18), 1.95 KiB | 222.00 KiB/s, done.
Total 18 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), done.
To https://github.com/lizababu/array-sorting-algorithms.git
* [new branch]      master -> master

În felul acesta commiturile locale au fost publicate (“împinse”, push) din repository-ul local în repository-ul remote identificat de origin. Commiturile locale se aflau pe branch-ul master din repository-ul local și au fost publicate tot în branch-ul master al repository-ului origin. Vorbim despre branch-uri în secțiunea Lucrul cu branch-uri.

Ca să verificăm publicarea commiturilor, folosim interfața GitHub:

Obținerea commiturilor din repository-ul remote

În lucrul cu Git / GitHub, există bune practici pe care recomandăm să le urmăm.

Atunci când ne apucăm de lucru vrem să sincronizăm repository-ul local cu cel remote. Pot apărea diferențe în momentul în care altcineva a publicat schimbări remote după ce am făcut noi ultima sincronizare. În momentul în care cineva a publicat modificări asupra unei secvențe de cod pe care și noi o modificăm, apar conflicte. Conflictele trebuie rezolvate.

Facem acest lucru prin operația pull, care aduce local toate modificările și încearcă să rezolve conflicetele în mod automat5). Dacă rezolvarea conflictelor nu se poate face automat, trebuie să ne ocupăm de acest pas.

Note de subsol

Extra: Lucrul pe branch-uri

Un dezvoltator vrea să lucreze la o funcționalitate nouă, care poate destabiliza proiectul. Pentru aceasta creează o ramură de dezvoltare nouă (branch) pe care face commituri noi. Ulterior, dacă funcționalitatea este utilă, va fi adăugată în proiect prin unificarea acestui branch (merge); altfel branch-ul va fi șters.

Un repository Git are un branch principal de dezvoltare, numit master. Branch-ul master este branch-ul implicit cu care lucrăm, în care adăugăm commituri și în care vedem istoricul de commituri.

În acest moment istoricul de commituri este următorul:

student@uso:~/array-sorting-algorithms$ git log
commit 66b7c5fabb93b521326e6cd9ff219a06a3aec064 (HEAD -> master, origin/master)
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 10:40:06 2020 -0700
 
    Update README title
 
commit 247b87f3f317816a204c4512f6fd9914527a03ad
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:50:30 2020 -0700
 
    Update README with project explanation
 
commit 3c835b0d8e7fc88ef45dfd3681867c21b75ed588
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:45:13 2020 -0700
 
    Add Merge Sort algotihm skeleton
 
commit b92a52c0fc5d66dce4b2562114cc84ea326b2763
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:44:36 2020 -0700
 
    Add Radix Sort algotihm skeleton
 
commit f65a7fbe8bbe1b36ba5c8e16607456a879bfb6fa
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:21:49 2020 -0700
 
    Add Bubble Sort algorithm skeleton
 
commit b2a590a8637f1eab96e557334dbd4be14bf95833
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 09:09:51 2020 -0700
 
    Add README file

O reprezentare vizuală a istoricului commiturilor este:

Root-commitul este primul commit din istoric (b2a590a8637f1eab96e557334dbd4be14bf95833, b2a590a)6). Toate celelalte commituri vin în continuarea acestuia. Spre exemplu, al doilea commit (f65a7fbe8bbe1b36ba5c8e16607456a879bfb6fa, f65a7fb) a avut la bază primul commit (b2a590a). Modificările au fost făcute peste commitul b2a590a.

Crearea unui commit nou pe un alt branch va modifica istoricul ca în imaginea de mai jos:

În acest moment, pe repository-ul nostru aveți un singur branch - master. În continuare vom lucra la proiectul nostru array-sorting-algorithms, dar vom face schimbări de pe alte branch-uri.

În următoarea secțiune vom adăuga un fișier .gitignore proiectului și vom adăuga implementarea pentru agloritmul Merge Sort.

În general adăugăm fișierul .gitignore la un proiect de pe branch-ul master.

În secțiunea Adăugarea unui fișier .gitignore repository-ului îl vom adăuga de pe un alt branch pe care îl numim add-gitignore ca să ne obișnuim să folosim branch-uri.

Adăugarea unui fișier .gitignore repository-ului

De acum încolo, vom face toate modificările de pe un nou branch, diferit de branch-ul master7).

În această subsecțiune vom crea un nou branch numit add-gitignore. Vom adăuga un fișier .gitignore proiectului de pe acest branch.

Într-un proiect Git, scriem în fișierul .gitignore nume de fișiere și directoare. Acestea sunt fișiere și directoare din directorul proiectului pe care nu vrem să le adăugăm în repository.

Spre exemplu, nu punem fișiere obiect și fișiere executabile în repository pentru că sunt fișiere generate pentru un anumit tip de sistem. Având codul sursă, putem genera fișierele obiect și executabile pe sistemul nostru.

Verificăm branch-ul pe care ne aflăm folosind comanda git branch:

student@uso:~/array-sorting-algorithms$ git branch
*  master

Caracterul * se află în dreptul branch-ului pe care ne aflăm, în cazul nostru master.

Creăm un nou branch numit add-gitignore folosind comanda git branch urmată de numele noului branch:

student@uso:~/array-sorting-algorithms$ git branch add-gitignore
student@uso:~/array-sorting-algorithms$ git branch
add-gitignore
* master

Acum avem 2 branch-uri add-gitignore și master și ne aflăm pe branch-ul master.

Trecem pe branch-ul add-gitignore folosind comanda git checkout:

student@uso:~/array-sorting-algorithms$ git checkout add-gitignore
Switched to a new branch 'add-gitignore'
student@uso:~/array-sorting-algorithms$ git branch
* add-gitignore
master

Avem 2 branch-uri add-gitignore și master și ne aflăm pe branch-ul add-gitignore.

În acest moment branch-ul add-gitignore nu diferă de branch-ul master. Când vom face schimbări (în formă de commituri), cele două branch-uri vor diverge.

Nu dorim să avem fișiere obiect în repository așa că vom configura Git să ignore aceste fișiere. Facem acest lucru prin adăugarea șirului *.o în fișierul .gitignore:

student@uso:~/array-sorting-algorithms$ echo "*.o" > .gitignore
student@uso:~/array-sorting-algorithms$ ls -a
.  ..  bubble-sort.c  .git  .gitignore  merge-sort.c  radix-sort.c  README.md
student@uso:~/array-sorting-algorithms$ git status
On branch add-gitignore
Untracked files:
(use "git add <file>..." to include in what will be committed)
 
    .gitignore
 
nothing added to commit but untracked files present (use "git add" to track)

Creăm un commit cu această modificare (git add și git commit) și îl publicăm (git push):

student@uso:~/array-sorting-algorithms$ git add .gitignore
student@uso:~/array-sorting-algorithms$ git status
On branch add-gitignore
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
 
    new file:   .gitignore
 
student@uso:~/array-sorting-algorithms$ git commit -m "Add .gitignore file"
[add-gitignore 988b188] Add .gitignore file
1 file changed, 1 insertion(+)
create mode 100644 .gitignore
student@uso:~/array-sorting-algorithms$ git push origin add-gitignore
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 283 bytes | 70.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote: 
remote: Create a pull request for 'add-gitignore' on GitHub by visiting:
remote:      https://github.com/lizababu/array-sorting-algorithmss/pull/new/add-gitignore
remote: 
To https://github.com/lizababu/array-sorting-algorithmss.git
* [new branch]      add-gitignore -> add-gitignore

Am publicat commitul în repository-ul remote pe branch-ul add-gitignore. Odată cu publicarea commitului pe GitHub, a fost creat și branch-ul add-gitignore în repository-ul remote.

Orice operație trebuie să fie urmată de o operație de verficare. Din grabă sau neatenție, putem face operații incorecte și este mai ușor să le remediem pe loc decât mai târziu. Spre exemplu, după crearea unui commit, verificăm statusul repository-ului și istoricul de commituri.

Operația merge dintre un branch secundar și master

În secțiunea Adăugarea unui fișier .gitignore repository-ului de mai sus am creat un commit pe branch-ul add-gitignore.

Ne amintim că este BAD PRACTICE să facem modificări direct pe branch-ul master.

Vrem ca modificarea făcută de noi, în acest caz crearea unui fișier gitignore, să se regăsească și pe branch-ul master. Facem acest lucru prin intermediul operației merge, operație care unește două branch-uri: adică aduce conținutul unui branch pe un alt branch, în cazul nostru de pe add-gitignore pe master. Pentru a face acest lucru trebuie să fim pe branch-ul master, adică pe branch-ul în care vrem să integrăm schimbările.

Trecem pe branch-ul master folosind comanda git checkout:

student@uso:~/array-sorting-algorithms$ git checkout master
Switched to branch 'master'

Suntem pe branch-ul master local și ne asigurăm că este sincronizat cu cel remote folosind comanda git pull:

student@uso:~/array-sorting-algorithms$ git pull origin master
From https://github.com/lizababu/array-sorting-algorithmss
* branch            master     -> FETCH_HEAD
Already up to date.

Branch-ul master local este sincronizat cu cel remote (Already up to date). Integrăm branch-ul add-gitignore în branch-ul master folosind comanda git merge:

student@uso:~/array-sorting-algorithms$ git merge add-gitignore
Updating 66b7c5f..988b188
Fast-forward
.gitignore | 1 +
1 file changed, 1 insertion(+)
create mode 100644 .gitignore
student@uso:~/array-sorting-algorithms$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
 
nothing to commit, working tree clean

Acum avem pe repository-ul local un commit în plus față de repository-ul origin (Your branch is ahead of 'origin/master' by 1 commit), adică avem un commit care nu a fost publicat. Verificăm istoricul de commituri pentru a-l vedea folosind comanda git log:

commit 988b188d2c6422a3162d1da25653f4682f7df6db (HEAD -> master, origin/add-gitignore, add-gitignore)
Author: Liza Babu <lizababu@example.com>
Date:   Mon Sep 28 02:29:00 2020 -0700
 
    Add .gitignore file
 
commit 66b7c5fabb93b521326e6cd9ff219a06a3aec064 (origin/master, origin/HEAD)
Merge: f59ba10 23d5cfa
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 10:40:06 2020 -0700
 
    Update README title
(...)

După integrarea branch-ului add-gitignore în branch-ul master vedem că ultimul commit (hash 988b188d2c6422a3162d1da25653f4682f7df6db) se află în istoric. Îl publicăm în repository-ul origin folosind comanda git push:

student@uso:~/array-sorting-algorithms$ git push origin master
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 296 bytes | 296.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/lizababu/array-sorting-algorithms
5c982a1..1f553ab  master -> master

Vedem commitul și pe GitHub ca în imaginea de mai jos pe branch-ul master:

Ștergerea unui branch

După ce terminăm lucrul pe un branch, îl ștergem. Vom folosi noi ramuri de dezvoltare (branch-uri) pentru alte funcționalități, deci nu vom mai avea nevoie de branch-urile vechi.

În acestă secțiune vom șterge branch-ul add-gitignore atât din repository-ul local cât și din interfața GitHub.

Trebuie să ne aflăm pe un branch diferit față de cel pe care vrem să-l ștergem. Verificăm branch-ul pe care ne aflăm folosind comanda git branch:

student@uso:~/array-sorting-algorithms$ git branch
  add-gitignore
* master

Ne aflăm pe branch-ul master. Ștergem branch-ul add-gitignore din repository-ul local folosind comanda git branch -d:

student@uso:~/array-sorting-algorithms$ git branch -d add-gitignore
Deleted branch add-gitignore (was 988b188).
student@uso:~/array-sorting-algorithms$ git branch
* master

Am șters branch-ul add-gitignore din repository-ul local și am rămas doar cu branch-ul master. Ștergem acum branch-ul add-gitiginore și din repository-ul origin folosind comanda git push:

student@uso:~/array-sorting-algorithms$ git push origin --delete add-gitignore
To https://github.com/lizababu/array-sorting-algorithms.git
- [deleted]         add-gitignore

Vedem și din interfața GitHub că nu mai există branch-ul add-gitignore ca în imaginea de mai jos:

Modificarea fișierului .gitignore

În această subsecțiune vom relua pașii prezentați în subsecțiunile anterioare. Vom lucra în continuare pe branch-uri. Vom adăuga o nouă linie în fișierul .gitignore, vom crea un commit cu această schimbare și vom integra schimbările din branch-ul secundar în branch-ul master prin operația merge.

Creăm un branch numit update-gitiginore și ne mutăm pe el.

student@uso:~/array-sorting-algorithms$ git branch update-gitignore
student@uso:~/array-sorting-algorithms$ git checkout update-gitignore 
Switched to branch 'update-gitignore'

Suntem pe branch-ul update-gitignore și adăugăm linia build/ în fișierul .gitignore.

De obicei, într-un proiect vom avea un director în care punem fișierele generate (spre exemplu, fișierele excutabile). Astfel, punem doar numele directorului în .gitginore și toate fișierele din el vor fi ignorate de Git.

Alternativa ar fi să adăugăm numele tuturor fișierelor executabile pe care le generăm în .gitignore. Dezavantajul aici este că nu putem refolosi un fișier .gitignore de la un proiect la altul pentru că numele executabilelor vor fi, cel mai probabil, diferite.

Putem face acest lucru folosind un editor text sau folosind următoarea comandă:

student@uso:~/array-sorting-algorithms$ echo "build/" >> .gitignore

Creăm un commit care să conțină modificărea din fișierul .gitignore.

student@uso:~/array-sorting-algorithms$ git status
On branch update-gitignore
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:   .gitignore
 
no changes added to commit (use "git add" and/or "git commit -a")
student@uso:~/array-sorting-algorithms$ git add .gitignore
student@uso:~/array-sorting-algorithms$ git status 
On branch update-gitignore
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
 
    modified:   .gitignore
 
student@uso:~/array-sorting-algorithms$ git commit -m "Update .gitignore content"
[update-gitignore 6d5189f] Update .gitignore content
 1 file changed, 1 insertion(+)
student@uso:~/array-sorting-algorithms$ git log
commit 6d5189fd7095a19a6ad9bddb8c95491b37d9e828 (HEAD -> update-gitignore)
Author: Liza Babu <lizababu@example.com>
Date:   Mon Sep 28 11:47:36 2020 -0700
 
    Update .gitignore content
 
commit 988b188d2c6422a3162d1da25653f4682f7df6db (origin/master, master)
Author: Liza Babu <lizababu@example.com>
Date:   Mon Sep 28 02:29:00 2020 -0700
 
    Add .gitignore file
 
commit 66b7c5fabb93b521326e6cd9ff219a06a3aec064
Author: Liza Babu <lizababu@example.com>
Date:   Thu Sep 24 10:40:06 2020 -0700
 
    Update README title
(...)

Am verificat istoricul de commituri și vedem că ultimul commit este cel cu identificatorul 6d5189fd7095a19a6ad9bddb8c95491b37d9e828, adică cel creat în această subsecțiune.

Publicăm commitul și în repository-ul origin, pentru ca acesta să fie vizibil și celorlalți colaboratori ai proiectului, folosind comanda git push

student@uso:~/array-sorting-algorithms$ git push origin update-gitignore
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 295 bytes | 295.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote: 
remote: Create a pull request for 'update-gitignore' on GitHub by visiting:
remote:      https://github.com/lizababu/array-sorting-algorithms/pull/new/update-gitignore
remote: 
To https://github.com/lizababu/array-sorting-algorithms.git
* [new branch]      update-gitignore -> update-gitignore

Revenim pe branch-ul master și efectuăm operația merge între branch-ul update-gitignore și master.

student@uso:~/array-sorting-algorithms$ git checkout master
Switched to branch 'master'
student@uso:~/array-sorting-algorithms$ git merge update-gitignore
Updating 988b188..6d5189f
Fast-forward
.gitignore | 1 +
1 file changed, 1 insertion(+)
student@uso:~/array-sorting-algorithms$ git push origin master
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 296 bytes | 296.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/lizababu/array-sorting-algorithms
5c982a1..1f553ab  master -> master

Ștergem branch-ul update-gitignore în repository-ul local și în cel origin.

student@uso:~/array-sorting-algorithms$ git branch -d update-gitignore 
Deleted branch update-gitignore (was 6d5189f).
student@uso:~/array-sorting-algorithms$ git push origin --delete update-gitignore
To https://github.com/lizababu/array-sorting-algorithms.git
 - [deleted]         update-gitignore

Vedem și din interfața GitHub că nu mai există branch-ul update-gitignore ca în imaginea de mai jos:

Exerciții

  1. Creați un branch cu numele merge-sort-implementation.
  2. Treceți pe branch-ul merge-sort-implementation.
  3. Modificați conținutul fișierului merge-sort.c cu următorul conținut:
#include <stdio.h>
 
#define MAX_LEN 100
 
static void merge(int arr[], int left, int mid, int right) 
{ 
    int i, j, k; 
    int n1 = mid - left + 1; 
    int n2 = right - mid; 
 
    int aux_left[n1], aux_right[n2]; 
 
    for (i = 0; i < n1; i++) 
        aux_left[i] = arr[left + i]; 
    for (j = 0; j < n2; j++) 
        aux_right[j] = arr[mid + 1 + j]; 
 
    i = 0;
    j = 0;
    k = l;
    while (i < n1 && j < n2) { 
        if (aux_left[i] <= aux_right[j]) { 
            arr[k] = aux_left[i]; 
            i++; 
        } 
        else { 
            arr[k] = aux_right[j]; 
            j++; 
        } 
        k++; 
    } 
 
    while (i < n1) { 
        arr[k] = aux_left[i]; 
        i++; 
        k++; 
    } 
 
    while (j < n2) { 
        arr[k] = R[j]; 
        j++; 
        k++; 
    } 
}
 
static void sort(int arr[], int left, int right)
{
    if (left < right) {
        int mid = left + (right - left) / 2; 
 
        sort(arr, left, mid); 
        sort(arr, mid + 1, right); 
 
        merge(arr, left, mid, right); 
    } 
}
 
static void print_array(int *arr, int len)
{
    int i;
 
    for (i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
 
int main()
{
    int arr[MAX_LEN], len, i;
 
    printf("What's the length of the array? Maximum lenght is %d\n", MAX_LEN);
    scanf("%d", &len);
 
    printf("Gimme the %d elements\n", len);
    for (i = 0; i < len; i++) {
    scanf("%d", &arr[i]);
    }
 
    printf("Nonsorted array: ");
    print_array(arr, len);
 
    sort(arr, len);
 
    printf("Sorted array: ");
    print_array(arr, len);
 
    return 0; 
}
  1. Creați un nou commit cu această schimbare folosind mesajul de commit Add Merge Sort implementation.
  2. Publicați commitul folosind comanda git push origin merge-sort-implementation.
  3. Efectuați operația merge între branch-ul master și merge-sort-implementation.
  4. Ștergeți branch-ul merge-sort-implementation.

Bune practici

Atunci când contribuim la un proiect putem să lucrăm fie pe branch-ul master, fie pe un alt branch. Este considerat BAD PRACTICE să lucrăm pe branch-ul master din mai multe motive:

  1. Pe branch-ul master se ține întotdeauna o versiune de cod funcțională. Astfel, lucrul pe acest branch ar însemna să facem commituri doar atunci când o funcționalitate este finalizată, altfel pe branch-ul master vom avea o bucată de cod neterminată care poate să afecteze întreg proiectul.
  2. Lucrul pe un singur branch nu oferă posibilitatea de a da feedback pe schimbările făcute pe repository. Dacă nu avem posibilitatea să oferim feedback unui coleg prin intermediul GitHub, atunci vom avea nevoie să comunicăm pe un alt mediu observațiile noastre, iar ei vor trebui să creeze un nou commit pentru rezolvarea problemelor. Mult mai simplu este să se realizeze întreaga etapă de feedback, numită code review înainte ca schimbările să apară pe master. Vom vorbi în următoarea secțiune, Crearea unui Pull Request, termenul de pull request, folosit pentru code review.

Note de subsol

Extra: Crearea unui Pull Request (PR) pe GitHub

În momentul în care vrem să adăugăm o funcționalitate nouă unui proiect software pe GitHub este recomandat să o facem printr-un Pull Request, prescurtat PR. Un Pull Request este o cerere de modificare a repository-ului. Alți colaboratori ai proiectului vor recenza modificările și vor aproba, vor sugera schimbări sau vor respinge această cerere. În momentul în care un Pull Request este aprobat, schimbările propuse în Pull Request pot fi integrate în proiect, adică se va putea face merge între codul sursă curent și noile modificări.

Practic, un Pull Request este o interfață GitHub de contribuit la proiecte software, interfață bazată pe lucrul cu branch-uri Git.

Spunem că deschidem un Pull Request care urmează să fie integrat într-un anumit branch. De obicei acel branch este master. În acest tutorial vom deschide un Pull Request care urmează să fie integrat în branch-ul master.

În secțiunea Lucrul pe branch-uri am adus modificările de pe branch-ul add-gitignore pe branch-ul master prin operația merge. În această secțiune vom integra schimbările prin intermediul unui Pull Request8) urmând pașii din subsecțiunea Pași Pull Request.

Folosirea unui Pull Request în loc de a efectua o operație merge vine cu mai multe beneficii:

  1. În general, schimbările nu pot fi integrate fără ca PR-ul să fie aprobat. Aprobarea vine în mod normal de la un alt coleg de echipă.
  2. Recenzia codului sursă se face ușor pe un Pull Request. Vom vedea în secțiunea Recenzii și recenzenți cum adăugăm recenzenți.

Pași Pull Request

Ca să avem o contribuție prin Pull Request, în mod uzual se urmează pașii:

  1. Creăm un fork sau o clonă a repository-ului. Noi vom folosi în continuare repository-ul array-sorting-algorithms.
  2. Creăm un branch nou.
  3. Creăm un commit (sau mai multe) pe noul branch și îl publicăm pe GitHub.
  4. Creăm un PR în interfața GitHub.
  5. Ulterior, un maintainer al repository-ului va recenza PR-ul. Dacă e nevoie de modificări, vom face actualizări pe care le vom adăuga (prin operația push) din nou în branch-ul nou creat, PR-ul fiind actualizat.
  6. După un număr dat de iterații, PR-ul va fi definitivat. Atunci maintainerul va integra branch-ul în master (operația merge).

În următoarele subsecțiuni vom implementa algoritmul Bubble Sort și vom modifica repository-ul array-sorting-algorithms prin intermediul unui Pull Request.

Crearea unui branch nou

Pentru a putea deschide un Pull Request, trebuie să lucrăm pe un branch diferit față de cel în care vrem să integrăm schimbările. Vom lucra pe un nou branch, diferit de branch-ul master.

Creăm un branch numit bubble-sort-implementation folosind comanda git branch:

student@uso:~/array-sorting-algorithms$ git branch
*  master
student@uso:~/array-sorting-algorithms$ git branch bubble-sort-implementation
student@uso:~/array-sorting-algorithms$ git checkout bubble-sort-implementation
Switched to a new branch 'bubble-sort-implementation'
student@uso:~/array-sorting-algorithms$ git branch
* bubble-sort-implementation
master

Până la acest moment am făcut deja o serie de commituri în repository-ul nostru.

student@uso:~/array-sorting-algorithms$ git log
commit 6d5189fd7095a19a6ad9bddb8c95491b37d9e828 (HEAD -> bubble-sort-implementation, origin/master, master)
Author: Liza Babu <lizababu@example.com>
Date:   Mon Sep 28 11:47:36 2020 -0700
 
    Update .gitignore content
 
commit 988b188d2c6422a3162d1da25653f4682f7df6db
Author: Liza Babu <lizababu@example.com>
Date:   Mon Sep 28 02:29:00 2020 -0700
 
    Add .gitignore file
(...)

Vedem același istoric de commituri și prin intermediul interfeței grafice GitHub ca în imaginea de mai jos:

În următoarea secțiune vom face încă un commit.

Crearea unui commit pe branch-ul nou creat

Vom actualiza fișierul bubble-sort.c cu implementarea algoritmului ales. Modificăm fișierul bubble-sort.c astfel încât conținutul său să fie următorul:

#include <stdio.h> 
 
#define MAX_LEN 100
 
static void swap(int *x, int *y)
{ 
    int tmp = *x; 
    *x = *y; 
    *y = tmp; 
} 
 
static void bubble_sort(int *array, int len)
{ 
    int i, j;
 
    for (i = 0; i < len - 1; i++)
        for (j = 0; j < len - 1; j++)  
            if (array[j] > array[j + 1]) 
                swap(&array[j], &array[j + 1]); 
}
 
static void print_array(int *array, int len)
{
    int i;
 
    for (i = 0; i < len; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
}
 
int main()
{
    int array[MAX_LEN], len, i;
 
    printf("What's the length of the array? Maximum lenght is %d\n", MAX_LEN);
    scanf("%d", &len);
 
    printf("Gimme the %d elements\n", len);
    for (i = 0; i < len; i++) {
    scanf("%d", &array[i]);
    }
 
    printf("Nonsorted array: ");
    print_array(array, len);
 
    bubble_sort(array, len);
 
    printf("Sorted array: ");
    print_array(array, len);
 
    return 0; 
} 

Algoritmul de mai sus sortează crescător un vector de numere întregi citit de la tastatură. Vrem să precizăm acest lucru și în fișierul README.md.

Modificăm fișierul README.md ca să aibă conținutul de mai jos:

# Sorting Algorithm for Integer Arrays

We implement 3 sorting algorithms for integer arrays.

## Bubble Sort

The Bubble Sort algorithm sorts the array in ascending order.

The algorithm is implemented in C.

Creăm un commit cu schimbările făcute. Adăugăm întâi modificările în zona de lucru (staging area) folosind comanda git add:

student@uso:~/array-sorting-algorithms$ git status
On branch bubble-sort-implementation
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
    modified:   bubble-sort.c
 
no changes added to commit (use "git add" and/or "git commit -a")
student@uso:~/array-sorting-algorithms$ git add README.md
student@uso:~/array-sorting-algorithms$ git add bubble-sort.c
student@uso:~/array-sorting-algorithms$ git status
On branch bubble-sort-implementation
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
 
    modified:   README.md
    modified:   bubble-sort.c

Creăm un commit cu modificările din staging area:

student@uso:~/array-sorting-algorithms$ git commit -m "Add Bubble Sort implementation, update README accordingly"
[bubble-sort-implementation d400958] Add Bubble Sort implementation, update README accordingly
2 files changed, 61 insertions(+), 11 deletions(-)
rewrite bubble-sort.c (82%)
 
student@uso:~/array-sorting-algorithms$ git log
commit d400958efb8a49cd9b6050fc03783b218891d165 (HEAD -> bubble-sort-implementation)
Author: Liza Babu <lizababu@example.com>
Date:   Tue Sep 29 01:59:27 2020 -0700
 
    Add Bubble Sort implementation, update README accordingly
 
commit 6d5189fd7095a19a6ad9bddb8c95491b37d9e828 (origin/master, master)
Author: Liza Babu <lizababu@example.com>
Date:   Mon Sep 28 11:47:36 2020 -0700
 
    Update .gitignore content

Acum ultimul commit din istoric pe branch-ul bubble-sort-implementation este d400958efb8a49cd9b6050fc03783b218891d165.

Publicăm commitul în repository-ul origin folosind comanda git push:

student@uso:~/array-sorting-algorithms$ git push origin bubble-sort-implementation
Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 924 bytes | 924.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
remote: 
remote: Create a pull request for 'bubble-sort-implementation' on GitHub by visiting:
remote:      https://github.com/lizababu/array-sorting-algorithms/pull/new/bubble-sort-implementation
remote: 
To https://github.com/lizababu/array-sorting-algorithms.git
* [new branch]      bubble-sort-implementation -> bubble-sort-implementation

Linkul https://github.com/{username}/array-sorting-algorithms/pull/new/bubble-sort-implementation este link la Pull Request.

În loc de {username} veți avea username-ul vostru de pe GitHub. Pentru autorul acestui capitol este lizababu.

Crearea Pull Requestului

Accesăm linkul PR-ului: https://github.com/{username}/array-sorting-algorithms/pull/new/bubble-sort-implementation

În acest moment s-a deschis o nouă pagină care arată că în imaginea de mai jos.

Mai jos explicăm semnificația celor mai importante componente prezente în imagine.

Descrierea Pull Requestului

În imaginea de mai jos vedem o parte a interfeței GitHub în care putem lăsa un comentariu în care să descriem (chiar și mai în detaliu decât într-un mesaj de commit) ce schimbări am făcut.

În cazul nostru, comentariul poate conține detalii despre implementarea algoritmului. O descriere potrivită poate fi:

I implemented a non-optimized Bubble Sort algorithm in C.
The program sorts an integer array in ascending order.

Configurarea branch-ului în care vom integra PR-ul

Ca să finalizăm PR-ul, modificările din acesta trebuie să fie adăugate într-un branch, cel mai adesea în branch-ul master9).

În acest tutorial vom folosi branch-ul master ca în imaginea de mai jos:

Acum creăm Pull Requestul apăsând pe butonul Create pull request ca în imaginea de mai jos:

Recenzii și recenzenți

Folosirea PR-urilor ne aduce un beneficiu important: putem primi feedback pe modificările aduse. Din interfața grafică GitHub putem adăuga recenzenți care să dea feedback pe modificări. Recenzenții fac parte din colaboratorii proiectului.

În acest tutorial nu vom adăuga recenzenți la PR-ul creat.

Integrarea PR-ului

După mai multe runde de feedback și aplicarea acestuia, PR-ul va fi gata de integrat în branch-ul master.

Acest scenariu ping-pong se va repeta până când voi, împreună cu recenzenții, ajungeți la concluzia că schimbările pot fi integrate.

Acum vom face operația merge a PR-ului. Apăsăm mai întâi butonul Merge pull request după care butonul Confirm merge ca în imaginea de mai jos.

Am făcut operația merge în repository-ul remote (origin). Avem pe branch-ul master implementarea algoritmului Bubble Sort. Actualizăm și repository-ul local folosind comanda git pull:

student@uso:~/array-sorting-algorithms$ git checkout master
Switched to branch 'master'
student@uso:~/array-sorting-algorithms$ git pull origin master
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (1/1), done.
From https://github.com/lizababu/array-sorting-algorithms
* branch            master     -> FETCH_HEAD
6d5189f..95c59cd  master     -> origin/master
Updating 6d5189f..95c59cd
Fast-forward
README.md     |  7 +++++++
bubble-sort.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 55 insertions(+), 5 deletions(-)

Ștergerea branch-ului în urma integrării în branch-ul master

Odată făcută operația merge, putem șterge branch-ul pe care am lucrat apăsând pe butonul Delete branch.

Această operație va șterge branch-ul doar pe GitHub. Ca să ștergem branch-ul bubble-sort-implementation și din repository-ul local folosim comanda git branch -d:

student@uso:~/array-sorting-algorithms$ git branch -d bubble-sort-implementation
Deleted branch bubble-sort-implementation (was d400958).
student@uso:~/array-sorting-algorithms$ git branch
* master

În final am ajuns să integrăm modificările de pe branch-ul bubble-sort-implementation în branch-ul master prin intermediul unui Pull Request. Am șters branch-ul bubble-sort-implementation.

Exercițiu - Crearea unui PR

Veți folosi repository-ul array-sorting-algorithms pentru rezolvarea acestui exercțiu.

Creați un Pull Request urmând pașii descriși mai sus în care să adăugați algoritmul de sortare Radix Sort. De această dată veți modifica fișierul radix-sort.c cu următorul conținut:

#include <stdio.h>
 
#define MAX_LEN 100
 
static int get_max(int array[], int n) 
{ 
    int max = array[0]; 
    for (int i = 1; i < n; i++) 
        if (array[i] > max) 
            max = array[i]; 
    return max; 
}
 
static void count_sort(int array[], int n, int exp) 
{ 
    int output[n];
    int i, count[10] = {0};
 
    for (i = 0; i < 10; i++) {
        count[i] = 0;
    }
 
    for (i = 0; i < n; i++) {
        count[(array[i] / exp) % 10]++;
    }
 
    for (i = 1; i < 10; i++) {
        count[i] += count[i - 1];
    } 
 
    for (i = n - 1; i >= 0; i--) { 
        output[count[(array[i] / exp) % 10] - 1] = array[i]; 
        count[(array[i] / exp) % 10]--; 
    }
 
    for (i = 0; i < n; i++) 
        array[i] = output[i]; 
} 
 
static void sort(int array[], int n) 
{ 
    int m = get_max(array, n); 
 
    for (int exp = 1; m / exp > 0; exp *= 10) 
        count_sort(array, n, exp); 
} 
 
static void print_array(int *array, int len)
{
    int i;
 
    for (i = 0; i < len; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
}
 
int main()
{
    int array[MAX_LEN], len, i;
 
    printf("What's the length of the array? Maximum lenght is %d\n", MAX_LEN);
    scanf("%d", &len);
 
    printf("Gimme the %d elements\n", len);
    for (i = 0; i < len; i++) {
    scanf("%d", &array[i]);
    }
 
    printf("Nonsorted array: ");
    print_array(array, len);
 
    sort(array, len);
 
    printf("Sorted array: ");
    print_array(array, len);
 
    return 0; 
}

Pe parcursul creării acestui Pull Request țineți cont de următoarele:

  1. Alegeți nume relevant pentru branch-ul pe care îl veți crea.
  2. Adăugați o secțiune în README.md în care să descrieți algoritmul pe care l-ați implementat (Radix Sort).
  3. Dați un mesaj de commit și o descriere potrivită PR-ului vostru.
  4. Verificați pe tot parcursul dezvoltării starea repository-ului (git status) și istoricul de commituri (git log).

Bune practici

Alternativa la a crea un Pull Request ar fi să lucrăm direct pe branch-ul master.

Așa cum am menționat și în secțiunile anterioare, este BAD-PRACTICE să lucrăm direct pe branch-ul master din mai multe motive:

  1. Pe branch-ul master se ține întotdeauna o versiune de cod funcțională. Astfel, lucrul pe acest branch ar însemna să facem commituri doar atunci când o funcționalitate este finalizată. Altfel pe branch-ul master vom avea o bucată de cod neterminată care poate să afecteze întreg proiectul.
  2. Lucrul pe un singur branch nu oferă posibilitatea de a da feedback pe schimbările făcute pe repository. Dacă nu avem posibilitatea să oferim feedback unui coleg prin intermediul GitHub, atunci vom avea nevoie să comunicăm pe un alt mediu observațiile noastre, iar ei vor trebui să creeze un nou commit pentru rezolvarea problemelor. Mult mai simplu este să se realizeze întreaga etapă de feedback, numită code review, înainte ca schimbările să apară pe master.
  3. În unele situații nici nu vom avea drepturi să scriem pe branch-ul master, astfel devenind obligatoriu lucrul pe un alt branch și integrarea codului în branch-ul master printr-un Pull Request.

Note de subsol

Cuprins

3) Mesajele de commit trebuie să fie punctuale și ușor de înțeles. Alte persoane care lucrează la același proiect software vor vrea să înțeleagă rapid ce am modificat printr-un anumit commit. Recomandări punctuale legate de crearea unor bune mesaje de commit găsiți aici: https://chris.beams.io/posts/git-commit/
4) Codul hash este calculat ca o sumă de control SHA-1 a conținutului commitului.
5) Noi am pornit de la un repository gol, așadar operația pull nu a fost necesară.
6) ID-ul unui commit are o formă lungă, completă, și o formă scurtă formată din primele 7 caractere. De exemplu, pentru commitul cu ID-ul b2a590a8637f1eab96e557334dbd4be14bf95833, forma scurtă este b2a590a.
7) Este GOOD PRACTICE ca fiecare modificare făcută pe un repository să fie făcută pe un branch nou. Branch-ul trebuie să aibă un nume sugestiv pentru ca ceilalți să poată înțelege rapid ce schimbări se fac pe el.
9) Putem schimba acest branch prin apăsarea butonului base: master. Vor fi afișate toate branch-urile din acest repository și vom putea alege în ce branch să integrăm PR-ul.
uso/laboratoare/laborator-04.txt · Last modified: 2020/11/03 12:04 by liza_elena.babu
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