fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
8 minut

W czym Git jest lepszy od TFS?


18.01.2012
[ uwaga: w tym poście piszę o aspekcie kontroli wersji w TFS, a nie o TFS jako całym kombajnie do zarządzania projektem; powinno to być oczywiste, ale mimo wszystko zaznaczam żeby nie było zażaleń ]

Programiści znający oba rozwiązania, zobaczywszy tytuł posta, mogą się tylko uśmiechnąć i mruknąć: “a o czym tu w ogóle pisać? we wszystkim!“.

Jednak osoby znające TYLKO TFSa nie chcą, złośliwcy, wierzyć na słowo dopóki nie zobaczą. No cóż, nie będą błogosławieni, ich strata. Postaram się jednak spisać tutaj kilka rzeczy, które być może zaciekawią co bardziej otwarte umysły i skłonią do zainteresowania alternatywą dla ich ukochanego molocha. Nie jest to bynajmniej pełna lista, a raczej zrzut mojego wewnętrznego RAMu po kilkunastu minutach krótkiej refleksji.

DVCS

Wiele z przewag Gita nad TFS wynika z zasadniczych różnic pomiędzy scentralizowanym a zdecentralizowanym podejściem do kontroli wersji. Więcej na ten temat pisałem w postach Git – rozproszony system kontroli wersji oraz Dlaczego już nie lubię SVN. Zapraszam również tam, a póki co…

Praca offline

Wiem, że TFS oferuje koncepcję “pracy offline”. Ale co to za praca? Wielkiej łaski VS mi nie robi że pozwoli zmodyfikować pliki jeśli nie mam połączenia z internetem – tego by jeszcze brakowało, żebym nie mógł tego robić! Nie ma to zbyt wiele wspólnego z kontrolą wersji. Pracując z DVCS mam LOKALNIE dostęp do całej historii. Mogę więc przeglądać commity, robić diffy, chodzić między wersjami, czytać logi – wszystko to bez jakiegokolwiek kontaktu ze zdalnym serwerem. Oprócz samego faktu, że mogę to robić offline, bardzo ważna jest też jeszcze jedna kwestia: WYDAJNOŚĆ. Tego nie da się porównać. Podczas gdy TFS musi skontaktować się z serwerem aby wykonać prawie każdą operację, Git ma wszystko czego potrzebuje u mnie, na mojej maszynie. Jedyne co może ograniczać wydajność takiego rozwiązania to prędkość dysku twardego, która przy coraz większej dostępności SSD powoli staje się czynnikiem pomijalnym.

Lokalne commity

Ten punkt można równie dobrze wciągnąć pod poprzedni akapit, ale jest tak zajebiście istotny, że potraktuję go osobno. Jeśli się tego wcześniej nie doświadczyło to naprawdę trudno jest sobie wyobrazić swobodę i komfort, jaka za tym idzie. Równie trudno jest mi teraz wyobrazić sobie pracę bez takiej możliwości. Mogę robić commity choćby co pięć minut – więc moja working copy jest prawie cały czas “czysta” a stan lokalnego repozytorium – zdatny do użytku. Dzięki temu koniec z problemami typu “dobra, coś spieprzyłem, jak teraz wrócić do stanu, który działał?“. Teraz nie jest to problemem – wiem, że commit z działającym kodem  zrobiłem chwilę temu, zatem mogę spokojnie powrócić do tej wersji i zacząć od początku. To jak nieskończona liczba żyć w grze komputerowej, albo cheat włączający god mode. Mało tego – wcale nie muszę tych wszystkich commitów synchronizować z całym zespołem! Po zakończeniu pracy nad jakimś ficzerem poświęcam kilka chwil na analizę swojej pracy (zorganizowanej w niewielkie commity) i tak nimi manipuluję, żeby ich historia i zawartość nie śmieciła w repozytorium, jednocześnie zachowując całą drogę jaką przebyłem od “nie działa” do “działa”. I taką zmodyfikowaną historię wysyłam w miejsce “kontaktu” z resztą dev-teamu.

Nie można mylić tego z funkcjonalnością “shelve” oferowaną przez TFS. Podobna rzecz jest zawarta również w Gicie (“stash”), ale to zupełnie inna koncepcja.

Pliki readonly? NIE!

