Git tutorial | stash, rebase, commit, merge, checkout, push i clone

Git tutorial

Tutorial Git wprowadzi Cię krok po kroku w tajniki systemów kontroli wersji. Kurs oparty jest o przykłady z objaśnieniami konkretnych zagadnień. Z powodzeniem można go potraktować jako monolit i przeczytać od deski do deski, ale sprawdzi się również jako ściąga z wybranych funkcjonalności Git.

Spis treści

Systemy kontroli wersji kodu źródłowego

Systemy kontroli wersji odpowiedzialne są za śledzenie wszystkich zmian dokonywanych w plikach. Za ich pomocą można podejrzeć wcześniej dokonane zmiany oraz, w razie potrzeby, powrócić do starszej wersji pliku. Dzięki nim można również sprawdzić kto i kiedy dokonał tych zmian.

Lokalne systemy kontroli wersji

Najprostszą formą lokalnego systemu kontroli wersji jest metoda polegająca na przekopiowaniu wszystkich plików do osobnego katalogu. Nowo powstały katalog można też oznaczyć datą powstania lub numerem wersji.

Takie podejście, z racji swojej prostoty, jest bardzo kuszące i jednocześnie wystarczające dla wielu podstawowych sytuacji. Jednak jego główną wadą jest brak odporności na błędy użytkownika. Przykładowo wystarczy pomylić katalog, na którym się pracuje i można w ten sposób stracić całą swoją pracę.

W celu zabezpieczenia się przed tym, dosyć szybko powstały proste systemy kontroli wersji oparte o bazę danych. W lokalnej bazie przechowywane były kolejne zmiany, które były dokonywane na wersjonowanych plikach. Dzięki temu można było już porównywać ze sobą zmiany z kolejnych wersji oraz, w razie czego, przywrócić stabilną wersję.

Jednym z najbardziej popularnych systemów tego typu jest RCS (Revision Control System).

Lokalny system kontroli wersji

Lokalny system kontroli wersji

Wykorzystanie lokalnego systemu kontroli wersji ogranicza jednak możliwość wykonywania zmian tylko do jednego komputera. Dlatego właśnie kolejnym krokiem w rozwoju tego typu systemów są CVCS.

Scentralizowane systemy kontroli wersji

Scentralizowane systemy kontroli wersji CVCS (Centralized Version Control System) powstały w celu umożliwienia pracy na plikach, z więcej niż jednej maszyny.

W tym celu zaprojektowano model, w którym jest jeden centralny serwer przechowujący wszystkie wersjonowane pliki oraz maszyny klienckie. Klienci łączą się do niego w celu pobrania najnowszych zmian. Ten model jest już na tyle funkcjonalny, że jest wykorzystywany powszechnie do dziś.

Wielką zaletą tego rozwiązania jest możliwość zarządzania uprawnieniami z jednego miejsca. Dzięki temu można również łatwo zweryfikować zmiany wprowadzone przez każdego uczestnika procesu.

Natomiast główny problem systemów scentralizowanych związany jest z ich potencjalną awaryjnością. Wystarczy, że główny serwer będzie przez chwilę niedostępny, a żaden klient nie może pobrać najnowszych zmian oraz zapisać swojej pracy. W przypadku uszkodzenia dysku z przechowywanymi plikami tracona jest również cała historia projektu.

Najbardziej popularne systemy tej klasy to CVS i Subversion.

Scentralizowany system kontroli wersji

Scentralizowany system kontroli wersji

Rozproszone systemy kontroli wersji

Kolejnym etapem rozwoju są rozproszone systemy kontroli wersji DVCS (Distributed Version Control System). Główna różnica polega na tym, że klienci nie pobierają tylko najnowszych zmian, a całe dostępne repozytorium. Dlatego w przypadku awarii głównego serwera wystarczy przekopiować pliki od jednego z klientów na nowy serwer i nie zostanie stracona historia projektu. W razie potrzeby klient też sam może służyć jako serwer główny dla innych klientów. Wygląda to trochę tak, że klienci dogadują się, który z nich będzie pełnił funkcję serwera i łączą się do niego.

Ciekawostką jest również to, że wiele z systemów tej klasy potrafi współpracować jednocześnie z kilkoma różnymi serwerami zewnętrznymi. Daje to ogromne możliwości, jeżeli chodzi o stosowanie różnych modeli współpracy.

Najpopularniejsze systemy tego typu to: Git oraz Mercurial.

Rozproszone systemy kontroli wersji

Rozproszone systemy kontroli wersji

Git pierwsze kroki

