Zadanie 1 - Wprowadzenie do systemu kontroli wersji Git

Wstępne informacje

Przebieg ćwiczenia

Instalacja Git

W trakcie zajęć, korzystając z terminali w sali możesz pominąć ten krok - Git już jest zainstalowany.

Aby korzystać z systemu kontroli wersji, trzeba go zainstalować.

Należy pobrać odpowiednią wersję instalatora dopasowaną do Twojego systemu operacyjnego ze strony https://git-scm.com/downloads. Po jego uruchomieniu należy przejść przez wszystkie kroki instalacji, pozostawiając wszystkie ustawienia domyślne.

Należy pamiętać, że jeśli przed rozpoczęciem instalacji była już włączona linia poleceń (konsola) w systemie Windows, trzeba ją uruchomić poonwnie, by nowo zainstalowane narzędzie było dostępne.

Zweryfikuj, czy git jest zainstalowany poprawnie wykonując komendę

$ git --version
git version 2.9.0.windows.1

Hands-on

Aby szybko ozobaczyć co to jest system konroli wersji i do czego nam się może przydać, od razu rozpoczniemy z niego skorzystać.

Stwórz w dowolnym miejscu na dysku katalog, w którym będziemy poznawać możliwości systemu kontroli wersji i wejdź do niego.

$ mkdir mwo-git
$ cd mwo-git

Następnie, zainicjalizuj w nim repozytorium Git:

$ git init
Initialized empty Git repository in C:/Users/Student/Desktop/mwo-git/.git/

Właśnie stworzyłeś swoje pierwsze repozytorium!

Zwróć uwagę na to, że w stworzonym katalogu powstał nowy katalog o nazwie .git. Znajdują się w nim wszystkie informacje wymagane do pracy Gita w danym projekcie.

Na koniec, skonfiguruj repozytorium tak, by wiedziało kim jesteś

$ git config user.name "Jan Kowalski"
$ git config user.email "[email protected]"

Jeśli chcesz dowiedzieć się więcej o użytych komendach, zerknij do dokumentacji:

Śledzenie zmian

Jak wiadomo, podstawą funkcją dostarczaną przez system kontroli wersji jest możliwość śledzenia zmian w kodzie i przechowywania jego historii.

Obecny status repozytorium można sprawdzić poprzez wykonanie komendy

$ git status
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)

Wskazuje ona na to, że repozytorium jest puste i nie ma żadnych nowych plików, które można by śledzić. Stwórzmy więc nowy plik o nazwie SomeProgram.java z zawartością:

public class SomeProgram {
    public static void main(String[] args) {
        System.out.println("We are learning to use Git.");
    }
}

Jaki jest teraz rezultat komendy git status?

Zgodnie z inforamcją zawartą w konsoli, musimy przekazać Gitowi informację, że chcemy śledzieć zmiany w nowo stworzonym pliku.

$ git add SomeProgram.java

Co teraz mówi git status?

Jeśli wsyzstko jest w porządku, wykonaj pierwszy commit zawierający nowo dodany plik

$ git commit -m "Add first implmentation of the program"

Commit, jest to pojedynczy element historii w systemach kontroli wersji - swojego rodzaju punkt kontrolny projektu, który powinno się tworzyć po wykonanym zadaniu i do którego bedzie można wrócić w przyszłości.

Zweryfikuj stan repozytorium za pomocą znanej już komendy.

Dodaj do metody main stworzonego pliku drugą linię kodu

System.out.println("We will be Git experts, soon.");

Zauważ, że Git wykrywa, że coś zmieniło się w tym pliku. Stwórz drugi commit zawierający wprowadzone zmiany.

Jeśli chcesz dowiedzieć się więcej o użytych komendach, zerknij do dokumentacji:

Przeglądanie zmian

Stworzona przez nas klasa SomeProgram powinna zawierać dwie linie w metodzie main. Możesz podglądnąć zawartość pliku poleceniem cat.

Oprócz obecnej zawartości plików, Git przechowuje także historię całego kodu stworzonego w ramach projektu. Zobacz listę commitów w Twoim repozytorium:

$ git log
commit 9934efbfee81f0bb78e75ddf8bc696ce694793c5
Author: Jan Kowalski <[email protected]>
Date:   Sun Jan 29 17:00:08 2017 +0100

    Further implementation of the program

commit a5f7c03e2e181e06a3d7719ecae45a6b8771cbca
Author: Jan Kowalski <[email protected]>
Date:   Sun Jan 29 16:59:13 2017 +0100

    Add first implementation of the program