Git nie traktuje naszego kodu jako “solucji-polucji” edytowalnej z jednego-li tylko, właściwego narzędzia. Git traktuje całe rozwiązanie jako zera i jedynki porozrzucane między plikami. Możemy więc edytować co chcemy i jak chcemy, a Git sam zobaczy co i jak zostało zmodyfikowane. Bez ograniczeń. Mało tego – Git jest na tyle sprytny, że sam wykryje taką choćby operację jak “rename”!

Checkout / exclusive lock? STOP!

Wiem, że niektóre zespoły zmuszone są przez TFS (a raczej jego prymitywny, niedziałający, żenujący merge) do stosowania “exclusive locks”, czyli tylko jedna osoba może pracować nad danym plikiem. Przecież to jest ograniczenie tak durne i tak niesamowicie nielogiczne! Co komu do tego nad czym pracuję? Co mi do tego który plik zmienia ktoś inny? A co jeśli jest to .csproj, albo jeśli robię refactoring dotykający kilkunastu klas? Cały zespół ma czekać aż skończę? Albo, co gorsza, ja mam czekać aż ktoś coś skończy?;) Niech każdy zajmie się swoją robotą, a system kontroli wersji niech domyśli się jak z tym sobie poradzić i pomoże nam w połączeniu zmian.

W Gicie nie jest to problemem – algorytm merge’owania zmian jest tak dobry, że bardzo sporadycznie zachodzi konieczność ręcznej manipulacji plikami przy tym procesie. A nawet jak już zachodzi to bez większego problemu można z tym sobie poradzić.

Czyż nie lepiej jest zająć się swoją robotą zamiast czekać aż kolego z biurka obok (jak mamy szczęście) lub z innego kontynentu (jak mamy pecha) “zwolni” plik?

Branche? TAK!

Gałęzie w TFS traktowane są bardziej jako narzędzie do oznaczania “wersji na produkcji”. Czyli przed wdrożeniem robi się nowy branch, a cały zespół dalej jedzie na jednej głównej gałęzi. Dlaczego? Bo w TFS nie działa merge, przez co programiści nie mają nawet możliwości eksplorować niezwykłej potęgi jaką daje bezbolesne rozgałęzianie kodu.

A co powiecie na lokalne branche? Typowy workflow u programisty pracującego z Gitem wg zasady “branch per feature” wygląda tak: zrób gałąź -> pracuj -> ściągnij najnowszą wersję gałęzi głównej -> połącz swoją gałąź z gałęzią główną -> wypchnij zmiany na zewnątrz. Przy czym krok 2) może być przerwany przez najróżniejsze “wrzuty”. Wyobraźmy sobie, że implementuję jakiś fajny ficzer, aż tu nagle przychodzi zgłoszenie: na produkcji wyskoczył błąd, masz rzucić wszystko i się nim zająć! W Gicie po prostu wracam do głównej gałęzi, zostawiając całą swoją pracę w “feature branch”, robię co jest do zrobienia, i ponownie przełączam się do przerwanej pracy. Nie mówcie że nie znacie tego z autopsji.


To tylko wierzchołek góry lodowej, bo możliwości Gita są po prostu ogromne. I TFS nie jest w NICZYM lepszy (nie zaryzykuję stwierdzenia, że jest gorszy  we WSZYSTKIM).

Naprawdę zachęcam do bliższego poznania Gita. Na blogu pisałem o nim kilkukrotnie pokazując co fajniejsze ficzery (jak modyfikacja historii czy bisect), w necie też jest tego sporo… I wkrótce kolejne kilka słów z mojej strony na ten temat, ponownie w kontekście TFSa.

Teraz chętnie poczytam kolejne komentarze w stylu “używam TFSa i nic z tego co napisałeś nie jest mi potrzebne“. 3, 2, 1 GO!

A tak na serio: jeśli masz, Czytelniku Najsłodszy, ochotę coś takiego napisać, to zrób mi tę przyjemność i naprawdę daj, choćby na kilka dni, szansę Gitowi. Obiecuję: jeśli podejdziesz do tego z odpowiednim nastawieniem (czyli innym niż “udowodnię sobie i wszystkim że TFS mi wystarcza“) – nie zawiedziesz się, a Twój codzienny komfort wzrośnie. Czyż nie na tym m.in. polega nasza praca – szukaniu i stosowaniu coraz to lepszych rozwiązań?