Obecnie Git jest najpopularniejszym rozproszonym systemem kontroli wersji. System został stworzony przez Linusa Torvalds jako narzędzie wspomagające rozwój jądra Linux. Git oficjalnie został wydany w 2005 roku i obecnie jest rozwijany na zasadach wolnej licencji GNU GPL v2.

Praca na zmianach

Podstawową cechą odróżniającą Gita od innych podobnych narzędzi jest sposób przechowywania zmian w lokalnej bazie danych. Większość systemów (np. CVSSubversion) przechowuje informacje o nowej wersji jako różnicową listę zmian, jakie zaszły na plikach.

Git natomiast tworzy swego rodzaju migawkę (snapshot) stanu repozytorium w danym momencie. W zapisanym w ten sposób obrazie repozytorium przechowane są informacje o wszystkich plikach, jakie są przechowywane w repozytorium. W celach optymalizacyjnych, jeżeli w danej wersji plik nie był zmieniany, przechowywana jest tylko referencja do jego najnowszej wersji.

Praca lokalnie

Większość operacji wykonywanych przy pomocy Gita nie wymaga połączenia do Internetu.  Jest to ogromny krok w przód w porównaniu do innych systemów. Nie tylko przeglądanie historii wersji trwa wielokrotnie szybciej, ale np. do rozpoczęcia pracy z nowym plikiem nie trzeba już łączyć się do zdalnego serwera.

Dzięki temu, że prawie wszystko dzieje się offline, praca z systemem kontroli wersji już dużo szybsza. Dopiero na koniec pracy potrzebne jest połączenie internetowe, w celu wysłania swojej pracy na serwer lub pobrania najnowszych zmian.

Stany plików w Git

Żeby móc pracować z Gitem trzeba zrozumieć, w jakich stanach mogą znajdować się zarządzane przez system pliki. Git wprowadza trzy główne stany dla zmian: zmodyfikowany, śledzony oraz zatwierdzony.

  • zmodyfikowany – plik był edytowany, ale zmiana o tym nie została jeszcze nigdzie zapisana;
  • śledzony – zmodyfikowany plik został oznaczony do zatwierdzenia przy najbliższej operacji commit;
  • zatwierdzony – dokonana zmiana została zapisana i utrwalona w lokalnej bazie danych;

Przesłanie zmian do zdalnego repozytorium jest już operacją opcjonalną.

Stany plików Git

Stany plików Git

 

  • katalog Git – to trzon lokalnego repozytorium. W nim Git przechowuje metadane o plikach oraz obiektową bazę danych. Ten katalog jest kopiowany podczas klonowania repozytorium.
  • katalog roboczy – jest to odtworzony obraz wersji projektu. To właśnie zawartość tego katalogu jest modyfikowana przez użytkownika.
  • przechowalnia (stage) – to miejsce pośrednie, między katalogiem roboczym, a lokalną bazą danych. Dzięki niej można utrwalić tylko wybrane zmiany.

Instalacja Git

Instalację Git można przeprowadzić samodzielnie z pobranych źródeł lub skorzystać z przygotowanych już binerek.

Windows

Instalator dostępny jest na stronie: https://git-for-windows.github.io

Linux

yum install git-core

lub

apt-get install git

Konfiguracja Git

Git posiada trzy poziomową konfigurację. Każdy kolejny poziom jest bardziej szczegółowy i można z niego nadpisać bardziej ogólną konfigurację.

  • systemowa (–system) – jest to najbardziej ogólna konfiguracja widoczna z poziomu całego systemu operacyjnego. Przechowywana jest w pliku tekstowym /etc/gitconfig
  • użytkownika (–global) – to konfiguracja dla aktualnie zalogowanego użytkownika do systemu. Przechowywana jest w pliku: ~/.gitconfig
  • repozytorium (–local) – najbardziej szczegółowy poziom ustawień. Jest to konfiguracja dla konkretnego repozytorium, która jest przechowywana w pliku .git/config. Konfiguracja z tego poziomu ma najwyższy priorytet.

Tożsamość użytkownika

Na tym etapie warto już skonfigurować użytkownika, jaki będzie widoczny w historii zmian oraz jakim będziemy się logować do zdalnego repozytorium. Ustawienia warto zapisać na poziomie użytkownika lub systemowym, żeby nie było potrzeby podawać ich za każdym razem.

git config –global user.name “Tomasz Woliński”
git config –global user.email kontakt@stormit.pl

W analogiczny sposób można zmieniać inne parametry konfiguracyjne na wszystkich trzech poziomach, np. domyślny edytor (core.editor), narzędzie do rozwiązywania konfliktów (merge.tool) oraz wiele innych. Ich pełną listę można przejrzeć, wpisując komendę: git help config.