W historii znajdują się obydwa commity wykonane przez Ciebie w poprzedniej części ćwiczenia. Zgadza się? Z podstawowego widoku historii można odczytać kto je wykonał, kiedy oraz jaką wiadomość (commit message) wpisał przy tworzeniu commita.

Commit hash

Opróczy wymienionych informacji, przy commitach w historii znajdują się także identyfikatory commitów (tzw. commit hash), dzięki którym możemy się do nich bezpośrednio odwołać. Dla przykładu, jeśli chcemy sprawdzić co wprowadza do pliku SomeProgram.java starszy commit, należy odczytać jego identyfikator i użyć komendy git show, która pokazuje co zostało zmienione w danym commicie. UWAGA: identyfikator commita w Twoimi przypadku będzie inny niż w instrukcji (identyfikatory są generowane przez Gita).

$ git show a5f7c03e2e181e06a3d7719ecae45a6b8771cbca
commit a5f7c03e2e181e06a3d7719ecae45a6b8771cbca
Author: Jan Kowalski <[email protected]>
Date:   Sun Jan 29 16:59:13 2017 +0100

    Add first implementation of the program

diff --git a/SomeProgram.java b/SomeProgram.java
new file mode 100644
index 0000000..a037960
--- /dev/null
+++ b/SomeProgram.java
@@ -0,0 +1,5 @@
+public class SomeProgram {
+    public static void main(String[] args) {
+        System.out.println("We are learning to use Git.");
+    }
+}
\ No newline at end of file

Z tych informacji można odczytać, że w pierwszym (starszym) commicie plik nie zawierał drugiej linii. Została ona dodana w następnym commicie, co można również zweryfikować odczytując odpowiedni identyfikator i używając tej samej komendy.

$ git show 9934efbfee81f0bb78e75ddf8bc696ce694793c5
commit 9934efbfee81f0bb78e75ddf8bc696ce694793c5
Author: Jan Kowalski <[email protected]>
Date:   Sun Jan 29 17:00:08 2017 +0100

    Further implementation of the program

diff --git a/SomeProgram.java b/SomeProgram.java
index a037960..b2f7f91 100644
--- a/SomeProgram.java
+++ b/SomeProgram.java
@@ -1,5 +1,6 @@
 public class SomeProgram {
     public static void main(String[] args) {
         System.out.println("We are learning to use Git.");
+        System.out.println("We will be Git exerts, soon.");
     }
 }
\ No newline at end of file

Nie przejmuj się, że zmiany kodu w konsoli trudno się interpretuje - za chwilę zobaczymy jak prezentować to lepiej używając odpowiednich narzędzi. Póki co pamiętaj, że Git przechowuje te informacje.

Kto pisał kod?

Na ogół przy pracy nad projektem bierze udział więcej niż tylko jeden autor, dlatego cenna może okazać się kolejna funkcjonalność systemu kontroli wersji - możliwoość sprawdzenia kto pisał którą linię kodu w danym pliku. Ze względu na to że zazwyczaj używa się tej techniki przy szukaniu “kto tu zepsuł”, komenda ta otrzymała odpowiednią nazwę.

$ git blame SomeProgram.java
^a5f7c03 (Jan Kowalski 2017-01-29 16:59:13 +0100 1) public class SomeProgram {
^a5f7c03 (Jan Kowalski 2017-01-29 16:59:13 +0100 2)     public static void main(String[] args) {
^a5f7c03 (Jan Kowalski 2017-01-29 16:59:13 +0100 3)         System.out.println("We are learning to use Git.");
9934efbf (Jan Kowalski 2017-02-01 18:00:08 +0100 4)         System.out.println("We will be Git exerts, soon.");
^a5f7c03 (Jan Kowalski 2017-01-29 16:59:13 +0100 5)     }
^a5f7c03 (Jan Kowalski 2017-01-29 16:59:13 +0100 6) }

Można stwierdzić, że autor całego pliku jest ten sam, ale nie napisał go od razu (linia 4 jest dodana później).

Jeśli chcesz dowiedzieć się więcej o użytych komendach, zerknij do dokumentacji:

Github

Załóżmy, że chciałbyś podzielić się napisanym kodem źródłowym z innym członkiem zespołu. W tym celu najłatwiej umieścić Twoje repozytorium w miejscu, skąd będzie mogło być pobrane przez inne osoby. W tym celu skorzystamy z Githuba.