P.S. Oczywiście (prawie) wszystkie powyższe punkty znajdują zastosowanie nie tylko w konfrontacji Git-TFS, ale także Mercurial-TFS czy Git-SVN. Jednak moja obecna sytuacja opiera się na tych dwóch narzędziach, więc i na nich się skupiam.

0 0 votes
Article Rating
24 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
h-one
h-one
12 years ago

Jeśli GIT tylko do powyższych, jaki system do zadań, jaki do raportów, jaki do CI i jaki do automatyzacji testów byś polecił z boku?
Jakiś best practice jak to wszystko ze sobą połaczyć w spójnie działający organizm?

emdzej
emdzej
12 years ago

Od siebie mogę dodać tylko tyle, że zgadzam się z Twoim punktem widzenia w 1000%. Niestety jak spoglądam na TFS to widzę VSS na sterydach. W pracy niestety mam coś jeszcze gorszego – Perforce. Nie umiem opisać przyzwoitymi słowami tego systemu kontroli wersji.
Na git’a przestawiłem się jakiś czas temu. Nie żałuję.
Ostatnio zacząłem "bawić" się kodem androida – git w połączeniu z narzędziem "repo" też jest fajny :)

procent
12 years ago

h-one,
Do wszystkiego innego może zostać TFS, o tym prawdopodobnie w poniedziałek.
Mi najfajniej sprawdzał się jednak składak: git + team city + redmine. Pisałem o tym np tutaj http://www.maciejaniserowicz.com/?tag=/teamcity czy tutaj http://www.maciejaniserowicz.com/?tag=/vipserv . Tak jak wspominałem wcześniej, TFS ma najbardziej rozbudowane raporty i nie wiem czy coś podobnie zaawansowanego da się osiągnąć w alternatywach. Podejrzewam że tak, tylko pewnie wymagałoby to precyzyjnego zdefiniowania swoich potrzeb i pogrzebania się w panelach administracyjnych… ale tego nie robiłem.

procent
12 years ago

emdzej,
Bo TFS to JEST VSS na stedyrach:). Nawet na jakimś blogu podlinkowanym w komentarzu przez kogoś pod ostatnim postem była informacja, że do dzisiaj diff tool jest ten sam od połowy lat ’90.
Z Perforce do czynienia nie miałem i w sumie dobrze.

tomek
tomek
12 years ago

Czy do gita jest darmowe narzedzie typu Tortoise ?

procent
12 years ago

tomek,
Tak, o jakże wdzięcznej nazwie TortoiseGit:). Podobnie zresztą jak TortoiseHG.

Pawel Klimczyk
12 years ago

Ja uzywam Gita do robienia backupu dokumentow nawet :)

Arkadiusz Waśniewski
Arkadiusz Waśniewski
12 years ago

Pierwsza zasada korzystania z TFS – zastąp wbudowany program do mergowania aplikacją KDiff3.

Marek
Marek
12 years ago

Jest w tym sporo racji, ale nie zgadzam się ze wszystkim. Po pierwsze, TFS jest narzędziem które ma się dać zastosować do pracy w bardzo dużych zespołach, na projektach które mają tyle plików, że ledwo się na dysku mieszczą. Dlatego np. TFS wymaga robienia checkout na pliku przed edycją (żeby nie musiał za każdym razem skanować wszystkich plików, żeby odkryć co się zmieniło – a tak zwykle robią inne systemy kontroli wersji). Poza tym, uwielbiam feature Gated Checkin, który nie pozwala zacommitować kodu który się nie kompiluje, nie przechodzi testów, StyleCopa, FxCopa, itp. Kolejna rzecz to wygodna integracja checkin’ów z taskami i bugami, wbudowana w VS. To są przykłady feature’ów typu ‘enterprise’ dla dużych organizacji, podobnie jak rozwiązania w Perforce (też na nim pracowałem…). Mają one swoje uzasadnienie, chociaż nie zawsze są wygodne. Ale fakt faktem, kontrola wersji w TFS jest jeszcze niedopracowana.
Co do DVCS, uważam że jest to świetne rozwiązanie, w szczególności do lokalnej/jednoosobowej kontroli wersji, aczkolwiek preferuje Mercuriala od Gita. Na TFS najlepiej się pracuje mając dodatkowo drugi rozproszony system kontroli wersji lokalnie (w tym samym folderze co TFS) – ja np. używam TFS i Mercuriala. Wtedy ma się najlepsze z dwóch światów! Lokalnie można robić branche, merge, itp w wygodny sposób w DVCS, a gotowy kod się wrzuca do scentralizowanego TFSa. Działa to także na innych scentralizowanych systemach kontroli wersji. Osobom, które lubią DVCS, a są zmuszone do pracy na TFS/SVN polecam takie rozwiązanie.