Aliasy

Dla często wykorzystywanych komend powstał mechanizm aliasów. Dzięki nim można przypisać wywołanie komendy, wraz z wszystkimi jej argumentami, do prostszego aliasu i korzystać z niego, jak z wbudowanej komendy.

Poniżej zaprezentowany jest przykład bardzo długiej komendy wyświetlającej historię zmian oraz przypisanie jej do aliasu.

Dużo łatwiej jest zapamiętać polecenie git hist niż jego pierwotną wersję.

Wywołanie zewnętrznej aplikacji z aliasu

Chcąc lepiej zintegrować gita z zewnętrznymi systemami, można posłużyć się wywołaniem zewnętrznej komendy z poziomu aliasu. W tym należy takie wywołanie poprzedzić wykrzyknikiem.

Klient GUI dla Gita

Niewątpliwie obsługa systemów z linii komend daje największe możliwości. Jednak nie zawsze mamy ochotę uczyć się na pamięć tych wszystkich komend. Dlatego w kursie będą pokazane alternatywne drogi: z poziomu konsoli i klienta GUI.

Git dorobił się już naprawdę pokaźnej listy aplikacji klienckich. Do tej pory najlepiej pracowało mi się na Windowsie na GitExtensions i TortoiseGit oraz na Linuksie na SmartGit i GitKraken.

Poniższe przykłady będą realizowane z wiersza poleceń oraz klienta SmartGit. Nie przejmuj się, jeżeli obecnie korzystasz z innego klienta GUI. Większość klientów oferuje bardzo zbliżone funkcjonalności i nie powinno sprawić Ci większych trudności znalezienie odpowiednich funkcjonalności w innej podobnej aplikacji.

Praca z Gitem

Pracę z Gitem rozpoczniemy od utworzenia nowego projektu. Można to zrealizować na dwa sposoby: sklonować istniejące już repozytorium lub stworzyć całkowicie nowe.

Utworzenie nowego repozytorium [init]

W celu zainicjowania nowego repozytorium, będąc w docelowym katalogu, należy wykonać poniższą komendę.

git init

Komenda wygeneruje strukturę nowego repozytorium w katalogu .git. Jednak jeżeli w katalogu znajdowały się już jakieś pliki, żeby rozpocząć śledzenie ich zmian, trzeba je jeszcze dodać do stage i zakomitować, ale o tym za chwilę.

W SmartGit przechodzimy do menu Repository -> Add or Create.

Klonowanie istniejącego repozytorium [clone]

Kolejnym sposobem na rozpoczęcie nowego projektu Gita jest sklonowanie istniejącego już repozytorium. W tym celu wywołujemy poniższą komendę, gdzie url można podmienić na dowolne inne repozytorium.

git clone https://github.com/StormITpl/JavaExamples.git

Podczas klonowania Git pobiera wszystkie pliki przechowywane w repozytorium oraz całą ich historię. Od tego momentu na lokalnym komputerze przechowujemy prawie idealny backup zdalnego repozytorium. W przypadku jego awarii wystarczy odtworzyć repozytorium z lokalnej wersji. Utracone zostaną tylko specyficzne konfiguracje przechowywane na serwerze.

Operacja klon utworzyła nowy katalog o nazwie repozytorium, w tym wypadku jest to JavaExamples. Wygenerowany został również podkatalog .git ze strukturą repozytorium, a kopia robocza została ustawiona na najbardziej aktualną wersję.

W SmartGit przechodzimy do menu Repository -> clone.

SmartGit clone

SmartGit clone

Rejestrowanie zmian w repozytorium

Rejestrowanie zmian w repozytorium podzielone jest na kilka etapów bezpośrednio związanych z cyklem życia zmian.

Sprawdzenie stanu plików w repozytorium [status]

Podstawowym narzędziem do weryfikacji stanu plików jest komenda:

git status.

Żeby lepiej zobrazować cały proces, przygotujmy w nowo zainicjowanym repozytorium prostą klasę javową.

Zapisujemy ją w głównym katalogu repozytorium i odpalamy komendę: git status.

Dobrze widać to również w SmartGit.

SmartGit nieśledzony plik

SmartGit nieśledzony plik

Git wykrył, że dodaliśmy nowy plik i ostrzega nas, że nie jest on jeszcze śledzony przez repozytorium.

Śledzenie nowych plików [add]

W celu rozpoczęcia śledzenia naszego pliku posłużymy się komendą: git add.

