Git to jeden z najpopularniejszych obecnie używanych systemów kontroli wersji. W tym wpisie postaram się przybliżyć Ci jego konfigurację jak i podstawowe komendy Git.
- Czym jest system kontroli wersji?
- VCS scentralizowane vs zdecentralizowane
- Instalacja git na systemie Linux
- Instalacja git na systemie Windows
- Git podstawy
- Praca na wielu gałęziach
- Operacja merge
- Scenariusz pierwszy: dwóch programistów pracuje nad dwóch różnych gałęziach.
- Scenariusz drugi: dwóch programistów pracuje na tej samej gałęzi, ale na innych plikach.
- Scenariusz trzeci: dwóch programistów modyfikuje ten sam fragment kodu.
- Scenariusz czwarty: jeden programista chce wykonać operację merge na swoim branchu.
- Podsumowanie
Czym jest system kontroli wersji?
System kontroli wersji (ang. Version Control System) to oprogramowanie, które pomaga programistom synchronizować między sobą kod, który tworzą oraz utrzymywać pełną historię ich pracy. Programiści już od dawna odeszli od małych jednoosobowych projektów, gdzie twórca od początku do końca nie musiał współpracować z innych przedstawicielami Homo Sapiens. Najlepszym przykładem niech będzie kultowa gra Prince of Persia z 1989 roku została napisana przez Jordana Mechnera. W dzisiejszych czasach przy produkcjach gier pracują setki osób. Stąd też programiści musieli wymyśleć prosty sposób w jaki współdzielić kod między sobą (no bo e-mailem raczej go sobie wysyłać nie będą 😉 ).
VCS scentralizowane vs zdecentralizowane
Systemy kontroli wersji dzielą się na dwie kategorie: scentralizowane (ang. Centralized version control system) oraz zdecentralizowane (ang. Distributed/Decentralized version control system). Przykładem pierwszego z nich jest często używany SVN (ang. Subversion). Podstawową różnicą między oboma podejściami jest sposób przetrzymywania kodu. W przypadku SVN kod jest od razu trzymany na zewnętrznym serwerze, do którego mają dostępy inni deweloperzy. Zupełnie innym podejściem stanowi Git, gdzie nie ma jednej centralnej wersji bazy kodu, a zamiast tego każdy użytkownik posiada kopię roboczą i historię zmian. Tak naprawdę różnic jest jednak dużo więcej. Ogólnie można powiedzieć, że jest rozwiązaniem bardziej elastycznym i dużo nowocześniejszym.
Instalacja git na systemie Linux
Aby zainstalować Git w konsoli na systemie typu Linux (konkretnie Ubuntu), wystarczy skorzystać z terminalu (konsoli) i wpisać:
apt-get update apt-get install git
Każdy inny system typu Unix umożliwia korzystanie z Gita w podobny sposób, nie mniej komendy instalacyjne mogą się tu różnić. Po instalacji, możesz już używać jego komend w konsoli, ponieważ Git przy każdym uruchomieniu systemu będzie działać wraz z powłoką (ang. shell).
Instalacja git na systemie Windows
W przypadku Systemu Windows, należy zainstalować aplikację o nazwie git bash, która symuluje konsolę podobną w systemach rodzaju Unix. Domyślnie w tej wersji konsoli skonfigurowany jest już Git.
Git podstawy
Sciąganie kodu ze zdalnego repozytorium na Twoją lokalną stację roboczą, wykonujesz za pomocą polecenia git clone. Z Gitem możesz pracować za pośrednictwem dwóch protokołów: SSH lub HTTPS. Jeśli ściągasz kod z jakiegoś publicznego repozytorium, takiego jak np. github, to jego twórca raczej nie będzie chciał dać Ci możliwości jego edycji. W takiej sytuacji z reguły wystarczy Ci skorzystać z protokołu HTTPS. W przypadku pracy nad kodem w zespole, wygodniej jest używać połączenia przez SSH. Wymaga to generacji odpowiednich kluczy, z których klucz publiczny powinien być skonfigurowany w Twoim repozytorium.
Przykład użycia komendy clone na moim projekcie udostępnionym na publicznym repozytorium:
git clone https://github.com/developeronthego/java-basics.git
Po ściągnięciu kodu i uruchomieniu go w Twoim IDE, kolejnym krokiem jest z reguły naniesienie swoich zmian. Kiedy zaimplementujesz poprawnie swój feature, to zatwierdzasz zmianę za pomocą komendy commit. Zazwyczaj do tego rozkazu dodaje się flagę -m, która pozwala napisać swój komentarz. W praktyce, zawsze będziesz dodawać opis swoich zmian do zatwierdzonego kodu. Najpierw jednak musisz wskazać Gitowi jakie pliki należy dodać do Twojej zmiany. Do tego służy komenda git add. Umożliwia ona dodawanie pojedynczego pliku, jak i całej listy. Jeśli chcesz wrzucić wszystkie wyedytowane pliki do zmiany, to wystarczy wstukać git add –all.
Dopiero po dodaniu plików do Twojej zmiany, możesz je zatwierdzić poprzez commit. Jak już wspominałem git jest systemem zdecentralizowanym, samo skomitowanie kodu nie zmienia nic w kontekście repozytorium zdalnego. Wszystkie zmiany będą odkładane na lokalnym komputerze aż do momentu wypchnięcia ich na zewnątrz. Aby tego dokonać należy użyć komendy git push. Opcjonalnie można dodać do niej nazwę repozytorium (domyślne origin).
git add --all git commit -m "My new changes" git push
Powyższy scenariusz dodawania kodu do repozytorium, brzmi banalnie, jednak jest on bardzo rzadki. Dlatego postaram się wspomnieć o kilku innych kwestiach. Po pierwsze zawsze przed wypchnięciem kodu, wpierw musisz pobrać zmiany ze zdalnego repozytorium. Myślę, że jest to logiczne, że skoro repozytorium centralne ma za zadanie scalać kod, to ma niejako pierwszeństwo i najpierw musisz je pobrać na swój komputer. Dlatego przed wykonaniem git push, należy wykonać git pull. Nawet, jeśli zapomnisz o tej komendzie, to w praktyce wykona się ona niejawnie przed każdym pushem.
Pobieranie zmian z zewnętrznego repozytorium
git pull
Kolejną rzecz, którą warto zrobić zaraz po pierwszym pobraniu kodu, jest stworzenie pliku .gitignore (lub jego edycji, jeśli istnieje). Jego celem jest wskazanie gitowi, które pliki powinny zostanie pominięte przez proces śledzenia zmian w projekcie. Umieszcza się tam pliki lub katalogi zawierające metadane tworzone przez Twoje IDE. Ponieważ każdy użytkownik ma je trochę inne, nie nadają się do współdzielenia. Dlatego, aby nieopatrznie nie przypisać je do commita, warto je tam skatalogować.
Przykładowa zawartość pliku .gitignore
.classpath .gitignore .project .settings/ target/
Kolejną istotną kwestią jest sprawdzenie historii oraz stanu Twojej lokalnej instancji. Można tego dokonać poprzez polecenie git log, które to wyświetli drzewo pokazujące wszystkie zmiany dokonane na pobranych gałęziach (będą one aktualne na moment wykonania komendy git pull). Oprócz sprawdzenie historii, warto sprawdzić stan Twojej gałęzi (ang. branch), używając git status. W ten sposób na konsoli zobaczysz jakie pliki uległy zmianie. Dodatkowo możesz przejrzeć konkretne linie kodu, które zostały zmienione, poleceniem git diff.
Sprawdzenie historii:
git log
Sprawdzenie stanu plików lokalnego repozytorium:
git status
Porównanie nowo naniesionych zmian ze stanem zapisanym w poprzednim „commicie”:
git diff
Praca na wielu gałęziach
Każda zmiana w Git jest zapisywana, na określone gałęzi. Jak już pisałem, struktura zapisywanych zmian w systemie Git przypomina drzewo. Aby to zobrazować, wyobraź sobie, że każda zmiana (commit) to liść, który znajduje się na określonej gałęzi.
Aby zacząć śledzić zmiany w Twoim projekcie, należy wykonać polecenie git init. Domyślną gałęzią, po zainicjalizowaniu Gita w projekcie, jest branch o nazwie master. Jest to główny korzeń całego drzewa, z którego deweloperzy tworzą kolejne gałęzie. Praca na własnych gałęziach, jest rzeczą typową w każdym projekcie w IT. W ten sposób, twórcy oprogramowania nie przeszkadzają sobie na wzajem, trzymają zmiany na osobnych gałęziach.
Jeśli chcesz stworzyć nową gałąź, wystarczy wpisać git checkout -b myBranch. Powyższa komenda stworzy nowy branch o nazwie myBranch i od razu przeniesie Cię na niego. Jeśli chcesz wrócić do korzenia master, to wystarczy wpisać git checkout master. W celu sprawdzenia jakie gałęzie są zawarte w Twoim repozytorium, wpisz: git branch. Czasami też może się zdarzyć, że będziesz potrzebować usunąć konkretną gałąź. Wykonasz taką operację poprzez git branch -D myBranch, gdzie ponownie myBranch to nazwa Twojego brancha.
git init git checkout -b helloworld git checkout master git branch -D helloworld
Operacja merge
Największym wyzwaniem podczas korzystania jakiegokolwiek systemu kontroli wersji są konflikty między Twoim kodem a kodem, który na repozytorium centralnym uległ zmianie w wyniku edycji innego dewelopera. Żeby lepiej to zrozumieć napiszmy sobie kilka scenariuszy.
Scenariusz pierwszy: dwóch programistów pracuje nad dwóch różnych gałęziach.
W tym przypadku nie ma problemu, ponieważ każdy branch posiada swoją historię, także nie dochodzi tu do żadnej kolizji. Git zachowuje się tu tak jakby każdy programista pracował na osobnym projekcie.
Scenariusz drugi: dwóch programistów pracuje na tej samej gałęzi, ale na innych plikach.
Tu też nic złego się nie dzieje z racji, że w przypadku zaciągnięciu kodu komendą git pull, git automatycznie zaciągnie zmiany z innych plików, ponieważ nie ma tam żadnych konfliktów. Dzieje się tak dlatego, bo pod magiczną nazwą git pull, skrywają się dwie inne komendy git fetch (która zaciąga kod) oraz git merge (która go łączy z obecnym stanem). Czyli mówiąc wprost git pull = git fetch + git merge. W tym przypadku git merge automatycznie wykonuje swoją pracę, dlatego możesz mieć wrażenie, że git pull jedynie pobiera kod.
Scenariusz trzeci: dwóch programistów modyfikuje ten sam fragment kodu.
Tu właśnie pojawia się problem. W momencie, gdy jeden deweloper wrzucił na centralne repozytorium zmiany kodu, który Ty też edytujesz (ale on zrobił to pierwszy), pojawi się tzw. konflikt. Powodem tego jest prosty fakt, że Ty w swojej lokalnej wersji kodu masz inną implementację, niż ta która jest na zdalnym repozytorium. Teraz Ty musisz zdecydować, która wersja jest poprawna, ponieważ Git już tego za Ciebie nie zdecyduje. W takim przypadku, gdy użyjesz git pull, dojdzie do operacji merge na Twoim branchu. Następnie Git wskaże Ci wszystkie miejsca, gdzie są konflikty i w zapyta Cię w jaki sposób powinien wyglądać kod finalnie w każdym z tych miejsc.
Scenariusz czwarty: jeden programista chce wykonać operację merge na swoim branchu.
Weźmy taki scenariusz. Na gałęzi master istnieje tylko jeden commit z poniższą implementacją:
public class Start { public static void main(String[] args) { System.out.println("Hello world!"); } }
Teraz stwórz własny feature, którego kod trzymasz na gałęzi o nazwie security-feature. Jest tam tylko jedna zmiana, nazwa „Hello world” została zaktualizowana na „Hello Eclipse”. W międzyczasie ktoś wrzucił swój commit do korzenia master, gdzie edytowano powyższy tekst na „Hello IntelliJ”.
Po zakończonej pracy chcesz „zmergować” wszystkie swoje zmiany na głównej gałęzi master. W takim wypadku wykonasz poniższe kroki:
Pobrać aktualne zmiany do korzenia master:
git checkout master git pull master
Wykonać komendę merge:
git merge security-feature
Teraz, jeśli istnieją konflikty, musisz je rozwiązać. Normalnie konflikty rozwiązuje się za pomocą IDE, takiego jak IntelliJ lub Eclipse, ponieważ porównywanie wielu plików w konsoli nie jest łatwe. Na potrzeby tej lekcji skupię się jednak tylko na samej pracy za pomocą linii komend.
Gdy spojrzysz na swoją klasę, powinna ona wyglądać tak:
public class Start { public static void main(String[] args) { <<<<<<< HEAD System.out.println("Hello IntelliJ!"); ======= System.out.println("Hello Eclipse!"); >>>>>>> security-feature } }
Taki kod się nie skompiluje. Musisz poprawić go tak, aby wybrać którą wersje chcesz zostawić. W moim przypadku chcę zostawić wersję z gałęzi security-feature.
public class Start { public static void main(String[] args) { System.out.println("Hello Eclipse!"); } }
Na koniec muszę wykonać commit z dodatkowymi zmianami.
git add . git commit -m "Merged security-feature"
Voilà! Właśnie rozwiązałeś/aś swój pierwszy konflikt. 🙂
Podsumowanie
Wyszła mi całkiem długa notka, także nie traktuj jej jako kompletny poradnik. Zasygnalizowałem Ci tutaj pewne podstawy, które pozwolą Ci zacząć pracę nad swoim kodem. Jeśli jesteś na etapie nauki programowania, powyższe informacje wystarczą Ci, aby zacząć programować. Przede wszystkim polecam przetestować powyższe komendy ściągając kod, który udostępniam na moim githubie.