procent
12 years ago

Arkadiusz,
Tak, mam to w notatkach do posta na przyszły tydzień.

procent
12 years ago

Marek,
Git powstał po to, żeby zarządzać jądrem linuxa. To chyba spory projekt z dużym zespołem:).
Gated check-in – to można zasymulować choćby w team city tworząc osobne projekty dla różnych gałęzi gita. Ale nie będę się spierał że nie jest to fajne rozwiązanie:).
Integracja z taskami – większość znanych mi systemów do project management na to pozwala. Dla mnie alternatywny sposób jest wygodniejszy niż klikanie w TFS – zawarcie ID taska w commit msg.
Co do zaproponowanego rozwiązania – podpisuję się oboma rencyma. O tym właśnie będę pisał następnym razem.

slawek
slawek
12 years ago

Ale checkout/exlusive locka nie musisz mieć domyślnie włączonego – a wręcz lepiej nie mieć. Natomiast bardzo przydaje sie jezeli uzywasz jeszcze DataSetów (lub innych dużych XMLi) i nie chcesz ich mergować. Wtedy możesz sobie założyć LOCKa i nikt inny nie bedzie mógł wycheckoutować tego pliku. W takich zastosowaniach jest to idealne i rachej w DVCS tego nie osiągniesz.

Peter_lin
12 years ago

Jej, jak fajnie, że ludzie powoli zaczynają odchodzić od TFS i SVN na rzecz systemów rozproszonych do zarządzania kodem. Nie czuję się już takim outsiderem i rewolucjonistą przekonując na siłę innych (m.in. współpracowników), że TFS to gówno ;]
Ale przy okazji miałbym pytanie: napisałeś Maćku, że korzystasz przede wszystkim z Gita jeśli chodzi o system kontroli wersji oraz zakładam, że na Windowsie. Jakich wobec tego narzędzi do Gita używasz? Tylko konsoli, czy też da się już korzystać normalnie z TortoiseGit, czy coś jeszcze innego polecasz?
Jak też sprawa wyglądała z współpracownikami i ich mentalnością?
Muszę przyznać, że ja wywalając TFS z naszej firmy, chciałem zastąpić go Gitem właśnie, ale z powodu braku dobrych narzędzi musiałem zostać przy Mercurialu (który też jest świetny, ale z perspektywy osoby zarządzającej nim ma kilka wad). Nikt z moich współpracowników nie brał pod uwagę używania konsoli do obsługi source controla; tylko i wyłącznie narzędzia z GUI oraz najlepiej zintegrowane z VS oraz najlepiej takie, które działają dokładnie tak jak TFS (sic! – ach ta siła przyzwyczajeń).
I jeszcze jedno: może i DVCS są super, ale w kwestii administracyjnej najlepiej repozytoria główne trzymać jak najdalej od systemów bazowanych na Windowsie (przynajmniej ja miałem z konfiguracją środowiska z repozytorium centralnym do Hg dużo problemów i w dalszym ciągu jest mu daleko do tego jak powinno wyglądać, ale fakt faktem nie jestem administratorem ;]) W Twoim zespole korzystacie z repo centralnego udostępnianego przez jakiegoś GitHuba, czy na własnym serwerze Linuxowym/Windowsowym? :>
Uch, więcej niż 1 pytanie wyszło i do tego trochę przydługo xD

procent
12 years ago

Peter_lin,
Wydaje mi się że nigdy nie byłeś outsiderem i rewolucjonistą przekonując ludzi że TFS to gówno, bo o tym jest głośno od 2005 A.D. :)