W naszym wypadku wystarczy wywołanie dowolnej z powyższych komend.

  • git add User.java – dodaje jeden wybrany plik
  • git add -A – dodaje wszystkie nieśledzone pliki
  • git add . – dodaje do śledzenia bieżący katalog ze wszystkimi plikami i katalogami, które się w nim znajdują

Ponowne uruchomienie komendy git status pokazuje, że plik jest już w przechowalni (status śledzony).

Dodawanie zmian do poczekalni

Tak jak wcześniej powiedzieliśmy, Git operuje na pojedynczych zmianach, a nie na plikach. Bardzo dobrze to widać podczas dodawania plików do poczekalni.

W tym celu zmodyfikujemy naszą klasę, dodając jej nowe pole.

Ponowne odpalenie git status pokazuje, że plik User.java jest jednocześnie w dwóch sekcjach:

Dzieje się tak, ponieważ Git umieszcza plik w poczekalni w dokładnie takiej wersji, w jakiej znajdował się podczas odpalenia komendy git add. Jeżeli w tym momencie zostanie uruchomiona komenda git commit to zatwierdzona zostanie wersja z poczekalni, a nie ta widoczna w katalogu roboczym.

Żeby zaktualizować plik w poczekalni, trzeba go zwyczajnie jeszcze raz dodać przez git add.

Wykorzystanie poczekalni w procesie zatwierdzania zmian daje ogromne możliwości, dzięki temu można wybrać, które konkretnie modyfikacje chce się zatwierdzić i utrwalić w repozytorium.

Podgląd dokonanych zmian [git diff]

To niezwykle ważna funkcjonalność. Przed zatwierdzeniem zmian zawsze warto zweryfikować, czy wszystko poszło zgodnie z planem. Może się zdarzyć, że zmiany zostały zrobione nie w tym miejscu, co trzeba lub pojawiły się jakieś dodatkowe wygenerowane pliki, których nie chcemy utrwalać w repo. Dzięki weryfikacji zmian w podglądzie można uniknąć tego typu błędów.

Git status dostarczy tylko informacji, które pliki zostały zmodyfikowane, natomiast dzięki git diff można dokładnie zobaczyć te zmiany.

  • różnica między katalogiem roboczym a poczekalniągit diff
  • różnica między poczekalnią a repozytoriumgit diff –cached

To samo można zobaczyć z poziomu SmartGit. Najpierw wyświetlona jest lista zmodyfikowanych plików, a po wybraniu jednego z nich, naniesione w nim zmiany.

SmargGit podgląd zmian

SmargGit podgląd zmian

Zatwierdzanie zmian [commit]

Jeżeli mamy już pewność, że dokonane zmiany są poprawne, można utrwalić je w lokalnym repozytorium. W tym celu posłużymy się komendą

git commit

Wywołanie komendy bez żadnych argumentów uruchomi najpierw domyślny edytor tekstowy w celu podania komentarza dla zatwierdzanych zmian.

 

Opcjonalnie można podać komentarz jako argument już przy samym wywołaniu komendy:

git commit -m “User initial commit”

Po potwierdzeniu zmiany zostaną zapisane w repozytorium w postaci nowej migawki.

Ten sam efekt osiągniemy w SmartGit, klikając prawym przyciskiem na naszym repozytorium i wybierając z menu kontekstowego opcję commit. Teraz wystarczy już tylko podać komentarz i zatwierdzić zmiany.

SmartGit commit

SmartGit commit

Historia zmian [log]

Przyjrzymy się teraz bliżej możliwościom przeglądania historii projektu. Żeby było to lepiej widoczne, dodałem jeszcze jedno pole do klasy użytkownika i zakomitowałem jako osobną zmianę.

Do przeglądania historii służy polecenie

git log

Domyślnie log bez podania żadnych argumentów wyświetla zmiany od najnowszego do najstarszego.

Polecenie log jest bardzo rozbudowane i zawiera wiele opcji konfiguracyjnych, ich pełną listę można znaleźć korzystając z pomocy:

git help log

Do przeglądania historii można wykorzystać również interfejs graficzny SmartGita.

SmartGit dziennik

SmartGit dziennik

Podstawową historię można zobaczyć już na głównym widoku repozytorium. Jeżeli natomiast chcemy bardziej przyjrzeć się informacjom zawartym w historii,  dwa razy klikamy w wybrany komit na liście i przechodzimy do szczegółów. W widoku szczegółowym można przeszukiwać historię oraz podejrzeć, jakie dokładnie zmiany zostały naniesione na plik.

SmartGit historia

