Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Strumenti e nozioni utili per esercitarsi

1Git, GitHub e Classroom 50: il minimo indispensabile

Durante questo corso useremo Git, GitHub e Classroom 50 per organizzare le esercitazioni e tenere traccia del lavoro svolto. Alcuni controlli verranno eseguiti automaticamente; figure, analisi dei risultati e risposte alle domande verranno invece esaminate dai docenti.

Git e GitHub sono strumenti molto generali e possono essere usati in modi assai più complessi di quanto ci servirà qui. Lo scopo di questa sezione non è imparare ad usarli in modo completo, ma costruire il modello mentale minimo necessario per lavorare alle esercitazioni senza usare i comandi alla cieca.

Nel seguito descriveremo brevemente questi strumenti in un ordine che rispecchia la logica con cui sono utilizzati:

  1. prima Git, che funziona sul vostro computer;

  2. poi GitHub, che permette di condividere un repository Git attraverso Internet;

  3. infine Classroom 50, che usa GitHub e alcuni degli strumenti che mette a disposizione degli sviluppatori (chiamati GitHub Actions) per organizzare le esercitazioni e fornire feedback automatico.

2Git

Quando si lavora a un software, è naturale modificarlo molte volte. Alcune modifiche funzionano, altre introducono errori, di cui magari ci accorgiamo più avanti. In altri casi, vogliamo provare a cambiare o aggiungere funzionalità anche complesse con la certezza di poter tornare indietro, o di rivedere i cambi fatti. In questo contesto capire quando e come una certa riga è stata modificata, recuperare una versione precedente o ottenere facilmente una lista di differenze tra versioni è utilissimo. Queste funzionalità sono fornite dai cosiddetti software di controllo di versione, di cui Git è senz’altro il più comune. Semplificando molto, un sistema di controllo di versione come Git registra la storia di un insieme di file come una lista di versioni. Ogni versione, che in Git ha un nome unico detto hash[1], è una foto scattata in un dato momento di tutti i file gestiti da Git nello spazio di lavoro considerato.

Questo spazio di lavoro, la directory controllata da Git, viene chiamata repository, o più brevemente repo. Per questo corso potete pensare a un repository come a una normale directory di lavoro, con in più una storia delle versioni che decidiamo di conservare. Git funziona innanzitutto localmente: potete creare nuove versioni, consultare la storia e confrontare versioni anche senza essere collegati a Internet.

2.1Il workflow locale: modificare, preparare, registrare

Il modo più semplice per capire Git è partire da quello che succede mentre lavorate davvero.

Supponiamo di avere un repository e di aprire uno dei suoi file con un editor. Modificate il codice, salvate il file, magari lo compilate e lo provate. In questa fase state modificando soltanto i file presenti sul vostro computer: la storia conservata da Git non è ancora cambiata.

Git, però, si accorge che quei file sono diversi rispetto all’ultima versione registrata. Potete controllare la situazione in qualunque momento con

git status

che mostra quali file sono stati modificati o creati, senza cambiare nulla nel repository.

Per vedere più precisamente quali righe avete cambiato potete usare

git diff

A un certo punto decidete che alcune delle modifiche fatte formano una versione sensata del lavoro e che volete conservarla nella storia del repository. Prima di creare questa nuova versione dovete indicare a Git quali modifiche ne faranno parte.

Per aggiungere tutte le modifiche correnti usate[2]

git add -A

Le modifiche selezionate in questo modo entrano nella staging area. Potete pensarla come all’insieme delle modifiche che avete preparato per la prossima versione del repository: non sono ancora state registrate nella storia, ma avete detto a Git «queste sono le modifiche che voglio includere nella prossima versione».

Dopo git add potete controllare nuovamente la situazione con

git status

Ora possiamo prepararci al commit, che è l’operazione che registra una nuova versione del progetto nella storia del repository. Se volete vedere nel dettaglio ciò che avete preparato per il commit, potete dare il comando

git diff --staged

Solo quando siete soddisfatti create il commit:

git commit -m "Implementato il metodo di Eulero"