Do Gita używam git bash oraz (sporadycznie) tortoise – głównie do diff/log, bo wygodniej mi w tym niż w gitk. Kiedyś przez chwilę próbowałem git extensions dla vs, ale po 1 czy 2 dniach wywaliłem… było to z półtora roku temu. Bardzo przyzwyczaiłem się do tego że IDE jest do kodowania, przeglądarka do proj mgmt a konsola do version control, to dla mnie idealne rozłożenie odpowiedzialności, a nie jeden wielki kombajn.
Dodatkowo polecić mogę ewentualnie swój skrypt autohotkey dla gita, preferuję to niż gitowe aliasy: http://www.maciejaniserowicz.com/post/2010/02/16/Skrypt-AutoHotkey-ulatwiajacy-prace-z-Git.aspx .

Aktualni współpracownicy i ich mentalność nie są jeszcze na razie zbytnio przeze mnie atakowani, do zespołu dołączyłem raptem półtora tygodnia temu, więc za wcześnie na rewolucje;). Więc jest TFS.

Wcześniej jednak pracowałem i z Gitem i z HG. Dla obu miałem w domu zwykły network share – to było moje domowe repo (do komunikacji host <-> wirtualki). A centralny punkt dla całego zespołu siedział na (w zależności od projektu): vipserv, bitbucket, repositoryhosting.com oraz codebasehq (chyba że coś jeszcze pominąłem, ale to nie ma znaczenia bo wszędzie działało niezawodnie). Administracją tego nigdy się nie zajmowałem i mam nadzieję, że nigdy zajmował się nie będę. Nie wiem zresztą po co trzymać to lokalnie skoro w bitbucket można hostować prywatne projekty i git i hg za darmo, a nawet płatny hosting gdziekolwiek kosztuje grosze.

Waldi
Waldi
12 years ago

No właśnie problem z git-em jest taki że "learning curve" jest duży i dla wielu developerów praca z "command line" jest nie do zaakceptowania, dopóki nie pojawią się dobre narzędzia GUI (najlepiej zintegrowane z VS) to będzie problem.

Zgadzam się że w TFS-ie fajne wybieranie taska/buga przy check-in, które można jeszcze wymusić przez checkin policy jest to dużo lepsze jakieś "ręczne wpisywanie id taska".

Zaletą TFS-a jest że wszystko jest zintegrowane "out-of-the-box", chociaż poszczególne elementy (kontrola wersji, bug tracking) są gorsze niż ich samodzielne odpowiedniki.

Maciek, dzięki za Twoje poprzednie wpisy o git, zmobilizowały mnie do uczenie się tego narzędzia, na razie dla prywatnych projektów.

procent
12 years ago

Waldi,
Ależ wcale nie trzeba pracować z cmdline! Tak jak pisałem: jest tortoise, są nawet git extensions to vs. Też bardzo sceptycznie podchodziłem do koncepcji pracy z linią komend, jestem tak samo upośledzony jak większość programistów pracujących na windows. Okazało się to tylko po prostu naprawdę świetnym i megasprawnym doswiadczeniem.
Powodzenia w nauce:).

unodgs
unodgs
12 years ago

Do gita jest dużo nakładek GUI. Jest aplikacja rozpowszechniana razem z instalatorem (ale taka sobie). IDE takie jak Netbeans, InteliJIdea, Eclipse, PHPStorm same z siebie obsługują gita w bardzo wygodny sposób. Są aplikacje na Linuxa (QGit), Maca (Git nub, GitX). Na Windowsa osobiście polecam SmartGIT (który od wersji 3.0 dodatkowo obsługuje Mercuriala). No i jest TortoiseGit (ale mi średnio się podoba). Do visual studio jest chyba coś takiego jak git extensions. Tak więc nie jest tak źle i na pewno aplikacji tego typu będzie przybywać.

procent
12 years ago

unodgs,
Tak jest, narzędzi przybywać będzie na pewno. Szczególnie że niedawno obiło mi się na twitterze o oczy, że Phil Haacked pracuje w githubie nad "oficjalnym" serwerem git dla windowsa. Myślę że będzie tylko lepiej:).

pawelek
12 years ago