SmartGit historia

Rozszerzone mechanizmy

Nadpisanie historii zmian [amend]

Najczęściej wykorzystywaną opcją modyfikacji historii jest edycja ostatniego komita. Dzięki mechanizmowi amend commit można modyfikować treść komentarza najnowszego komita oraz jego zawartość.

W tym celu przeprowadzamy procedurę podobną do zwykłego komitowania zmian, jakbyśmy chcieli zatwierdzić nowy komit. Jednak wybranie opcji amend połączy ostatni wpis w historii z nowymi zmianami, tworząc jeden wpis w historii.

git commit –amend -a -m “Edit history”

Z poziomu SmartGit przed zatwierdzeniem zmian w nowym komicie można zaznaczyć checkboxem opcję amend.

SmartGit commit amend

SmartGit commit amend

Istnieje również możliwość edycji ostatniego komentarza z poziomu samej listy historii zmian.

SmartGit edycja komentarza

SmartGit edycja komentarza

Nie powinno się jednak w ten sposób edytować zmian wysłanych już do zdalnego repozytorium. Edytowany w ten sposób komit zmienia swój hash i może to doprowadzić do rozsynchronizowania lokalnego i zdalnego repozytorium.

Cofanie zmian [revert]

W celu wycofania zmian, które zostały już wysłane do zdalnego repozytorium, można skorzystać z komitów wycofujących. Ten mechanizm nie modyfikuje historii, a generuje komit, który jest przeciwieństwem zmiany, którą chcemy wycofać.

git revert 2b5ed9d

Żeby to zobrazować, do klasy User dodałem nowe pole surname, a następnie utworzyłem i zatwierdziłem komit wycofujący tą zmianę.

SmartGit revert

SmartGit revert

W SmartGit z poziomu listy historii można utworzyć tego typu komit.

SmartGit revert commit

SmartGit revert commit

Tagowanie źródeł

Tagowanie, czy inaczej mówiąc etykietowanie źródeł, to mechanizm pozwalający na oznaczenie ważniejszych miejsc w historii zmian projektu. Najczęściej jest wykorzystywany do oznaczania wersji aplikacji (np. wersja 11.0, itp).

Git posiada dwa rodzaje etykiet: lekkie oraz opisane. W tym tekście zajmiemy się lekkimi.

W celu oznaczenia aktualnych źródeł nowym tagiem wpisujemy komendę:

git tag [nazwa-taga]

Natomiast sama komenda git tag, bez podania żadnych argumentów, wyświetli listę wszystkich znanych tagów.

Komendą git show v2.0 można zobaczyć, na co dokładnie wskazuje dany tag.

Tagowanie historii

Istnieje też możliwość tagowania historycznych komitów. Trzeba jedynie znać ich hash, który można wyciągnąć z historii.

Z poziomu SmartGit można też oczywiście zarządzać tagami. Wystarczy wybrać komit i z menu kontekstowego dodać lub usunąć tag.

W lewym menu jest dostępna lista wszystkich aktualnie założonych tagów.

SmartGit tagi

SmartGit tagi

Ignorowanie plików

W większości projektów mamy do czynienia z plikami, których nie chcemy wersjonować. Są to np. pliki generowane automatycznie lub przechowujące skeszowane dane. Dodanie ich do repozytorium powoduje tylko zaciemnienie obrazu wprowadzanych zmian.

Można, co prawda, pomijać tego typu pliki przy zatwierdzaniu zmian, jednak nie jest to zbyt pragmatyczne podejście. Dużo lepszym wyjściem jest oznaczenie takiej klasy plików jako ignorowane. Od tego momentu nie będą nawet widoczne jako pliki zmodyfikowane.

Mechanizm ignorowana plików oparty jest o plik tekstowy .gitignore. Poniżej przykładowa zawartość:

*.tmp
tmp

Kolejne klasy ignorowanych plików wpisujemy w osobnych linijkach.  Pierwsza linijka odpowiedzialna jest za ignorowanie wszystkich plików o rozszerzeniu .tmp, natomiast druga za cały katalog tmp oraz jego zawartość.

Warto już na starcie zdefiniować, które pliki mają być ignorowane. Pozwoli to w przyszłości na uniknięcie zabawy z niepotrzebnymi plikami.

Ponieważ plik .gitignore jest zwykłym plikiem tekstowym przechowywanym w głównym katalogu repozytorium, on również może podlegać wersjonowaniu. Po jego dodaniu lub modyfikacji warto zakomitować naniesione zmiany lub jego też oznaczyć do ignorowania :).