Questo comando prende le modifiche che avevate preparato nella staging area, le applica alla versione precedente e ne crea una nuova, accludendo (con -m) un breve messaggio che descrive il lavoro svolto.

Il workflow locale è quindi, in pratica:

# modifico, compilo e provo i file

git status
git diff

git add -A # oppure git add file.c altro_file.c
git status
git diff --staged

git commit -m "Implementata la prima parte"

I controlli intermedi non sono formalità (soprattutto le prime volte!): servono a evitare di registrare per errore file o modifiche che non volevate includere.

Quando aggiungete un commit, è utile perdere qualche secondo a scrivere un messaggio sensato. Messaggi come

Implementato il metodo di Eulero
Corretto il calcolo dell'energia
Aggiunta la figura del periodo

sono più utili di

prova
cose
ultimo

Non serve però cercare il messaggio perfetto: basta che descriva in modo comprensibile ciò che avete fatto.

A questo punto il nuovo commit esiste nella storia Git sul vostro computer. Non è stato ancora inviato da nessuna parte: vedremo nella sezione su GitHub come condividere questi commit con gli altri membri del gruppo.

2.2Consultare la storia

La storia di un repository si può riassumere in un elenco di commit ordinati dal più al meno recente con il comando

git log --oneline

a cui si può opzionalmente passare un ulteriore argomento -N per limitarci agli ultimi N commit (esempio: git log --oneline -5). Non avremo bisogno di analizzare in dettaglio la struttura della storia di Git[3]. Ci basta sapere che i commit formano una successione di versioni del progetto e che Git ne conserva traccia.

3GitHub

Finora tutto ciò che abbiamo descritto avviene sul vostro computer. Per lavorare in gruppo, però, è utile avere una copia del repository accessibile a tutti.

GitHub è un servizio online che, fra le altre cose, ospita repository Git. Nel nostro caso avremo quindi (almeno) due copie dello stesso progetto:

Vediamo ora quali sono i comandi Git che ci serviranno per far comunicare il repository locale con quello remoto (e viceversa).

3.1git clone: ottenere una copia del repository

Per ottenere l’indirizzo del repository da clonare, andate sulla pagina GitHub del repo, premete su Code e poi sull’icona di copia, qui evidenziata, insieme all’indirizzo stesso, dal rettangolo rosso.

Figure 1:Per ottenere l’indirizzo del repository da clonare, andate sulla pagina GitHub del repo, premete su Code e poi sull’icona di copia, qui evidenziata, insieme all’indirizzo stesso, dal rettangolo rosso.

Quando un repository esiste già su GitHub e volete iniziare a lavorarci su un computer, dovete clonarlo. La Figura 1 mostra come ottenere l’indirizzo del repository da clonare. Una volta copiato l’indirizzo, eseguite

git clone INDIRIZZO_DEL_REPOSITORY

per esempio

git clone https://github.com/nome-organizzazione/esercitazione-gruppo42.git

Git crea una nuova directory contenente i file del progetto e la sua storia. Entrate quindi nella directory:

cd esercitazione-gruppo42

git clone si esegue normalmente una sola volta per ogni repository e per ogni computer sul quale volete lavorare.

3.2git push: inviare i propri commit a GitHub

Dopo aver creato uno o più commit sul repository locale, potete inviarli al repository remoto con

git push

Il punto importante è ricordare che salvare un file non equivale a creare un commit, e creare un commit non cambia la storia del repository remoto di GitHub. In altre parole,

È solo dopo un git push che gli altri membri del gruppo (o voi stessi da casa!) possono ottenere i vostri commit.

3.3git pull: ricevere i commit degli altri

Il repository sul vostro computer non si aggiorna automaticamente quando un compagno fa git push. Per ottenere da GitHub i commit più recenti usate

git pull

Quindi:

3.4Lavorare in gruppo e i conflitti

Supponiamo che Alice e Bob abbiano entrambi clonato lo stesso repository.