Github (https://github.com/) jest jednym z najpopularniejszych platform udostępniających możliwość tworzenia i zarządzania repozytoriami Git. Jeśli jeszcze nie masz tam konta - zarejestruj się teraz. Będąc programista, z pewnością Ci się przyda.

Po zalogowaniu stwórz nowe repozytorium na Githubie używając tego formularza (możesz też się do niego dostać klikając przycisk z plusem w prawym górnym rogu a następnie wybierając opcję New repository). Nadaj swojemu repozytorium nazwę (np. mwo-git) a następnie kliknij w przycisk Create repository.

Po dodaniu repozytorium powinieneś się znaleźć na stronie jego szczegółów. Klikając w przycisk zaznaczony na screenie skopiujesz publiczny adres nowego repozytorium do schowka.

Gdy już zdobędziesz ten adres, prześlij swoje lokalne repozytorium na Githuba za pomocą poniższych komend.

$ git remote add origin https://github.com/fracz/mwo-git.git
$ git push -u origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 633 bytes | 0 bytes/s, done.
Total 6 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To https://github.com/fracz/mwo-git.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

Gdy odświeżysz stronę na Githubie powinieneś zobaczyć, że Twój kod został poprawnie przesłany do repozytorium na Githubie.

Krótkie podsumowanie tego co zrobiliśmy

  1. Stworzyliśmy puste, nowe repozytorium w serwise Github.
  2. Używając wygenerowanego tam adresu URL repozytorium skonfigurowaliśmy repozytorium lokalne tak, by wiedziało że jego kolejna instancja znajduje się na Githubie (mówiąc bardziej gitowo - dodaliśmy repozytorium odległe, tj. remote, używająć komendy git remote add).
  3. Dodane do repozytorium lokalnego repozytorium odległe nazwaliśmy origin - jest to domyślna nazwa repozytorium odległego.
  4. Przesłaliśmy stworzony przez nas kod źródłowy (wraz z jego historią) z repozytorium lokalnego do repozytorium na Githubie komendą git push -u origin master która oznacza prześlij kod z master do origin.

Zanim dowiemy się czym jest master, zwróć uwagę na kilka interesujących informacji które Github udostępnia już na tym etapie.

Na koniec tej części - zauważ, że zarówno w repozytorium lokalnym jak i na Githubie przechowywana jest pełna historia kodu źródłowego. Nawet będąc offline (bez dostępu do repozytoriumn odległego), możliwe jest przeglądanie pełnej historii kodu, tworzenie nowych commitów itp. Dlatego właśnie Git jest klasyfikowany jako rozproszony system kontroli wersji (DVCS, Distributed Version Control System), bo do pracy z nim nie jest wymagane żadne repozytorium centralne jak to miało miejsce chociażby przy pracy z SVN.

Brawo! Upubliczniłeś swój kod na Githubie.

Jeśli chcesz dowiedzieć się więcej o użytych komendach, zerknij do dokumentacji:

Gałęzie kodu

Poza utrzymywaniem pełnej historii tworzonego kodu, systemy kontroli wersji umożliwają niezależną pracę kilku osób bez wchodzenia sobie w drogę oraz implementację kilku niezależnych od siebie funkcjonalności przez tego samego autora. Mechanizmem, który to ułatwia są gałęzie kodu (branches).

Hands-on branches

Tak jak do tej pory - spróbujmy coś najpierw zrobić a potem wytłumaczymy co się stało. Przyjrzyj się najpierw temu jaki jest obecny stan repozytorium.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

Nic interesującego. Może poza tym, że Git mówi nam iż obecnie jesteśmy na gałęźi master.

Dokładnie! master z poprzedniej części ćwiczenia to nic innego jak domyślnie tworzona przez Gita gałąź przy tworzeniu nowego repozytorium - często też traktuje się ją jako główną gałąź repozytorium. Od samego początku pisaliśmy więc kod w gałęzi o nazwie master a komenda git push origin master mówiła, by przesłać gałąź master do repozytorium origin.

Historię repozytorium często wygodnie wizualizuje się za pomocą grafu zmian. Twoje repozytorium obecnie zawiera jedną gałąź o nazwie master. Graf wygląda więc następująco:

Podobną wizualizację możesz zobaczyć także w konsoli - spróbuj dodać argument --graph do komendy git log.

Tworzenie nowej gałęzi kodu

Jako że nasz program ma odpowiadać za sterowanie satelitą - chcemy zająć się teraz tą funkcjonalnością, więc tworzymy dla niej nową gałąź.

$ git branch managing-satellite

Zauważ, że git status nie pokazuje nic nowego. Możesz natomiast wyświetlić listę gałęzi kodu w repozytorium i zobaczyć, że faktycznie nowa gałąź została stworzona.

$ git branch
  managing-satellite
* master

Zauważ, że przy aktualnej gałęzi kodu (tej, nad którą pracujemy, nazywanej także przez Gita jako HEAD) znajduje się gwiazdka. Przełącz się na utworzoną gałąź.

$ git checkout managing-satellite
Switched to branch 'managing-satellite'

Sprawdź, czy git status i git branch rzeczywiście pokazują, że gałąź kodu została zmieniona.

Gdy zajrzysz do pliku SomeProgram.java odkryjesz, że jego zawartość nie zmieniła się. Spróbuj więc nad nim popracować. Dodaj do tej klasy nową metodę.

public void manageTheSatellite() {
    // TODO implement
}

I stwórz kolejny commit z tą zmianą. Sprawdź historię kodu - powinna zawierać trzy commity. Twoja historia wygląda teraz tak:

Mamy pierwszą gałąź kodu!

Przełączanie się pomiędzy gałęziami kodu

Oczywiście, praca nad funkcjonalnością zarządzania satelitą w Twoim programie nie jest jeszcze gotowa, ale wyobraź sobie że teraz przychodzi szef i mówi że natychmiast musisz zaimplementować zarządzanie startami rakiet!

Nie chcesz pisać tego kodu na obecnie nieskończonej funkcjonalności obsługi satelity. Wiesz natomiast, że w gałęzi master jest stabilny kod, od którego można zacząć implementację zarządzania rakietami. Przełącz się więc z powrotem na gałąź master używając do teog komendy git checkout.

Sprawdź teraz zawartość pliku SomeProgram.java. Czy jest tam zarządzanie satelitami, które napisałeś przed chwilą?

Nie martw się - nic się nie zgubiło. Twój kod nadal jest w gałęzi manage-satellite.

Wykonaj analogicznie swoje drugie zadanie (zarządzanie startowaniem rakietami), tworząc dla niego gałąź manage-rockets.

Wyświetlanie historii z wizualizacją gałęzi kodu

Po wykonaniu powyższych zadań, historia Twojego kodu wygląda następująco:

Jak widzisz - gałąź master zatrzymała się na drugim commicie w historii (szary punkt) i z niej wywodzą się dwie inne gałęzie kodu, które mogą być rozwijane niezależne przez kilka osób, lub mogą ułatiwać pracę jednej osobie nad kilkoma zadaniami.

Aby wyświetlić podobny graf w konsoli, również możesz użyć komendy git log, ale by był on czytelny należy dodać kilka flag.

Spróbuj! git log --all --oneline --decorate --graph.

Ta komenda bardzo często się przydaje by szybko zobaczyć pełną historię zmian. Jak zapamiętać wszystkie te flagi?

Zadania dodatkowe:

Jeśli chcesz dowiedzieć się więcej o użytych komendach, zerknij do dokumentacji:

Łączenie historii

Gdy funkcjonalność w danej gałęzi jest skończona i sprawdzona, należy ją dołączyć z powrotem do głównej gałęzi repozytorium. Często - umownie - główna gałąź to właśnie master. Reprezentuje ona obecny stan produktu i zawiera wszystkie ukończone funkcjonalności.

Operacja łączenia gałęzi w systemach kontorli wersji nazywana jest mergowaniem (git merge). Załóżmy, że ukończyliśmy pracę nad sterowaniem rakietami i chcemy kod znajdujący się w tej gałęzi dołączyć do stabilnej wersji programu, czyli do gałęzi master.

W tym celu należy przełączyć się na stabilną gałąź (git checkout master) i złączyć ją z gałęzią zawierającą kod do zarządzania startami rakiet - git merge manage-rockets --no-ff. Flaga --no-ff mówi Gitowi by nie upraszczał historii kodu i zachował oryginalne gałęzie kodu nawet po ich połączeniu (dowiesz się o tym więcej w zadaniu domowym).

Sprawdź, jak wygląda teraz historia kodu. A dog, pamiętasz? Pamiętałeś?

Konflikty

Powyższa operacja zakończyła się bez żadnego problemu. Co jednak stanie się, gdy w kilku gałęziach modyfikowany był ten sam fragment kodu? Jak rozwiązać ten problem?

W obydwu Twoich gałęziach kodu - złączonym już manage-rockets oraz niezłączonym jeszcze managing-satellite modyfikowany był ten sam fragment kodu (do tej samej klasy w tym samym miejscu dodano nową metodę).

Zobacz, co się stanie gdy spróbujesz dołączyć teraz gałąź z kodem do zarządzania satelitą do gałęzi master.

$ git merge managing-satellite
Auto-merging SomeProgram.java
CONFLICT (content): Merge conflict in SomeProgram.java
Automatic merge failed; fix conflicts and then commit the result.

Git mówi nam, że pojawił się konflikt - obydwu gałęziach modyfikowany był ten sam kod.

Zobacz, co mówi git status? Plik SomeProgram.java jest w stanie both modified, czyli zmieniony w dwóch miejscach jednocześnie.

Zobacz, co jest w pliku?

public class SomeProgram {
    public static void main(String[] args) {
        System.out.println("We are learning to use Git.");
        System.out.println("We will be Git exerts, soon.");
    }
<<<<<<< HEAD
    public void manageRockets() {
        // TODO implement
    }
=======
    public void manageTheSatellite() {
        // TODO implement
    }
>>>>>>> managing-satellite
}

Zauważ, że Git dodał do pliku zmiany z obdydwu gałęzi, oddzielając je trudnymi do przeoczenia ciągami znaków <, = i > (tzw. krzaki :-)). Dzięki temu możesz zobaczyć, co doszło w jednej i drugiej gałęzi kodu i co powoduje konflikt przy łączeniu gałęzi.