Żeby z poziomu SmartGita oznaczyć jakiś plik jako ignorowany, klikamy na nim prawym przyciskiem i oznaczamy jako ignorowany.

SmartGit ignore

SmartGit ignore

Praca z gałęziami w Gicie [branch, checkout]

Jest to jedna z kluczowych funkcjonalności Gita, dzięki której stał się tak popularny. Niby większość systemów kontroli wersji ma możliwość rozgałęziania kodu, ale w Gicie ten mechanizm jest zrobiony wyjątkowo dobrze i przejrzyście.

Co ważne, praca z gałęziami jest bardzo szybka, w odróżnieniu od innych podobnych rozwiązań. Gałąź w Gicie została zaimplementowana jako lekki, przesuwalny wskaźnik na miejsce w historii.

Domyślna nazwa gałęzi to master. Do tej pory pracowaliśmy tylko na niej. Pracując na kilku branczach system musi wiedzieć, na którym aktualnie się znajdujemy, w tym celu wprowadzono wskaźnik HEAD.

Utwórzmy nową gałąź o nazwie feature.

git branch feature

Bardzo dobrze to widać na GUI w SmartGit. Z tamtego poziomu też można dodawać nowe branche klikając na wybrany komit i wybierając z menu kontekstowego opcję add branch.

SmartGit branch

SmartGit branch

Na rysunku widoczne są dwa branche:

  • główny master
  • nowo utworzony feature

W chwili obecnej oba wskazują na ten sam komit.

Dodatkowo branch master został oznaczony zielony trójkącikiem. Jest to wskaźnik HEAD oznaczający, że aktualnie znajdujemy się na tej gałęzi.

Plecenie git branch nie zmienia aktualnego brancha, tylko tworzy nowy. W celu zmiany aktualnego brancha odpalamy komendę:

git checkout feature

Rozgałęzienie historii projektu

Czasami zachodzi potrzeba rozwidlenia historii projektu, żeby móc np. rozwijać na osobnej gałęzi nową funkcjonalność, jednocześnie nie przeszkadzając innym w pracy. Wprowadzane w ten sposób zmiany są od siebie niezależne, łączy je tylko wspólny punkt w historii.

Żeby to lepiej zademonstrować, wprowadzimy do brancha feature nową funkcjonalność, natomiast do brancha master szybką poprawkę hotfix.

Ogólnie przyjętym zwyczajem jest rozwijanie nowych, niestabilnych funkcjonalności na osobnych branchach, żeby zachować, w miarę możliwości, jak największą stabilność głównej gałęzi master.

Nowa funkcjonalność

W naszym przykładzie nowa funkcjonalność będzie polegała na dodaniu nowego pola z nazwiskiem do klasy użytkownika. Dodajemy takie pole i zatwierdzamy do lokalnego repozytorium na branchu feature zgodnie z wcześniejszymi objaśnieniami.

Hotfix

Kiedy spokojnie sobie pracowaliśmy nad nową funkcjonalnością na środowisku produkcyjnym, klient wykrył poważną lukę bezpieczeństwa w naszej aplikacji. Nasz kod jest jeszcze nieskończony do tego stopnia, że nawet się nie kompiluje, a klient nie będzie przecież czekał, aż dokończymy nową funkcjonalność i wgramy mu ją razem z poprawką. Co robić!?

Przełączamy się na branch master, gdzie nie ma jeszcze nowej funkcjonalności i tam wprowadzamy poprawki.

Załóżmy, że nasza poprawka będzie polegała na zmianie typu pola z Integer na int. Wprowadzamy poprawkę i wgrywamy nową wersję do klienta. Aplikacja i dobre imię naszej firmy uratowane! Możemy sobie polać : )

SmartGit rozwidlona historia

SmartGit rozwidlona historia

Na graficznej historii w SmartGit widać, w jaki sposób źródła projektu zostały rozgałęzione oraz gdzie mają wspólny punkt w historii.

Dobrym zwyczajem jest robienie hotfixów również na osobnym branchu i dopiero jak wszystko zostanie przetestowanie mergowanie ich do gałęzi master, ale o tym za chwilę.

Scalanie gałęzi (merge)

Problem z hotfixem został już zażegnany, możemy spokojnie wrócić do pracy nad rozwojem projektu. Chcielibyśmy jednak pracować na możliwie najnowszych źródłach, dlatego potrzebujemy również zmian, które zostały wprowadzone na masterze.

Jednym z możliwych sposobów, by to osiągnąć, jest zmergowanie gałęzi master do gałęzi feature. Jest to o tyle dobre rozwiązanie, że w historii zmian dokładnie widać różnice, jakie zaszły między gałęziami.