Alice modifica un file, crea un commit e fa git push. Il repository su GitHub ora contiene il suo nuovo commit. Bob, eseguendo git pull, riceve quel commit e aggiorna la propria copia locale.

Se Alice e Bob modificano parti diverse del progetto, Git spesso riesce a combinare automaticamente il loro lavoro. Se invece modificano in modo incompatibile le stesse righe, Git non può decidere da solo quale versione mantenere: si verifica un conflitto.

Potreste vedere messaggi contenenti parole come

CONFLICT
Automatic merge failed

Un conflitto non significa che il lavoro sia andato perso. Significa soltanto che è necessario decidere manualmente come combinare due modifiche incompatibili.

Per questo corso, se incontrate un conflitto e non sapete come risolverlo, non usate git push --force o altri comandi che trovate in rete (o che vi fornisce il vostro LLM preferito), e non cancellate il repository. Mostrate ai docenti l’output del comando e il risultato di git status.

3.5GitHub Actions e Continuous Integration

GitHub non si limita a conservare repository. Può anche eseguire automaticamente operazioni e programmi quando nel repository accadono determinati eventi. Questa funzionalità si chiama GitHub Actions.

Un workflow di GitHub Actions è una sequenza di operazioni eseguita automaticamente da GitHub. Per esempio, dopo un git push, un workflow potrebbe

  1. ottenere la nuova versione del repository;

  2. compilare il programma;

  3. eseguirlo;

  4. controllare che siano stati prodotti i file richiesti;

  5. verificare alcuni risultati numerici.

L’uso sistematico di controlli automatici di questo tipo rientra in ciò che viene normalmente chiamato Continuous Integration, o CI. Per questo corso non è necessario conoscere l’architettura della CI. È sufficiente sapere che i vostri push faranno partire automaticamente dei controlli che vi daranno del feedback iniziale sull’esercitazione che state svolgendo.

Un esempio di scheda “Actions” su github. Dei quattro worfklow mostrati, quattro si sono conclusi con successo, mentre il terzo ha dato errore.

Figure 2:Un esempio di scheda “Actions” su github. Dei quattro worfklow mostrati, quattro si sono conclusi con successo, mentre il terzo ha dato errore.

Potete vedere le esecuzioni dalla scheda “Actions” del repository su GitHub, di cui un esempio è mostrato in Figura 2. In generale le possibilità sono tre:

Se un controllo fallisce, aprite l’esecuzione e leggete l’output dello step che ha dato errore. Il messaggio potrebbe indicare, per esempio, che il programma non compila, che manca un file richiesto o che un risultato numerico non è quello atteso.

3.6Pull request

GitHub possiede anche uno strumento chiamato pull request, spesso abbreviato in PR. Nonostante il nome, una pull request non è il comando git pull, che come abbiamo visto aggiorna il repository sul vostro computer. Una pull request è invece una pagina di GitHub che permette di mostrare, confrontare e commentare un insieme di modifiche.

Nel normale sviluppo software le pull request sono spesso usate per discutere modifiche prima di integrarle in un progetto. Nel nostro corso non sarà necessario imparare il workflow generale delle pull request: quando ne useremo una, servirà soprattutto come luogo in cui visualizzare il lavoro e ricevere commenti dai docenti.

4Classroom 50

A questo punto abbiamo tutti gli ingredienti necessari per parlare di Classroom 50, il sistema che useremo per organizzare le esercitazioni. Non sostituisce Git o GitHub: li usa. In particolare, Classroom 50 può preparare per un’esercitazione

Il punto importante è che, una volta creato il repository dell’esercitazione, voi lavorerete con normali comandi Git e con la normale interfaccia di GitHub.

Classroom 50 serve quindi a “montare” questi strumenti in una struttura adatta al corso.

4.1Come si svolgeranno le esercitazioni

Ora abbiamo gli strumenti necessari per capire come si svolgerà un’esercitazione. All’inizio di ogni esercitazione, il docente vi metterà a disposizione un link, dopodiché dovrete eseguire i passi seguenti:

  1. Seguite il link fornito dai docenti e accettate l’esercitazione in Classroom 50. Verrà preparato il repository GitHub associato al vostro gruppo.

  2. Clonate il repository (da fare ogni volta che cominciate a lavorare all’esercitazione su un certo computer):