Konflikty należy rozwiązywać ręcznie. Istnieją programy, które wspomagają ten proces, jednak w tym przypadku w zupełności wystarczy usunąć ciągi znaków dodane przy konflikcie - zostawiając zmiany wprowadzone w obydwu gałęziach kodu.

Po dokonaniu tej edycji, zatwierdź rozwiązanie konfliktu komendą git add SomeProgram.java. Zweryfikuj komendą git status, czy Git już nie uznaje, że plik ten tworzy konflikt. Następnie, potwierdź łączenie gałęzi komendą git commit.

Sprawdź, jak teraz wygląda historia Twojego kodu w konsoli.

Prześlij aktualny kod na Githuba!

Gratulacje! Rozwiązałeś pierwszy konflikt w repozytorium!

Jeśli chcesz dowiedzieć się więcej o użytych komendach, zerknij do dokumentacji:

Praca w domu - Git exercies

Korzsytając z platformy git exercises można w przyjemny sposób ćwiczyć i rozwijać swoje umiejętności pracy z Gitem. Obecnie istnieje tam 20 zadań, z czego pierwsze 10 pozwolą Ci na utrwalenie i rozszerzenie umiejętności poznanych na zajęciach. Dalsza część zadań pozwoli Ci na poznanie zaawansowanych technik związanych z systemem kontroli wersji, m.in. możliwość edycji historii lub inteligentnego jej przeszukiwania.

