build software better, together - delphiday.it · mainform.caption := ‘git test’; git init git...

49
speaker: Maurizio Del Magno Build software better, together git

Upload: lykiet

Post on 13-Aug-2019

271 views

Category:

Documents


0 download

TRANSCRIPT

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)

[email protected]

[email protected]

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)

[email protected]

[email protected]

levante software

Membro fondatore

Grazie!!!