git clone URL_DEL_REPOSITORY
cd NOME_DEL_REPOSITORY

Leggete quindi il README.md e gli altri file che contengono la traccia.

  1. Se il repository è già presente sul computer, all’inizio di una sessione controllate il suo stato e scaricate eventuali commit fatti dai vostri compagni:

git status
git pull
  1. Modificate il codice, compilatelo ed eseguitelo sul vostro computer come richiesto dalla traccia. Git non sostituisce il normale ciclo di sviluppo: prima di tutto dovete provare il programma voi stessi.

  2. Quando avete completato una parte sensata, fate una “foto” alla versione del lavoro aggiungendo un commit:

git status
git diff
git add -A
git status
git commit -m "Descrizione delle modifiche"

Il commit è ora registrato localmente.

  1. Condividete il lavoro inviando il nuovo commit a GitHub con

git push

Questo rende il lavoro disponibile agli altri componenti del gruppo e, nelle esercitazioni configurate per farlo, avvia i controlli automatici.

  1. Leggete il feedback automatico: dopo il push, controllate il risultato su GitHub nella scheda Actions oppure attraverso la pagina di Classroom 50 indicata nella traccia. Se un controllo fallisce, leggete il messaggio, correggete il problema e create un nuovo commit. Non è necessario aspettare di aver terminato tutta l’esercitazione prima di fare push: commit piccoli e ragionevoli rendono più semplice capire cosa è cambiato e permettono di ricevere feedback durante il lavoro.

  2. I controlli automatici possono verificare soltanto ciò che è stato programmato esplicitamente. Possono, per esempio, controllare che il codice compili, che un file esista, che l’output abbia un certo formato o che un valore numerico rientri in una certa tolleranza. Non possono sostituire la valutazione scientifica di una figura, di un’analisi o di una risposta argomentata. Queste parti verranno esaminate dai docenti. Quindi il fatto che i controlli automatici previsti sono stati superati non significa necessariamente che l’intera esercitazione sia corretta o completa.

4.2I comandi da conoscere

Per il normale svolgimento delle esercitazioni vi serviranno soprattutto questi comandi:

# crea sul computer una copia di un repository esistente su GitHub
git clone URL

# mostra lo stato del repository
git status

# mostra le modifiche non ancora registrate
git diff

# prepara le modifiche per il prossimo commit
git add -A # oppure git add file1.c file2.c ...

# registra un nuovo commit nella storia locale
git commit -m "Descrizione"

# invia a GitHub i nuovi commit locali
git push

# scarica da GitHub i nuovi commit e aggiorna la copia locale
git pull

# mostra in forma compatta gli ultimi 5 commit
git log --oneline -5

Se non sapete cosa sta succedendo, il primo comando da provare è quasi sempre

git status

perché mostra lo stato del repository senza modificarlo.

5Il workflow da ricordare

Una volta compresi i concetti precedenti, il ciclo di lavoro può essere riassunto in modo compatto.

All’inizio di una sessione:

git status
git pull

Durante il lavoro:

  1. Modifico e provo il codice

  2. Controllo le modifiche fatte con git status e/o git diff

  3. Dico a git quali sono i file da modificare con git add -A

  4. Salvo le modifiche su git con git commit

  5. Invio le modifiche a GitHub con git push

  6. Controllo il feedback automatico sul sito di Classroom 50 o direttamente sulla tab “Actions” di GitHub. Se il feedback segnala un problema, correggete il codice e ripetete il ciclo.

Quando lavorate a casa ma in gruppo, è importantissimo ricordare di fare git pull prima di iniziare a lavorare e git push quando volete condividere i vostri nuovi commit.

5.1Alcuni problemi comuni

«Ho modificato un file, ma GitHub mostra ancora la vecchia versione»

Salvare il file non basta. Controllate git status: probabilmente dovete ancora eseguire