Forkowanie i klonowanie

Na koniec wprowadzenia do systemu kontroli wersji wprowadzimy sobie jeszcze dwa pojęcia - forkowanie i klonowanie.

Forkowanie od angielskiego fork - widelec to kopiowanie repozytorium w obrębie jednej platformy (np. Github) na swoje konto. Dzięki temu, możesz zmieniać i rozwijać kod, który zaczął ktoś inny. Nie możesz robić tego w oryginalnej lokalizacji (aby wysyłać commity na Githuba musisz być właścicicielem lub współwykonawcą danego projektu) - dlatego przed jego modyfikacją konieczne jest sforkowanie kodu.

Wykonaj fork repozytorium mwo-fork-me na swoje konto.

Po chwili na Twoim koncie pojawi się nowe repozytorium o nazwie mwo-fork-me. Aby móc modyfikować kod w nim zawarty, musisz pobrać to repozytorium na swój komputer. Operacaja ta nazywana jest klonowaniem repozytorium.

Skopiuj adres sforkowanego repozytorium do schowka

i użyj go do sklonowania repozytorium.

$ git clone https://github.com/$YOUR_USERNAME$/mwo-fork-me.git
Cloning into 'mwo-fork-me'...
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.
Checking connectivity... done.

Zauważ, że sforkowana i sklonowana została cała historia kodu. Od tej pory możesz zacząć modyfikować (tj. tworzyć gałęzie, commity) projekt, który został napisany przez kogoś innego.

Zwróć uwagę na to, że możesz sklonować do siebie również kod z oryginalnej lokalizacji (git clone https://github.com/fracz/mwo-fork-me.git). Jednakże - bez forka - nie będziesz mógł wysłać wprowadzonych zmian z powrotem na Githuba (git push zakończy się błędem).

Czy wiesz ile projektów znajduje się na Githubie? Zobacz!

Dokładnie - jest tam już ponad 2 miliony projektów, których kod źródłowy możesz przypisać do swojego konta za pomocą forkowania i pobrać do siebie, klonując repozytorium. W większości są to projekty open-source, których licencja pozwala na ich użcie w Twoich rozwiązaniach. Nawet sterowanie rakietami i zarządzanie satelitami jest już gotowe :-)

Udało się! Teraz pewnie przyda się chwila przerwy.

Wojciech Frącz, [email protected]