Drobne uwagi – git rozpoznaje rename tylko wtedy gdy zawartość pliku nie zmieni się znacznie. Git jest potężniejszy od TFS, ale w TFS podoba mi się wymiana zmian przez shelva. W Gicie chyba nie jest to ogarnięte w pełni (albo za słabo znam Gita co też jest możliwe).
TortoiseGit nadaje się nie tylko do przeglądania histori, logów, refloga. Dodatkowo całkiem nieźle wspiera bitsecta, spokojnie można w nim commitować zmiany (wpisywać komentarz etc, operować na plikach w wygodnym interfejsie). Można pushować zmiany do serwera. Trzeba uważać na fetch+merge i kilka innych rzeczy. Czasem commity potrafiły zniknąć, ale te bugi zostały już w większości poprawione. Te rzeczy stosowałem i działają nieźle.
Teraz używam tylko konsoli jako mimo wszystko najwygodniejszego rozwiązania :) No i TFS w pracy od 2 tygodni.

procent
12 years ago

pawelek,
No tak, rename będzie rename jeśli zawartość pliku nie zmieni się znacznie – bo w przeciwnym wypadku to już nie będzie rename!
W Gicie możesz wymieniać zmiany na wiele sposobów, z których 3 przychodzące mi do głowy to: bezpośrednie dodanie repo kolegi jako remote, wymiana przez centralne repo w dedykowanym do tego branchu, wysłanie patcha (jak zauważyłeś w poprzednim poście).
TortoiseGit jak najbardziej nadaje się do prawie wszystkiego, ale mi strasznie odpowiada git bash.

pawelek
12 years ago

Co do wymiany zgadzam się, aczkolwiek wysyłanie patch jest cokolwiek niewygodne, a reszta wymaga konfiguracji – jest skomplikowana. Używanie shelve ma plusy wysyłania patcha i jest prostsze, i szybsze. To tak tylko pocieszająco dla zwolenników TFS :) I uwaga dla Tworzących gita – mogli by zrobić coś jak shelve i byłoby po sprawie.

Maciek
12 years ago

Ja co prawda preferuję Mercuriala od Gita, ale zgadzam się z większością głosów, że jak tylko załapie się ideę DVCSów, to już nie ma powrotu do SVN, TFS, czy innych wynalazków (choć mimo wszystko doceniam perforce, za jego obsługę wielkich plików binarnych i darmowego diff/merga, którego używam naprzemiennie KDiff3).

Jeśli chodzi o Git i VS, to ja polecam nie Git Extensions, ale raczej "Git Source Control Provider" (http://visualstudiogallery.msdn.microsoft.com/63a7e40d-4d71-4fbb-a23b-d262124b8f4c), który jest dostępny z poziomu Visual Studio przez Extensions Manager – zarówno dla VS 2010, jak i VS 11. Porównanie popularności obu rozszerzeń na Visual Studio Gallery mówi zresztą samo za siebie :)

Jak duża część devów pracujących na Windows, nie byłem specjalnie przekonany do konsoli (aczkolwiek nadal używam rozszerzeń do VS – bo tak mi wygodniej widzieć natychmiast zmiany i mieć możliwość natychmiastowego zrobienia diffa z poziomu VS). Natomiast po pewnym czasie doszedłem do wniosku, że dobrze skonfigurowana konsola (wszystkie ustawienia typu aliasy, edytor do commitów itp.) jest niesłychanie wygodna. A Posh-Git (http://haacked.com/archive/2011/12/13/better-git-with-powershell.aspx) to już ogólnie bajka (wolę na windows pracować z użyciem PowerShella niż w gitowym bashu na windows)

Pero
Pero
12 years ago

> Bo w TFS nie działa merge, przez co programiści nie mają nawet możliwości eksplorować niezwykłej potęgi jaką daje bezbolesne rozgałęzianie kodu.

Merge w TFSie nie działa – mógłbyś rozwinąć temat ? Wytłumacza mi jak utrzymujemy duży komercyjny produkt, który codziennie wymaga mergy między produkcją-testem-deweloperką ?

procent
12 years ago

@Pero,
Na prośbę "Wytłumacza mi jak utrzymujemy duży komercyjny produkt, który codziennie wymaga mergy między produkcją-testem-deweloperką" odpowiedzieć mogę tylko: "nie mam pojęcia, ale szczerze współczuję". Zobacz jak to wygląda w alternatywnym rozwiązaniu, serio.

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również