speaker: Maurizio Del Magno
Build software better, together
git
https://github.com/delphiforce/eInvoice4DeInvoice4D
Maurizio Del MagnoDeveloper
speaker: Maurizio Del Magno
i-ORM
DJSONgithub.com/mauriziodm/iORM
github.com/mauriziodm/DJSON
facebook.com/maurizio.delmagno
iORM + DJSON (group)
levante software
Membro fondatore
Build software better, together
git
Git …perchè questo nome, cosa significa?
‘Idiota’ Git Hub?“Sono maledettamente egoista ed egocentrico, chiamo tutti i miei progetti in modo che rimandino direttamente e me:
prima ‘Linux’, ora ‘Git’ ” (cit. Linus Torvalds)
“ ‘Git’ può significare qualsiasi cosa, a seconda del tuo stato d’animo, fai la tua scelta dal dizionario dei gerghi inglese” (cit. Linus Torvalds)
• Idiota / stupido • Persona polemica, irascibile, che vuole avere sempre ragione • Testa di porco • Semplice • …
• “Global Information Tracker” (se sei di buon umore e funziona davvero per te, gli angeli cantano e la luce improvvisamente riempie la stanza - cit. Linus Torvalds) • “Maledetto autocarro carico di m…a” (quando si rompe - cit. Linus Torvalds)
Maurizio Del Magno
! …a tenere traccia delle modifiche ai sorgenti (e ai files in generale)
! …se qualcosa va storto ci permette di tornare senza problemi a versioni precedenti del codice
! …a collaborare con altri
Git …a cosa serve?
VCS (Version Control System)
Maurizio Del Magno
Local Computer
Repository
Version 3
Version 2
Version 1
Version 3Working copy
checkout
commit
LVCS Local Version Control System
• RCS. Team?
Maurizio Del Magno
Local Computer
Repository
Version 3
Version 2
Version 1
Version 3
Working copy
chec
kout
com
mit
Remote Server
Local Computer
Working copy
Local Computer
Working copy
chec
kout
commit
checkout
comm
it
CVCS Centralized Version Control System
• CVS • Subversion • Perforce.
Very large & distributed teams?
Single point of failure
Maurizio Del Magno
Local Computer
Repository
Version 3
Version 2
Version 1
Version 3
Working copy
checkout
commit
Remote Server
Local Computer
Working copy
Local Computer
Working copy
push
pu
ll
checkout
commit
Repository
Version 3
Version 2
Version 1
Version 3 checkout
commit
Repository
Version 3
Version 2
Version 1
Version 3
Repository
Version 3
Version 2
Version 1
Version 3
pullpush push
pull
DVCS Distributed Version Control System
• Git • Mercurial • Bazaar • Darcs.
Git …i perchè di Linus Torvalds
! Serviva un sostituto di BitKeeper (che non era più gratuito)
! Nessun altro sistema gratuito soddisfaceva le necessità di Torvalds1. Sistema distribuito (come BitKeeper)
2. Salvaguardia dalla corruzione dei dati (accidentale o intenzionale)
3. Altissime prestazioni
4. Forte supporto allo sviluppo non lineare (migliaia di branch paralleli)
5. CVS come esempio di cosa “non fare” (nel dubbio fai il contrario)
Maurizio Del Magno
Git Basics:
Dimenticate quello che sapete degli altri VCS!
Maurizio Del Magno
Git Basics:
Snapshots, Not Differences
Gli altri VCS memorizzano liste di differenze per singolo file
Maurizio Del Magno
Git Basics:
Git memorizza snapshots del filesystem
Commit = foto/snapshot dello stato del filesystem in un dato istanteRepository = Stream di snapshots
Snapshots, Not Differences
Maurizio Del Magno
Git Basics:
Più simile a un mini-filesystem…
… con tools aggiuntivi particolarmente potenti.
(Linus Torvalds)
Snapshots, Not Differences
Maurizio Del Magno
Git Basics:
Nearly Every Operation Is Local
! Quasi ogni operazione è locale (non ha bisogno di accedere a info remote)• Scorrere la storia del progetto• Ripristinare una versione precedente (checkout)• Commit• Branch, Merge
! Lavorare offline (es: develop, commit, branch, merge, stash, patch… upload differito)• Aereo• Treno• Off-VPN• In caso di guasti o comunque senza connessione per qualunque motivo
Maurizio Del Magno
Git Basics:
Git Has Integrity! Tutto è check-summed prima di essere archiviato
• Strettamente integrato in Git al più basso livello• Impossibili variazioni, corruzioni, perdite di informazioni che Git non sia in grado di rilevare• Il tipo di check-sum usato è SHA-1 (40 car. es: “24b9da6552252987aa493b52f8696cd6d3b00373”)
• E’ usato anche come nome per i commit (No filenames, bastano i primi 7 caratteri es: “24b9da6”)
! Non cancella nulla, aggiunge solo
! E’ veramente difficile convincere Git a fare qualcosa che causi perdita di informazioni o che non sia annullabile
Maurizio Del Magno
Git …caratteristiche
Distribuito
Sicuro
Veloce
Forte supporto allo sviluppo non lineare (migliaia di branches paralleli)
Maurizio Del Magno
Git Basics: Repository 2 tipi di oggetti
e986c9a
e0e5ae3
4a6a298
a41c147 8783136
1fc5678
HEAD
master
develop
! Commit• Rappresenta lo stato di un progetto in un dato istante (foto/snapshot)
• Nome (SHA-1)
• Descrizione• Riferimento a genitore• Un oggetto commit può avere più di un genitore.
! Intestazioni (Heads)• Puntatore a un oggetto commit• Nome / Titolo (alla creazione del repository viene creata una intestazione ‘master’ per default)
• Possono esserci molteplici intestazioni• HEAD: speciale intestazione che punta all’oggetto commit su cui stiamo lavorando.
(working directory) (checkout) (attenzione al maiuscolo)
“inizio del progetto”
“aggiunta feature xyz”
“refact”
Maurizio Del Magno
Git Basics: Repository 2 “azioni” principali + 1
Branch (o ramo, braccio)• Una linea indipendente di sviluppo• Ha un nome• C’è sempre un ramo principale “master”• Sperimentare/testare nuove feature o bug-fix senza introdurre anomalie nel ramo principale• Lavorare in team
Merge• Fusione tra due rami• 3 tipi di merge: fast forward, clean, conflict
Push/Pull• Push: scrivere sul repository remoto (origin) le modifiche fatte nel nostro repository locale (upload)
• Pull: leggere dal repository remoto (origin) le modifiche fatte da altri (download)
Maurizio Del Magno
Git Basics:
e986c9a
master
2 + 2 + 1 = git
+ +
Maurizio Del Magno
Git Basics:
Files: 3 Stati Possibili
1. Committed: già salvato in modo sicuro nel repository locale
2. Modified: modificato e non ancora committato (modifiche pendenti)
3. Staged: file modificato marcato per essere incluso nel prossimo commit/snapshot
Untracked: file non tracciato da Git
Maurizio Del Magno
Git Basics:
Repository ‘.git’ subdir
e986c9a
e0e5ae3
4a6a298
a41c147 8783136
1fc5678master
develop
Local
Working Directory
Staging AreaStage Commit
Checkout 4a6a298
4a6a298M + - f01a9e3
Commited (unmodified) Modified Untracked
Staged
Repository: 3 Sezioni
HEAD
shell
view.pas model.pas
e986c9aH m
MainForm.Caption := ‘Git test’;
git init git add view.pas git commit -m “primo commit”
1) Inizializzazione e primo commit
Staging areaview.pas
M
“primo commit”
.git
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9aH m
MainForm.Caption := ‘Git test’;
git init git add view.pas git commit -m “primo commit”
2) Modifichiamo il file
Staging areaview.pas
M
MainForm.Show;
git add view.pas git commit -m “Risolto bug MainForm non visibile”
e0e5ae3
“primo commit”
“Risolto bug…”
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
H m
MainForm.Caption := ‘Git test’;
git add view.pas git add model.pas git commit -m “Aggiunto model e commento”
3) Un altro commit?
MainForm.Show;
e0e5ae3
“primo commit”
“Risolto bug…”
TCliente = class…// Necessario;
M M
Staging areaview.pas; model.pas
4a6a298 “Aggiunto model…”
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
H m MainForm.Caption := ‘Git test’;
git checkout e0e5ae3
4) Come era prima?
MainForm.Show;
e0e5ae3
TCliente = class…// Necessario; 4a6a298
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
H
m MainForm.Caption := ‘Git test’;
git checkout e0e5ae3
5) Ci chiedono una nuova feature (ma senza toccare il master)
MainForm.Show;
e0e5ae3
TNewCliente = class…
M M
Staging areaview.pas; model.pas
4a6a298
git branch feature1 git checkout feature1 git add . git commit “Nuova classe cliente…”
f
LCliente := TNewCliente.Create;8783136
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
H m MainForm.Caption := ‘Git test’;
git checkout e0e5ae3
6) Continuiamo a lavorare sulla nuova feature
MainForm.Show;
e0e5ae3
TNewCliente = class…4a6a298
git branch feature1 git checkout feature1 git add . git commit “Nuova classe cliente…”
f LCliente := TNewCliente.Create;
8783136property Age: integer read FAge;
M
git commit -a -m “aggiunto proprietà Age”
Staging areamodel.pas
2f57bh6
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
H
m MainForm.Caption := ‘Git test’; MainForm.Show;
git checkout e0e5ae3
7) Ora però dobbiamo tornare alla versione di produzione
e0e5ae3
TNewCliente = class… property Age: integer read FAge; 4a6a298
git branch feature1 git checkout feature1 git add . git commit “Nuova classe cliente…”
f
8783136
git commit -a -m “aggiunto proprietà Age”
2f57bh6
git checkout master git commit -a -m “visualizzazione età cliente”
// Necessario;LCliente := TNewCliente.Create;
TCliente = class…
Label1.Caption := LCliente.Age;
M
Staging areaview.pas
fa526e1
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
git merge feature1
8) Merge (no conflict)
e0e5ae3
TNewCliente = class… property Age: integer read FAge; 4a6a298
f
8783136
2f57bh6
TCliente = class…
fa526e1
MainForm.Caption := ‘Git test’; MainForm.Show; // Necessario LCliente := TNewCliente.Create; Label1.Caption := LCliente.Age;
MainForm.Caption := ‘Git test’; MainForm.Show; // Necessario Label1.Caption := LCliente.Age;
H m fa526e1 2f57bh6
156he1b
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
git merge feature1
9) Merge (with conflict)
e0e5ae3
… <<<<<<< HEAD TCliente = class… =======
4a6a298 8783136
2f57bh6fa526e1fa526e1 2f57bh6
156he1b
Auto-merging model.pas CONFLICT (content): Merge conflict in model.pas Automatic merge failed; fix conflicts and then commit the result.
Conflict
merging commit
MainForm.Caption := ‘Git test’; MainForm.Show; // Necessario LCliente := TNewCliente.Create; Label1.Caption := LCliente.Age;
MainForm.Caption := ‘Git test’; MainForm.Show; // Necessario Label1.Caption := LCliente.Age;
TCliente = class…
TNewCliente = class… property Age: integer read FAge;>>>>>>> feature1 …
M
git add model.pas git commit “feature1 merged on master”
Staging areaview.pas
H m f
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
git merge feature1
10) Eliminiamo il branch (oppure no?)
e0e5ae3
4a6a298 8783136
2f57bh6fa526e1
156he1b
Auto-merging model.pas CONFLICT (content): Merge conflict in model.pas Automatic merge failed; fix conflicts and then commit the result.
MainForm.Caption := ‘Git test’; MainForm.Show; // Necessario LCliente := TNewCliente.Create; Label1.Caption := LCliente.Age;
TNewCliente = class… property Age: integer read FAge;
git add model.pas git commit “feature1 merged on master”
H m
f
Ci serve ancora?
git branch -D feature1
.git
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
git branch -D feature1
11) Se branch il viene eliminato prima del merge reflog command
git branch NewBranchName SHA1 git checkout -b NewBranchName SHA1
e0e5ae3
4a6a298 8783136
2f57bh6fa526e1H m f
8783136
2f57bh6
fa526e1 HEAD@{0}: checkout: moving from branch feature1 to master 2f57bh6 HEAD@{1}: commit: visualizzazione età cli… 8783136 HEAD@{2}: commit: aggiunto proprietà Age E0e5ae3 HEAD@{4}: checkout: moving from branch master to feature1 …git branch oldfeature1 HEAD@{1}
git reflog
MainForm.Caption := ‘Git test’; MainForm.Show; // Necessario LCliente := TNewCliente.Create; Label1.Caption := LCliente.Age;
TNewCliente = class… property Age: integer read FAge;
.git
Maurizio Del Magno
Stashshell
view.pas model.pas
e986c9a
git checkout feature1 git stash save git checkout feature1 … git checkout master git stash apply git stash drop
12) Stash
e0e5ae3
4a6a298 8783136
2f57bh6fa526e1m f
MainForm.Caption := ‘Git test’; MainForm.Show; // Necessario LCliente := TNewCliente.Create; Label1.Caption := LCliente.Age;
TNewCliente = class… property Age: integer read FAge;
MM
MainForm.Close;
procedure Reset;
Pending Pending
procedure Reset;
.git
H
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
git checkout feature1 git rebase master
13) Rebase (no conflict) Fast forward merge alla fine
e0e5ae3
TNewCliente = class… property Age: integer read FAge; 4a6a298
f
8783136
2f57bh6fa526e1
MainForm.Caption := ‘Git test’; MainForm.Show; // Necessario LCliente := TNewCliente.Create; Label1.Caption := LCliente.Age;
m
8783136
2f57bh6
8783136
2f57bh6
67e1b11
d5517feFirst, rewinding head to replay your work on top of it... Applying: commit Applying: commitgit checkout master git merge feature1 git branch -D feature1
.git
H
Non fare rebase su rami già inviati a un repository remoto
Maurizio Del Magno
14 ) Cominciamo a collaborare Creazione repository remoto su GitHub
shell
view.pas model.pas
e986c9a
H m
MainForm.Caption := ‘Git test’;MainForm.Show;
e0e5ae3
.git
Origin
o/m
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
H m
MainForm.Caption := ‘Git test’;
git remote add origin https://github.com/mauriziodm/delphiday2019.git git push —set-upstream origin master git push
15) creare un repository remoto (Origin) git remote -v
colleghiamo il repository remoto (origin) al nostro repository locale colleghiamo il nostro branch master locale con il master remoto
cacciamo il nostro primo Push
MainForm.Show;
e0e5ae3
.git
Origin
e986c9a
e0e5ae3
o/m
e986c9a
e0e5ae3
Maurizio Del Magno
shell
view.pas model.pas
e986c9a
H m
MainForm.Caption := ‘Git test’;MainForm.Show;
e0e5ae3
.git
shell
view.pas model.pas
16) Un nuovo contributor
Origin
e986c9a
e0e5ae3o/m
shell
view.pas model.pas
.git
17) Un nuovo contributor git clone
shell
view.pas model.pas
e986c9a
H m
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
Origin
e986c9a
e0e5ae3o/m
git clone https://mauriziodm:[email protected]/mauriziodm/delphiday2019.git
MainForm.Caption := ‘Git test’; MainForm.Show;
H m
e986c9a
e0e5ae3
e986c9a
e0e5ae3
shell
view.pas model.pas
.git
18) Un nuovo contributor push/pull (same branch, no conflict)
shell
view.pas model.pas
e986c9a
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
Origin
e986c9a
e0e5ae3o/m
git clone https://mauriziodm:[email protected]/mauriziodm/delphiday2019.git
MainForm.Caption := ‘Git test’; MainForm.Show;
TNewCliente = class…
M
git commit -a -m “aggiunta classe TNewCliente” git push
e986c9a
e0e5ae3
Staging areamodel.pas
4a6a298
H m
4a6a298
4a6a298
git fetch git statusOn branch master Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded. (use "git pull" to update your local branch)
nothing to commit, working tree cleangit pull
4a6a298
4a6a298
H m
TNewCliente = class…
shell
view.pas model.pas
.git
19) Un nuovo contributor push/pull (same branch, divergent)
shell
view.pas model.pas
e986c9a
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
Origin
e986c9a
e0e5ae3o/m
git clone https://mauriziodm:[email protected]/mauriziodm/delphiday2019.git
MainForm.Caption := ‘Git test’; MainForm.Show;
TNewCliente = class…
M
git commit -a -m “aggiunta classe TNewCliente” git push git pull
e986c9a
e0e5ae3
Staging areamodel.pas
4a6a298
H m
4a6a298
4a6a298
ada55ff
H m
TOtherCliente = class…
git commit -a -m “aggiunta classe TOtherCliente” git push git pull git push
M
Staging areamodel.pas
Divergent
4a6a298
4a6a298
156he1b
4a6a298 ada55ff
ada55ff
156he1b
ada55ff
156he1b
ada55ff
156he1b
ada55ff
156he1b
shell
view.pas model.pas
.git
20) Un nuovo contributor push/pull (same branch, divergent+conflict)
shell
view.pas model.pas
e986c9a
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
Origin
e986c9a
e0e5ae3
o/m git clone https://mauriziodm:[email protected]/mauriziodm/delphiday2019.git
MainForm.Caption := ‘Git test’; MainForm.Show;
TNewCliente = class…
git commit -a -m “aggiunta classe TNewCliente” git push
e986c9a
e0e5ae3
4a6a298H m
4a6a298
ada55ffH m TOtherCliente = class…
git commit -a -m “aggiunta classe TOtherCliente” git push
4a6a298
156he1b
ada55ff
156he1b
ada55ff
156he1b
ada55ff 4a6a298
Conflict
merging commit
… <<<<<<< HEAD TOtherCliente = class… =======TNewCliente = class…
>>>>>>> origin/master …
git pull git add model.pas git commit -m “fatto merge TNewCliente” git push
M
Staging areamodel.pas
ada55ff
156he1b
git pull
156he1b
ada55ff 4a6a298
shell
view.pas model.pas
.git
21) Un nuovo contributor branch separati
shell
view.pas model.pas
e986c9a
MainForm.Caption := ‘Git test’; MainForm.Show;
e0e5ae3
.git
Origin
e986c9a
e0e5ae3
git branch newfeature git checkout newfeature git commit -a -m “nuova classe TNewCliente” git push git checkout master git fetch git pull git merge newfeature git push
MainForm.Caption := ‘Git test’; MainForm.Show;
H
e986c9a
e0e5ae3
TNewCliente = class…
M
f
Staging areamodel.pas
ada55ff
ada55ff
e0e5ae3
4a6a298
ada55ff
o/f
TOtherCliente = class…
M
git commit -a -m “aggiunto classe TOtherCliente” git push git fetch git pull
Staging areamodel.pas
4a6a298
H m
4a6a298
o/m
-1
4a6a298
4a6a298
m
ada55ff 4a6a298
156he1b156he1b
156he1b
-2
ada55ff
156he1b
ada55ff
156he1b
shell
view.pas model.pas
22) Se il contributor non ha i diritti? fork
shell
view.pas model.pas
e986c9a
H m
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
Origin
e986c9a
e0e5ae3o/m
Origin/Fork
e986c9a
e0e5ae3o/m
shell
view.pas model.pas
23) Se il contributor non ha I diritti? Fork: clone locale
shell
view.pas model.pas
e986c9a
H m
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
Origin
e986c9a
e0e5ae3o/m
Origin/Fork
e986c9a
e0e5ae3o/m
git clone https://rinobosco70:[email protected]/mauriziodm/delphiday2019.git
e986c9a
e0e5ae3
e986c9a
e0e5ae3
.git
H m
MainForm.Caption := ‘Git test’; MainForm.Show;
shell
view.pas model.pas
24) Se il contributor non ha I diritti? New Branch (ora possiamo)
Push
shell
view.pas model.pas
e986c9a
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
Origin
e986c9a
e0e5ae3
Origin/Fork
e986c9a
e0e5ae3o/m
e986c9a
e0e5ae3
.git
m
MainForm.Caption := ‘Git test’; MainForm.Show;
f
git branch newfeature git checkout newfeature git commit -a -m “nuova classe TNewCliente” git push
H
TNewCliente = class…
M
Staging areamodel.pas
ada55ffada55ff ada55ff o/f
TOtherCliente = class…
M
git commit -a -m “aggiunto classe TOtherCliente” git push
Staging areamodel.pas
4a6a2984a6a298
4a6a298H m
o/m
shell
view.pas model.pas
25) Se il contributor non ha I diritti? Pull request (su GitHub)
shell
view.pas model.pas
e986c9a
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
Origin
e986c9a
e0e5ae3
Origin/Fork
e986c9a
e0e5ae3o/m
e986c9a
e0e5ae3
.git
m
MainForm.Caption := ‘Git test’; MainForm.Show; f
git branch newfeature git checkout newfeature git commit -a -m “nuova classe TNewCliente” git push
TNewCliente = class…ada55ff ada55ff o/f
TOtherCliente = class…
git commit -a -m “aggiunto classe TOtherCliente” git push
4a6a298
4a6a298
H m
o/m
Pull request
ada55ff
ada55ff
2f57bh6 2f57bh6
2f57bh6
M
Staging areamodel.pas
property Age: integer read FAge;
git commit -a -m “aggiunto proprietà age” git push
2f57bh6
H
Origin
Merge
shell
view.pas model.pas
26) Se il contributor non ha I diritti? Pull request: merge a cura del manteiner
shell
view.pas model.pas
e986c9a
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
e986c9a
e0e5ae3
Origin/Fork
e986c9a
e0e5ae3o/m
e986c9a
e0e5ae3
.git
m
MainForm.Caption := ‘Git test’; MainForm.Show;
f
TNewCliente = class…ada55ff ada55ff
o/f
TOtherCliente = class…4a6a298
4a6a298
H m
o/m ada55ff
Pull request
ada55ff
2f57bh6 2f57bh6
2f57bh6
2f57bh6
M
property Age: integer read FAge;
ada55ff
2f57bh6
156he1b
4a6a298
2f57bh6
Conflict ???
H
shell
view.pas model.pas
27) Se il contributor non ha I diritti? Pull request: merge a cura del contributor
shell
view.pas model.pas
e986c9a
MainForm.Caption := ‘Git test’; MainForm.Show
e0e5ae3
.git
Origin
e986c9a
e0e5ae3
Origin/Fork
e986c9a
e0e5ae3o/m
e986c9a
e0e5ae3
.git
m
MainForm.Caption := ‘Git test’; MainForm.Show;
f
git remote add upstream https://github.com/mauriziodm/delphiday2019.git git fetch upstream git merge upstream/master git push
H
TNewCliente = class…ada55ff ada55ff
o/f
TOtherCliente = class…
git fetch git pull
4a6a298
4a6a298
H m
o/m
Pull request
ada55ff
2f57bh6 2f57bh6
2f57bh6
M
property Age: integer read FAge; 4a6a298
4a6a298
u/m
156he1b
H
4a6a298
2f57bh6
Conflict ???
4a6a298
156he1b
4a6a298
156he1b
156he1b
ada55ff
2f57bh6
156he1b
ada55ff
2f57bh6
156he1b
-3
ada55ff
2f57bh6
156he1b
ada55ff
2f57bh6
156he1b
Maurizio Del Magno
DOMANDE?
https://github.com/delphiforce/eInvoice4DeInvoice4D
Maurizio Del MagnoDeveloper
speaker: Maurizio Del Magno
i-ORM
DJSONgithub.com/mauriziodm/iORM
github.com/mauriziodm/DJSON
facebook.com/maurizio.delmagno
iORM + DJSON (group)
levante software
Membro fondatore
Grazie!!!