Trunk based development - hit, czy kit?

0

Czytam sobie ostatnio na temat dlaczego gitflow bee trunk cacy i zastanawiam się jakie jest wasze podejście. Czy stosujecie krótko żyjące brancze i czy ograniczenie problemów z merge jest warte zapłacenia ceny w postaci niedokończonego/niepotrzebnego kodu na produkcji?

2

A czemu niby miałoby to prowadzić do niedokończonego/niepotrzebnego kodu na produkcji w większym stopniu niż GitFlow? Mój główny problemem z GitFlow jest taki, że bezsensownie mnoży byty nad potrzebę:

  • Skoro każdy commit na master/main jest otagowany, to po co trzymać develop osobno? Nie można mieć jednej gałęzi master/main i na niej tagować release?
  • Tworzenie nowej gałęzi dla każdego release również nie ma większego sensu. Tworzymy gałąź, na której "zamrażamy" release, dajemy tam taga, a następnie mergujemy do master/main i dzięki temu mamy git describe, które faktycznie działa.
0

A te krótko żyjące brancze to ile żyją?
Jeśli tester testuje na trunk/master/develop bo nie mamy środowiska pod testowanie na feature branchach (tylko review i testy integracyjne na nich są) to to jest jeszcze git flow czy już Trunk based development?

0
hauleth napisał(a):

A czemu niby miałoby to prowadzić do niedokończonego/niepotrzebnego kodu na produkcji w większym stopniu niż GitFlow?

Brak ficzer branczy. Jeżeli dobrze rozumiem jak ma to działać to wygląda to tak: Masz do napisania jakiegoś ficzera powiedzmy na 2 tygodnie roboty.
W gitflow robisz sobie ficzer brancza i wrzucasz na niego możliwie drobne zmiany, na koniec integrujesz z develop, przeprowadzasz testy, jak przejdą, to dorzucasz do develop.

Trunk - każdą zmianę wprowadzasz bezpośrednio do głównej gałęzi kodu, więc w czasie o rozpoczęcia pracy nad ficzerem, co jej zakończenia masz na głównym branczu nie używany kod, bo ficzer nie jest skończony i dla użytkownika zostaje wyłączony na poziomie konfiguracji (feature switch). Kod musi być w każdym momencie gotowy do release.

Czyli w przypadku trunk based odpuszcza się to co było ideą gitflow, czyli brak niedokończonego kodu w artefaktach produkcyjnych.

1

Jeżeli dobrze rozumiem jak ma to działać to wygląda to tak: Masz do napisania jakiegoś ficzera powiedzmy na 2 tygodnie roboty.

Wołam @UglyMan żeby wytłumaczył ci że nie możesz mieć ficzera powiedzmy na 2 tygodnie roboty. Że to trzeba rozbijać na mniejsze zadania. A jak masz mniejsze zadania to częściej merge'ujesz do mastera i problemy nieświeżych feature branchy odpadają

3

Przede wszystkim to trunk-based development (czy też feature branche, jak kto woli) kładzie na prostotę, i ja jestem zdecydowanym zwolennikiem zaczynania właśnie od tego. Mamy główny branch (master, chyba że jesteś zbyt czuły i łatwo się obrażasz to nazywasz go inaczej co by nie ranić serduszek innych ludzi), programista zaczynając pracę nad ticketem/story tworzy swój branch z najnowszej wersji mastera i na nim pracuje. Następnie kiedy ma już gotowe zmiany (i idealnie sprawdzone przez inne osoby z zespołu) to merguje się to z powrotem. Tutaj znów jest zwolennikiem robienia rebase ze squashem lokalnych commitów, dzięki czemu historia na masterze jest liniowa, nie ma odgałęzień a na każdy ticket przypada jeden commit.

Czy stosujecie krótko żyjące brancze i czy ograniczenie problemów z merge jest warte zapłacenia ceny w postaci niedokończonego/niepotrzebnego kodu na produkcji?

To tak nie działa, chyba nie do końca rozumiesz na czym polega trunk-based development. Oczywiście że na produkcję czy w ogóle na master nie wrzuca się niedokończonego kodu, chyba że stosuje się feature toggles ale to już nie ma nic wspólnego z konkretnym system kontroli wersji.

1

Jak masz do zrobienia zadanie typu możliwość drukowania jakiegoś raportu, to zanim nie skończysz wszystkiego to tego raportu wydrukować się nie da. Więc możesz sobie dzielić jak chcesz, a ostatecznie na produkcji może znaleźć się kod, który jeszcze nie jest wykorzystywany.

@piotrpo: to przy takiej definicji git flow miałem w wielu firmach zakaz pracy w git flow :D przede wszystkim branche miały być małe, żeby dało je się przejrzeć w skończonym czasie. To czy w ostetecznym produkcie jest trochę nieużywanego kodu nie powinno nikogo interesować. I oczywiście jest tam mnóstwo nieużywanego kodu, bo dołączamy gigantyczne biblioteki, żeby wywołać jedną funkcję. No chyba, że pisze się w JS, tam każda funkcja jest w osobnej bibliotece :D

1

@piotrpo: nie zgodzę się. "Krótkość" życia gałęzi jest bardzo zależna od projektu. Ja osobiście nie widzę problemu by przy większej zmianie stworzyć osobną gałąź żyjącą na czas tego jednego feature do której są dokładane kolejne gałęzie. Jednak w 99% przypadków (IDZD) jeśli coś takiego jest potrzebne, to znaczy, że coś albo z opisem wymagań, albo ze stanem projektu, jest mocno nie tak.

2

A to nie jest tak, że wielkość zespołu, ilość zespołów, złożoność produktu, ilość/częstość dostarczanych ficzerów, releasów będą wpływać na wybór strategii zarządzania kodem źródłowym?
I dyskusja czy coś w ogólności jest kitem/hitem doprowadzi strony do rozejścia się do narożników? ;-)

0

@hauleth: Jeżeli ficzer to coś, co ma "dostarczyć wartość (>0) biznesową", to nie ma cudów - czasami będzie to trwać, bo np. biznes życzy sobie wysyłania maili w jakiś wypadkach a tu wszystko daleko w polu, trzeba sendgrida postawić, jakieś kolejki poefiniować, dorobić wychwytywanie przypadków do wysyłania itd. Oczywiście każdą z tych rzeczy można sobie wyciągnąć do osobnego taska, rozbić na kilka commitów, ale ostatecznie gdzieś ten kod "w produkcji" musi trafić. W gitflow jest to jakiś ficzer brancz, w TBD main line.

Ja nie dyskutuję z tym, że zadania trzeba dzielić do drobne kawałki. Tylko nie zawsze ten drobny kawałek będzie dowoził coś widocznie użytecznego.

@yarel: "To zależy" jest dość trafną odpowiedzią na większość nieco złożonych problemów.

5

A co, jeśli na masterze znajdzie się kompletny i dokończony ficzer, ale nie będzie przez nikogo używany bo ktoś go źle zaprojektował?

No przed tym nie uratuje żaden gitflow, trunk based ani nic innego, a i tak się może zdarzyć - i ludzie z tym żyją :P

W trunk-based development nie ma 40 branchy to ogarniania / synchronizowania między sobą i nie ma (zwykle) problemów jak np. PRy na 15k linii zmian w 80 plikach. Nie ma rozwiązywania konfliktów przez dwa tygodnie, żeby po dwóch tygodniach rozwiązywać nowe konflikty przez następny tydzień. Wychodzę z mastera / maina z króko żyjącym (na czas jednego taska) feature branchem, po zatwierdzeniu zmian robię rebase + squash i tyle. Jeśli leci release z danego commita, to można go otagować i tyle.

0
superdurszlak napisał(a):

króko żyjącym (na czas jednego taska) feature branchem

Czas jednego taska to u ciebie zazwyczaj kilka godzin, dni, tydzień?
Czy efekt taska jest widoczny dla użytkownika końcowego? Czy regularnie zdarzają się taski "zrób coś tam, żeby w przyszłości dało się zrobić coś przydatnego"?

4
piotrpo napisał(a):

Czas jednego taska to u ciebie zazwyczaj kilka godzin, dni, tydzień?
Czy efekt taska jest widoczny dla użytkownika końcowego? Czy regularnie zdarzają się taski "zrób coś tam, żeby w przyszłości dało się zrobić coś przydatnego"?

Tak / nie / nie wiem / to zależy / różnie.

Jeśli to niczego nie psuje, to co za różnica? Jeśli nawet dodam zmiany, które nie wpłyną od razu na użytkownika końcowego - to tak właściwie co z tego? To mógł być task przygotuj codebase pod takie i siakie zmiany a mógł być dodaj logi bo nie wiemy co się dzieje jak jest pożar - z punktu widzenia użytkownika końcowego efekt jest widoczny tak samo, czyli wcale.

4
piotrpo napisał(a):

Czy efekt taska jest widoczny dla użytkownika końcowego? Czy regularnie zdarzają się taski "zrób coś tam, żeby w przyszłości dało się zrobić coś przydatnego"?

Czy użytkownik końcowy wie, czym są taski, kod źródłowy, i na czym polega zarządzanie kodem źródłowym?
Jeśli nie - to jakie to ma dla niego znaczenie, co się znajduje w kodzie.
Jeśli tak - to daj mu klawiaturę, i niech sobie sam napisze.

Git Flow to jakiś wynalazek germańskich architektów, którzy widząc zbyt szybki system kontroli wersji postanowili spowolnić go biurokracją.

0
somekind napisał(a):

Czy użytkownik końcowy wie, czym są taski, kod źródłowy, i na czym polega zarządzanie kodem źródłowym?
Jeśli nie - to jakie to ma dla niego znaczenie, co się znajduje w kodzie.

Jeżeli kod, który napisałeś i wszedł do wersji produkcyjnej nie jest podpięty do UI/API to nie da się go przetestować z tego poziomu, w tym napisać testów funkcjonalnych, mniejsza czy w postaci scenariusza, czy automatu. Można się jedynie starać, by takie testy powstały przed udostępnieniem funkcjonalności użytkownikowi, po przełączeniu feature switch. W dodatku w tej chwili nie za bardzo widzę możliwość automatyzacji takiego działania. Feature switch jest realizowane na poziomie buildu / osobnej usługi a informacja, czy feature (kompletna funkcja biznesowa) został zakończony w dżirze.

Staram się być otwarty na ten pomysł (Trunk Based Development), próbuję do niego jednak podejść krytycznie i przemyśleć jego wady zanim potencjalnie zacznę go wdrażać.

Git Flow to jakiś wynalazek germańskich architektów, którzy widząc zbyt szybki system kontroli wersji postanowili spowolnić go biurokracją.

Bez przesady. To nie oni go wymyślili. Merge Miał też odpowiedzieć na dość konkretne wyzwania. To, że ma też swoje wady jest jasne, jak również to, że nie do końca jest zgodny z aktualnym podejściem do tworzenia kodu, jest dość oczywiste.

0

@ProgScibi: W TBD nie masz współdzielonych ficzer branczy. Jak coś trafia do mainline, to ma to być kod gotowy na produkcję. Nie masz dodatkowego procesu release, czyli zrobienia brancza release, pospolitego ruszenia testerów itd. Na ile rozumiem sens tego podejścia, pożądane jest automatyczne wypychanie zmian na produkcję możliwie często.

5
piotrpo napisał(a):

@ProgScibi: W TBD nie masz współdzielonych ficzer branczy. Jak coś trafia do mainline, to ma to być kod gotowy na produkcję. Nie masz dodatkowego procesu release, czyli zrobienia brancza release, pospolitego ruszenia testerów itd. Na ile rozumiem sens tego podejścia, pożądane jest automatyczne wypychanie zmian na produkcję możliwie często.

No tak, jeśli chcesz mieć dywizje deweloperów, bataliony testerów i pułki release managerów do wielkiej kobyły releasowanej co pół roku, to trunk based development może do tego pasować jak pięść do nosa. Ale nie sprecyzowałeś, że celujesz w coś takiego :)

Jedyny raz, gdy widziałem wdrożony git flow, to właśnie przy wielkiej kobyle, która miała release może raz albo dwa razy do roku, była instalowana on-premise więc klient mógł chcieć sobie podbić do najnowszej wersji ale mógł nie chcieć, w efekcie do starych releasów też leciały fixy i tak dalej. Ale nadal, było to potężnie biurokratyczne - był szpec od robienia merge do developa, code freeze i inne wynalazki, jak się nie wyrobiłeś z ficzerem przed code freeze to był problem - no pełen serwis. Nie wiem, jak trunk based development sprawdził by się przy tak dużym repo. Mając na uwadze naturę on-premise, raczej i tak skończyłoby się na pączkowaniu release branchy do których leciałyby fixy.