git add -A
git commit -m "..."
git push
«Ho fatto un commit, ma GitHub non cambia»

Il commit è locale. Dovete ancora fare

git push
«Il mio compagno ha fatto git push, ma io non vedo le sue modifiche»

La vostra copia locale non si aggiorna automaticamente. Eseguite

git pull
«git push viene rifiutato perché su GitHub ci sono nuovi commit»

Probabilmente un vostro compagno ha fatto push prima di voi. Non usate git push --force.

Controllate lo stato del repository e provate a sincronizzarlo:

git status
git pull

Se Git riesce a combinare le modifiche, potrete poi fare git push. Se compare un conflitto e non sapete come procedere, chiedete ai docenti.

«GitHub Actions mostra un errore»

Il push è arrivato correttamente a GitHub; è uno dei controlli automatici ad aver trovato un problema. Aprite la scheda “Actions”, selezionate l’ultima esecuzione e leggete l’output dello step che è fallito.

«Ho scaricato il repository come ZIP»

Lo ZIP contiene i file, ma non contiene una normale copia di lavoro Git. Tornate alla pagina GitHub del repository ed eseguite git clone INDIRIZZO_DEL_REPOSITORY.

«Ho eseguito git init dentro un repository clonato»

Non serve: git clone crea già un repository Git completo. Se non siete sicuri di cosa avete modificato, fermatevi e chiedete ai docenti prima di provare comandi correttivi trovati online.

«Posso modificare gli altri file del repo?»

In generale, meglio di no. Il repository può contenere file usati da Classroom 50 o dai workflow di GitHub Actions, per esempio file sotto

.github/

o altri file di configurazione indicati nella traccia. Salvo istruzioni esplicite, non modificateli e non cancellateli. Analogamente, non aggiungete al repository grandi quantità di file temporanei, eseguibili o dati generati se la traccia non vi chiede di farlo.

6Programmi controllabili da riga di comando: argc e argv

Come abbiamo visto, alcuni workflow di GitHub Actions preparati dai docenti compileranno ed eseguiranno automaticamente i vostri programmi. Perché questo sia possibile, è utile che un programma abbia un modo semplice e ben definito per ricevere dall’esterno i parametri che ne controllano il comportamento.

Supponiamo, per esempio, di scrivere un programma che integra numericamente il moto di una massa collegata a una molla (capiremo bene cosa vuol dire nelle prossime lezioni!). Il risultato dipenderà da diversi parametri, come il passo temporale Δt\Delta t, la massa mm e la costante elastica kk.

Una possibilità sarebbe fissare questi valori direttamente nel sorgente:

double dt = 0.01;
double m = 1.0;
double k = 2.0;
int N_passi = 1000;

Se volessimo cambiare uno dei parametri dovremmo però modificare il file, ricompilarlo ed eseguirlo di nuovo. Questo approccio è particolarmente scomodo per un controllo automatico, che dovrebbe poter provare lo stesso programma con molti valori diversi.

È molto più comodo fornire i parametri quando il programma viene eseguito. Vorremmo, per esempio, poter scrivere

./oscillatore 0.01 1.0 2.0 1000

stabilendo che i tre numeri rappresentino, nell’ordine, dt massa k N_passi. La riga di comando diventa così una semplice interfaccia del nostro programma: chi lo esegue non deve modificarne il sorgente, ma deve soltanto sapere quali argomenti fornire e in quale ordine.

6.1Gli argomenti di main

Finora abbiamo scritto la funzione main nella forma

int main() {
    /* ... */
}

In C possiamo però scriverla anche come

int main(int argc, char *argv[]) {
    /* ... */
}

I due argomenti argc e argv permettono di accedere a ciò che è stato scritto sulla riga di comando:

Consideriamo di nuovo

./oscillatore 0.01 1.0 2.0 1000

In questo caso il sistema operativo chiamerà la funzione main inizializzando gli argomenti come segue:

argc = 5
argv = { "./oscillatore", "0.01", "1.0", "2.0", "1000" };

6.2Gli elementi di argv sono stringhe