W tym celu przechodzimy do gałęzi feature i wywołujemy komendę git merge master.

Udało się automatycznie przenieść wszystkie zmiany z master do feature. Czasami jednak mogą wystąpić konflikty, ale do tego wrócimy.

SmartGit merge

SmartGit merge

Na wykresie zmian widać jak podczas merga został utworzony nowy komit pokazujący różnice oraz jak jedna gałąź przechodzi w drugą.

Kończymy rozwijanie nowej funkcjonalności, w moim wypadku będzie to dodanie pola z nazwą do klasy Interest. Teraz można gotową funkcjonalność zakomitować i zmergować do gałęzi master.

Wszystkie zmiany zostały naniesione na gałąź master. Tym razem jednak nie powstał komit mergowy, ponieważ git mógł skorzystać z mechanizmu Fast-forward. Stało się tak, ponieważ podczas łączenia zmian wystarczyło przesunąć wskaźnik gałęzi i nie trzeba było wykonywać pełnego mergowania zmian.

SmartGit merge fast forward

SmartGit merge fast forward

Po zmergowaniu brancha z nową funkcjonalnością można już go usunąć.

Konflikty scalania

Nie za każdym razem jednak mergowanie gałęzi przebiega bezproblemowo. Mamy wtedy do czynienia z konfliktami w kodzie, które należy rozwiązać. Dzieje się tak najczęściej, jeżeli dany fragment kodu będzie edytowany na obu gałęziach.

Żeby wywołać taką sytuację, zasymulujemy jednoczesną edycją klasy Interest na obu branczach. Na gałęzi master ustawimy dla pola name domyślną wartość “Java” a dla gałęzi feature wartość “Git”.

Tym razem git nie poradził sobie z automatycznym połączeniem gałęzi i trzeba zrobić to ręcznie.

Ostatnie wywołanie git status pokazuje, których plików nie udało się połączyć.

W konfliktującym pliku Git umieścił kod z obu gałęzi między specjalnymi znacznikami.

Git konflikt

Git konflikt

Teraz należy rozwiązać konflikt. Poprawiamy kod oraz komitujemy go do repozytorium. Trzeba też usunąć znaczniki dodane podczas merga.

Przeniesienie pliku do poczekalni oznacza w Gicie rozwiązanie konfliktu.

Narzędzia graficzne do rozwiązywania konfliktów [mergetool]

Istnieje również możliwość rozwiązania konfliktów, wspomagając się narzędziami graficznymi. Zamiast edytować pliki ręcznie, odpalamy komendę:

git config –global merge.tool kdiff3

git mergetool

Pierwsza komenda ustawia domyślny edytor na kdiff3. Można też posłużyć się innym narzędziem, np. jednym z poniższej listy.

opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc codecompare emerge vimdiff

Po rozwiązaniu konfliktu i zapisaniu plików z poziomu wybranego narzędzia można już zakomitować zmiany.

Praca ze zdalnym repozytorium [remote]

Dzięki wykorzystaniu możliwości zdalnego repozytorium można współpracować z innymi osobami, nie ograniczając się już tylko do pracy na jednym komputerze. Git umożliwia współpracę jednocześnie z kilkoma różnymi zdalnymi repozytoriami. Można z nich pobierać kod, wysyłać swoje zmiany oraz zarządzać gałęziami kodu itp.

Wyświetlenie zdalnych repozytoriów

Żeby wyświetlić listę wszystkich skonfigurowanych zdalnych repozytoriów, posłużymy się komendą: git remote. Dla zainicjowanego lokalnie repozytorium będzie to pusta lista, jednak dla repozytorium sklonowanego powinno być dostępne co najmniej jego źródło.

Dodanie zdalnego repozytorium

Konfiguracja nowego zdalnego repozytorium polega na wywołaniu komendy w ogólnym formacie:

git remote add [skrót] [url]

Od tego momentu nowe zdalne repozytorium jest już dowiązane do naszego lokalnego.

Wysłanie lokalnych zmian do zdalnego repozytorium

Jeżeli napisany kod jest już sprawdzony i mamy odpowiednie prawa zapisu, można wysłać go na zewnątrz. Służy do tego komenda:

git push origin master

Nadpisanie zmian w zdalnym repozytorium powiedzie się tylko, jeżeli od czasu ostatniego pobierania zmian z zewnątrz, nikt nie wrzucił swoich zmian. Jeżeli już do tego doszło, musimy najpierw takie zmiany pobrać i połączyć je ze swoimi.