ALE, tutaj uwaga - mimo, że były tam długo żyjące feature branche, releasy itd. nie chroniło to przed wrzucaniem na "produkcję" rzeczy niekompletnych lub niewidocznych dla użytkownika - bo znowu pojawiały się tematy w stylu nie zdążymy z tym do releasu 7.4.x, ale możemy zrobić zmianę i jak wypuścimy plugin pod 7.5.x to będzie wciąż kompatybilny z 7.4 (przykład z grubsza zmyślony, ale oddaje sytuacje)

1

@piotrpo: Główna zasada działania Git Flow jest błędna: nie zawsze da się dostarczyć ficzer w jednym branchu. Czasami okazuje się, że do dalszej pracy potrzebny jest commit z innego brancha, wtedy to wszystko się załamuje. Póżniej okazuje się, że programista walczy więcej z mergami niż to warte.

2

Nie masz dodatkowego procesu release, czyli zrobienia brancza release, pospolitego ruszenia testerów itd. Na ile rozumiem sens tego podejścia, pożądane jest automatyczne wypychanie zmian na produkcję możliwie często.

No właśnie nie rozumiesz, o czym już pisałem wcześniej. Oczywiście że przy TBD możesz mieć release branche, przecież to jest nawet pokazane na stronie do której sam link podałeś! Różnica jest taka że cały development odbywa się na głównym branchu. Wtedy release jest brany kiedy faktycznie ma się odbyć release. Taki branch może zostać wtedy przetestowany odpowiednio, przejść przez różne środowiska jeśli trzeba itp. Nie commituje się natomiast do niego nowych feature'ów.

Niepotrzebnie wszystko nadinterpretujesz i komplikujesz. Idea TBD jest właśnie taka by to wszystko uprościć, a Ty to przeinaczasz i próbujesz nadmiernie nad tym filozować.

Poza tym weź pod uwagę że to co się powinno robić lub nie to nie jakieś żelazne zasady, i tak gdzie ma to sens się je łamie.

0

@Aventus:
Według tego co jest na stronce, release branch jest kompletnie nieaktywny. Nie tylko nie robi się na nim nowych ficzerów, ale nie robi się również poprawek błędów. Zgadza się to z twierdzeniem również zawartym na wspomnianej stronce: This ensures the codebase is always releasable on demand and helps to make Continuous Delivery a reality, które sam przytoczyłeś.

Rozumiem również, że sam proces release'u może być bardziej skomplikowany niż "wypchnijmy to na produkcję" i nie jest zdefiniowany przez TBD (np. przejście artefaktów przez środowiska testowe, A/B testing itp. - kompletnie osobny temat).

Może faktycznie nadmiernie komplikuję temat, staram się po prostu zrozumieć problem. Moje aktualne wnioski są takie:

Jeżeli kod w trunku ma być "produkcyjny" i "gotowy do releasu na produkcję w każdym momencie" to musi mieć zapewnioną jakość.
Jeżeli nie mamy ficzer branchy (za wyjątkiem technicznych pod code review) to możliwe momenty zapewnienia tej jakości są podczas pisania kodu (np. XP), code review i testów przeprowadzanych przed wykonaniem merge'a.
Skoro tak, a taki proces ma być przeprowadzany wiele razy w ciągu dnia, to nie ma miejsca na jakiekolwiek testy manualne. Wszystko powinno być pokryte automatami działającymi szybko. Mogę sobie wyobrazić, że część testów jest robiona nightly, ale to już trochę łamie zasadę "always releasable".
Skoro automaty nie są w stanie pokryć wszystkiego (np. testy eksploracyjne), to musi istnieć sposób na łatwy i bezbolesny rollback zmian wykrytych już post factum, czy to na produkcji, czy na środowisku testowym (czyli 2 częściowy release - na środowisko testowe, blue/green, po wykonaniu dodatkowych testów promocja rozwiązania na produkcję. Tutaj wyzwaniem jest zalecana przez podejście CD zdolność do releasu zmian w ciągu < 1h.

3

@piotrpo: stronka mówi

Bugs should be reproduced and fixed on the trunk, and then cherry-picked to the release branch.

Zresztą po to mamy release branch, jakby się nie dało nic tam zmieniać, to taki branch byłby bezużyteczny

Jeżeli kod w trunku ma być "produkcyjny" i "gotowy do releasu na produkcję w każdym momencie" to musi mieć zapewnioną jakość.

Wszystko zależy od jakości kodu i procedur testujących. Ten punkt bardziej rozumiem w taki sposób, że do mastera nie jest mergowany kod, który nie ma prawa działać np. częściowy refaktor, jakieś dupa debugi, kod rozgrzebany w połowie. Kod ma być na tyle "dobry", że inny deweloper będzie mógł klepać jakiś inny task bez obawy, że testy nie przechodzą albo kod się nie kompiluje. To w jaki sposób i kiedy wdrażasz się na produkcję nie jest ściśle określone jak w Git Flow: możesz używać wspomnianych już feature branczy, możesz też używać danego commita z mastera

3

Według tego co jest na stronce, release branch jest kompletnie nieaktywny.

No i znów- to jest nieprawda. Na release branch commituje się hotfixy, najczęściej do problemów pojawiających się podczas testowania. https://trunkbaseddevelopment.com/branch-for-release/

Ciekawostka- u mnie w pracy stosujemy odwrotność tego co na tej stronie jest zalecane, i hotfix branch bierzemy z release, następnie cherry-pick na release i dopiero później na master. Łamiemy "zasadę" i jakoś świat się nie zawala od tego...

4

TBD jak i Continuous Delivery działają w oparciu o jeden ciężki warunek - programiści są odpowiedzialni. I w samym procesie CD chodzi o to, że jesteśmy w miarę pewni, że programiści nie lecą w kulki i mamy na tyle testów automatycznych, żeby wiedzieć, że appka raczej odpali i główne funkcje będą działać, a jak coś jest nie tak to jesteśmy w stanie wyłączyć dany feature lub nawet zrobić rollback kilkoma kliknięciami.
W takim procesie TBD wpasowuje się bardzo dobrze. W tym wszystkim powinno wystarczyć dodanie warunku, że merge do mastera da się zrobić tylko jak przejdzie build i unit testy, a z doświadczenia wiem, że nawet to jest dla niektórych ciężkie do ogarnięcia.
U nas robiliśmy tak, że wszystkie feature branche były mergowane do mastera regularnie i od razu automatycznie budowane i wrzucane na środowisko developerskie, więc był serwer, gdzie był uruchomiony zawsze najnowszy kod, potem jak testerzy powiedzieli, że są gotowi na kolejne zmiany, bo np. przetestowali poprzednie featury, to po prostu ostatnia zbudowana paczka, która była na developie była wpuszczana na środowisko testowe, jak tam przeszła testy to już zmierzała na UAT i produkcję. A w tym czasie master i środowisko developerskie cały czas dostawały nowe zmiany.

2
piotrpo napisał(a):

Jeżeli kod, który napisałeś i wszedł do wersji produkcyjnej nie jest podpięty do UI/API to nie da się go przetestować z tego poziomu, w tym napisać testów funkcjonalnych, mniejsza czy w postaci scenariusza, czy automatu. Można się jedynie starać, by takie testy powstały przed udostępnieniem funkcjonalności użytkownikowi, po przełączeniu feature switch. W dodatku w tej chwili nie za bardzo widzę możliwość automatyzacji takiego działania. Feature switch jest realizowane na poziomie buildu / osobnej usługi a informacja, czy feature (kompletna funkcja biznesowa) został zakończony w dżirze.

No więc testy się dodaje z ostatnim taskiem w ramach danego feature, tym który wystawia endpoint czy UI.
Albo po prostu można mieć się branch per feature i cały ficzer merdżować na raz - niezależnie czy zajmuje on 4h czy 4dni. Jeśli religia TBD tego zabrania, to znaczy, że to jest zła religia, więc zamiast ją wyznawać trzeba pracować po ludzku.

Bez przesady. To nie oni go wymyślili. Merge Miał też odpowiedzieć na dość konkretne wyzwania. To, że ma też swoje wady jest jasne, jak również to, że nie do końca jest zgodny z aktualnym podejściem do tworzenia kodu, jest dość oczywiste.

Może i nie oni wymyślili, ale mogli.
Istnienie wielu długoterminowych gałęzi i cała ta orgia merdżowania między nimi brzmi jak scenariusz niemieckiego filmu nieromantycznego.

0
mar-ek1 napisał(a):

TBD jak i Continuous Delivery działają w oparciu o jeden ciężki warunek - programiści są odpowiedzialni. I w samym procesie CD chodzi o to, że jesteśmy w miarę pewni, że programiści nie lecą w kulki i mamy na tyle testów automatycznych, żeby wiedzieć, że appka raczej odpali i główne funkcje będą działać, a jak coś jest nie tak to jesteśmy w stanie wyłączyć dany feature lub nawet zrobić rollback kilkoma kliknięciami.

Nie do końca, ludzie popełniają błędy. Każdy ma czasami gorszy dzień, śpieszy się z czymś itd. i narzędzia mają ograniczyć wpływ takich pomyłek. Oczywiście jak masz ancymonów, których odpowiedzą na sypiące się testy jest wywalenie tych testów to będzie kiepsko i to chyba nie zależy od przyjętego sposobu branczowania.

W takim procesie TBD wpasowuje się bardzo dobrze. W tym wszystkim powinno wystarczyć dodanie warunku, że merge do mastera da się zrobić tylko jak przejdzie build i unit testy, a z doświadczenia wiem, że nawet to jest dla niektórych ciężkie do ogarnięcia.

Aktualnie mamy przed wykonaniem merge puszczany build z unit testami, quality gate na sonarze + analiza podatności. Do pełnego zestawu brakuje jeszcze testów integracyjnych, ale te trwają jak na razie za długo żeby je uruchamiać inaczej niż nightly. Minus taki, że niektórych komponentach pokrycie testami jednostkowymi jest niskie.

U nas robiliśmy tak, że wszystkie feature branche były mergowane do mastera regularnie i od razu automatycznie budowane i wrzucane na środowisko developerskie, więc był serwer, gdzie był uruchomiony zawsze najnowszy kod, potem jak testerzy powiedzieli, że są gotowi na kolejne zmiany, bo np. przetestowali poprzednie featury, to po prostu ostatnia zbudowana paczka, która była na developie była wpuszczana na środowisko testowe, jak tam przeszła testy to już zmierzała na UAT i produkcję. A w tym czasie master i środowisko developerskie cały czas dostawały nowe zmiany.

Dzięki za ten opis, mamy w tej chwili wszystko ustawione bardzo podobnie. Kwestia automatycznego wyzwalania niektórych akcji.

somekind napisał(a):

No więc testy się dodaje z ostatnim taskiem w ramach danego feature, tym który wystawia endpoint czy UI.
Albo po prostu można mieć się branch per feature i cały ficzer merdżować na raz - niezależnie czy zajmuje on 4h czy 4dni. Jeśli religia TBD tego zabrania, to znaczy, że to jest zła religia, więc zamiast ją wyznawać trzeba pracować po ludzku.

Nie chodzi o wyznawanie TBD, raczej o zrozumienie jakie są haczyki i czy przypadkiem po rozmowie o Jezusie nie poproszą o przepisanie na siebie mieszkania.

0

A jak w TBD wyglądają testy manualne? Załóżmy, że mamy typową webówkę, gdzie dla wielu rzeczy - np. frontu - nie opłaca się pisać automatów - po prostu ktoś musi ocenić wzrokowo czy wszystko jest OK, sprawdzić na kilku urządzenia itp.

Jak w takim przypadku wygląda testowanie? Tester sobie sam stawia środowisko z danego brancha np. używając Jenkinsa i jak wszystko jest OK to zezwala na merg do mastera?

0

@hadwao: Wiedza czysto teoretyczna z mojej strony - testy walidujące merge'a powinny być automatyczne. W przypadku release'u masz osobna gałąź, którą można sobie testować jak się chce i dowolnie długo. Można też wyrywkowo i ciągle testować manualnie aktualny kod w trunku.
Niezależnie od tego czy się opłaca tworzyć automaty, to bez nich pójście w taki proces będzie z jednej strony trudne, z drugiej oderwane od celu jakim w moim rozumieniu jest wprowadzenie pełnego CI/CD i uzyskanie zdolności do wykonania produkcyjnego deploy'a w krótkim czasie. Rozwiązanie/ograniczenie problemu z merge postrzegam jedynie jako cel pośredni.

1 użytkowników online, w tym zalogowanych: 0, gości: 1