C’è un’altra cosa importante da osservare. Anche se abbiamo scritto

0.01
1.0
2.0
1000

gli elementi di argv non sono numeri ma stringhe. Per usarli nei calcoli dobbiamo quindi convertirli nel tipo appropriato. Nel nostro caso, quello che ci interesserà quasi sempre sarà di convertire una stringa in un double, oppure un intero in un int. Questo si può fare usando atof (ascii to float) e atoi (ascii to integer), dichiarate in stdlib.h:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    double dt = atof(argv[1]);
    double m = atof(argv[2]);
    double k = atof(argv[3]);
    int N_passi = atoi(argv[4]);

    printf("dt = %lf, m = %lf, k = %lf, N_passi = %d\n", dt, m, k, N_passi);

    return 0;
}

Possiamo ora compilare una sola volta il programma,

gcc -o oscillatore oscillatore.c

e poi eseguirlo con parametri diversi:

./oscillatore 0.01 1.0 2.0 1000
./oscillatore 0.001 1.0 2.0 1000
./oscillatore 0.01 2.0 5.0 2000

6.3Controllare il numero degli argomenti

Il programma precedente assume che argv[1], argv[2], argv[3] e argv[4] esistano. Ma cosa succede se qualcuno esegue semplicemente

./oscillatore

oppure dimentica uno dei parametri? Prima di accedere agli elementi di argv dobbiamo controllare che sia stato fornito il numero corretto di argomenti, altrimenti rischiato di accedere ad elementi di argv che non esistono, il che, nel migliore dei casi, farà terminare il programma con un segmentation fault, mentre nel caso peggiore darà luogo a comportamenti inaspettati (undefined behaviour). Nel nostro esempio vogliamo quattro parametri oltre al nome del programma, quindi ci aspettiamo

argc == 5

Possiamo scrivere:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc != 5) {
        fprintf(stderr, "Uso: %s dt massa k N_passi\n", argv[0]);
        return 1;
    }

    double dt = atof(argv[1]);
    double m = atof(argv[2]);
    double k = atof(argv[3]);
    int N_passi = atoi(argv[4]);

    /* calcolo */

    return 0;
}

Se il programma viene invocato in modo errato, per esempio con

./oscillatore

otterremo un messaggio simile a

Uso: ./oscillatore dt massa k N_passi

e il programma terminerà senza provare ad accedere ad argomenti che non esistono.

6.4L’interfaccia del programma fa parte della traccia

Nelle esercitazioni specificheremo, quando necessario, quali argomenti il vostro programma deve accettare e in quale ordine.

Se, per esempio, la traccia dice che il programma deve essere eseguito come

./oscillatore dt massa k N_passi

allora dovrete interpretare gli argomenti precisamente come

argv[1] -> passo temporale
argv[2] -> massa
argv[3] -> costante elastica
argv[4] -> numero di passi di integrazione

Questa convenzione fa parte dell’interfaccia del programma.

Un workflow di GitHub Actions potrà quindi compilare il vostro codice e provare automaticamente comandi come

./oscillatore 0.01 1.0 2.0 1000

e

./oscillatore 0.005 3.0 0.5 2000

senza modificare il codice sorgente.

Questo è il motivo principale per cui useremo argc e argv: ci permettono di separare il codice che implementa il calcolo dai parametri con cui vogliamo eseguirlo, e forniscono al programma un’interfaccia semplice che può essere usata sia da voi sia dai controlli automatici.

Footnotes
  1. Un “git hash” è composto da 40 caratteri esadecimali (spesso abbreviato in 7 per comodità).

  2. Se invece volete essere più precisi e aggiungere solo alcuni file specifici, potete passarne l’elenco a git add, omettendo il -A. Ad esempio, se volete aggiungere solo eulero.c il comando corretto è git add eulero.c

  3. Se siete interessati potete studiare come funziona git diff, che permette di ottenere la lista di cambi fatti tra la versione attuale e una o più versioni specifiche, oppure tra versioni specificate, di tutto il repository o di particolari file.