SmartGit origin master

SmartGit origin master

Po wysłaniu zmian lokalna gałąź master oraz zdalna origin/master powinny być w tym samym miejscu.

Pobranie zmian ze zdalnego repozytorium

Warto zauważyć, że pobranie zmian ze zdalnego repozytorium oraz połączenie ich z lokalnym repozytorium to dwie całkowicie niezależne czynności.

Polecenie git fetch origin pobiera wszystkie zmiany, których jeszcze nie było lokalnie.

SmartGit fetch

SmartGit fetch

Widać to dobrze na liście zmian w SmartGit. Zmiany z origin zostały już pobrane lokalnie, ale git nie zmodyfikował jeszcze lokalnej kopii repozytorium – lokalny branch master jest poniżej zdalnego origin/master.

Żeby scalić zmiany wprowadzone przez innych użytkowników z naszymi, trzeba je jeszcze nanieść na lokalne repozytorium, np. przez merge:

git merge origin/master

W tym momencie może oczywiście dojść do konfliktów, jednak ich rozwiązywanie przebiega analogicznie jak przy łączeniu lokalnych gałęzi.

Powyższe dwa kroki zazwyczaj są wykonywane bezpośrednio po sobie, dlatego zostały połączone w jedną komendę:

git pull origin master

To polecenie jest równoznaczne z pobraniem najnowszych zmian z origin oraz zmergowaniem ich do lokalnego mastera.

Git Flow – czyli sposoby pracy z gałęziami

Wprowadzenie lekkiego i szybkiego modelu zarządzania gałęziami w Gicie przyczyniło się do powstania wielu różnych schematów pracy, opartych o rozgałęzianie kodu.

Częścią wspólną tych modeli jest podział na gałęzie długotrwałe oraz krótkotrwałe. Na gałęziach długotrwałych utrzymywany jest stabilny kod, który zmienia się raczej rzadko. Natomiast gałęzie krótkotrwałe przeznaczone są na rozwój aplikacji oraz naprawę błędów. Kod powstały w gałęziach krótkotrwałych jest przenoszony podczas mergu do gałęzi długotrwałych.

Jednym z najpopularniejszych modeli jest Git-Flow, którego założenia przedstawione są poniżej.

Git-Flow

Git-Flow

Master

Na gałęzi master trzymany jest jedynie stabilny kod aplikacji. Tutaj nie powinno być zwykłych komitów ze zmianami kodu, a jedynie komity mergujące z gałęzi hotfix oraz release. Każda kolejna wydana wersja aplikacji oznaczana jest nowym tagiem z wersją. Dzięki takiemu podejściu w dowolnym momencie rozwoju aplikacji, mamy do dyspozycji stabilną wersję kodu. Można ją bezpiecznie zaprezentować klientowi oraz, w razie potrzeby, nanieść niecierpiące zwłoki poprawki.

HotFix

HotFix jest zawsze “wystrzeliwany” z gałęzi master. Po naprawie błędu taką gałąź należy zmergować do master. Poprawkę można również dodać do obecnie rozwijanej gałęzi develop, jeżeli chcemy, żeby już teraz została poprawiona. W tym samym czasie może być kilka gałęzi typu HotFix.

Develop

Jest to główna gałąź rozwojowa aplikacji. Kod, który zawiera, może być chwilami niestabilny. Tu spotykają się i łączą wszystkie niezależnie rozwijane funkcjonalności. Gałąź develop wystrzeliwana jest z gałęzi master po wydaniu nowej wersji.

Feature

Wszystkie większe funkcjonalności powinny być rozwijane na osobnych gałęziach. Dzięki temu można je niezależnie rozwijać, nie przeszkadzając jednocześnie w rozwoju innych funkcjonalności. Daje to również możliwość wypuszczenia nowej wersji aplikacji, bez konieczności czekania aż wszystkie rozwijane rzeczy zostaną ukończone. Po zakończeniu prac kod jest mergowany do gałęzi develop, gdzie testowany jest razem z innymi funkcjonalnościami.

Release

Po zakończeniu pewnego okresu rozwoju aplikacji, z gałęzi Develop wystrzeliwana jest nowa gałąź, tak zwany Release candidate. Na tym branchu nie są już rozwijane żadne nowe funkcjonalności. Jego celem jest ustabilizowanie obecnie wytworzonego kodu przed ostatecznym mergem do master.

Programista – Pytania rekrutacyjne

Lista pytań rekrutacyjnych, które pozwolą przygotować Ci się na rozmowę kwalifikacyjną.

No comments
Share:

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *