Kurs Ruby on Rails

Kurs Ruby on Rails – Lekcja 1 – GIT

Czego się nauczycie?

  • Czym jest GIT i do czego służy
  • Tworzyć commity i nowe branche
  • Załadować lokalne zmiany na zdalny serwer repozytorium
  • Pracować ze zdalnym repozytorium
  • Używać branchy oraz pull requestów
  • Jak działa code review
  • Jak sobie radzić z konfliktami

Niezbędnik kursu:

Słowniczek

branch – gałąź, odnoga naszego repozytorium
commit – paczka z naszymi zmianami
pull – pobranie zmian ze zdalnego repozytorium
push – wrzucanie zmian na zdalne repozytorium

1. Co to jest GIT?

Git (Global Information Tracker) jest jednym z darmowych systemów kontroli wersji.

Oprogramowania te służą do śledzenia zmian w kodzie, umożliwiając równoległą współpracę między programistami.

Z każdego kawałka napisanego kodu tworzymy “commit”, który zawiera nasze zmiany, ich opis oraz autora; w ten sposób tworzymy historię zmian. W dowolnym momencie mamy możliwość zobaczenia dokładnych zmian dowolnego pliku oraz, jeśli jest taka potrzeba, cofnięcie się do konkretnego momentu.

Aby jeszcze bardziej uporządkować pracę, powinniśmy korzystać z branchy.

Domyślnie w repozytorium główną branchą jest master i nie można jej usunąć.

Ale tworząc nowy element aplikacji możemy stworzyć nową, na której będziemy umieszczać nasze commity. Póki nie skończymy całego elementu, nie będzie on robić bałaganu na głównej gałęzi. W tym czasie inni członkowie zespołu pracują na swoich gałęziach. Po skończeniu funkcjonalności, łączymy naszą branch z główną.

2. Podstawy używania GIT

1. Zaczynamy od stworzenia katalogu, w którym będziemy pracować.

$ mkdir kurs-ror
$ cd kurs-ror

2. Inicjalizujemy repozytorium lokalnie

$ git init

Polecenie stworzy katalog .git, w środku którego znajdziemy plik konfiguracyjny oraz całą lokalną zawartość repozytorium.

3. Tworzymy plik i zapisujemy go.

$ touch init.txt

4. Przygotowujemy plik do commita

  • $ git status – powie nam, że plik init.txt jest “untracked”, czyli nie będzie umieszczony w commicie oraz, że nie jest śledzony przez repozytorium. Oznacza to, że nie zobaczymy zmian używając $ git diff.
  • Aby dodać pojedynczy plik używamy $ git add init.txt, można też dodać wszystkie zmienione pliki za pomocą $ git add ..
  • Teraz polecenie $ git status powie, że plik init.txt będzie zawarty w naszym commicie i będzie oznaczony jako “staged”.

5. Commit

$ git commit -m "opis zmian"

Polecenie stworzy commit, ale w naszym lokalnym repozytorium. Teraz $ git status nic nie wyświetli, ponieważ nie mamy aktualnie już żadnych zmian.

$ git log pokazuje wszystkie commity na aktualnej branchy, a więc wyświetli nam nasz pierwszy commit.

Wiadomość commita jest bardzo ważna. Często, zwłaszcza w większych i dłuższych projektach, warto dodać dłuższy opis wprowadzanej zmiany. Skupiamy się wtedy na tym, dlaczego wprowadzamy taką zmianę i tłumaczymy zawiłości.

Kiedy po czasie wrócimy do danego fragmentu kodu, pomoże nam to w szybkim przypomnieniu całej sytuacji. A dla innych członków zespołu, będzie to oszczędnością czasu i nerwów.

Przeciwieństwem polecenia $ git add jest $ git reset, które wycofuje pliki ze staged area – czyli tych, które zostaną umieszczone w commicie. Natomiast jeśli chcemy wycofać poprzedni commit, polecenie jest już bardziej skomplikowane. $ git reset --soft HEAD~1 oznacza zdjęcie najnowszego commita i umieszczenie całej jego zawartości jako pliki not staged. Liczba za HEAD~ wskazuje na ilość commitów, do zdjęcia. Flaga --soft oznacza, że zmiany chcemy zachować, ale nie w commicie. Flaga --hard usunie commit wraz z jego zmianami.

Jeśli ten commit znajduje się już w zdalnym repozytorium (punkt 7.) należy zachować szczególną ostrożność, ponieważ tworzy to bardzo dużo komplikacji. Na ten moment, w takich przypadkach możemy uznać, że to polecenie nie istnieje 🙂

6. Zmiana

Dodajemy linijkę tekstu do wcześniej utworzonego pliku:

$ echo "hello world" > init.txt

$ git status powie nam tym razem, że zmiany w pliku są not staged, ale plik jest już śledzony w repozytorium, więc polecenie $ git diff wyświetli nam nasze zmiany. Dzięki temu możemy na bieżąco kontrolować nasze wprowadzone zmiany i pilnować, by nie umieścić tam czegoś czego nie powinno być.

7. Remote – zdalny serwer

Do lokalnego repozytorium możemy podłączyć dowolną ilość zdalnych, używając innych nazw. Za pomocą polecenia:

$ git remote -v

Możemy wyświetlić nazwy i adresy wszystkich zdalnych repozytoriów. Aby dodać nowy remote, musimy stworzyć najpierw repozytorium GIT. Jeśli nie mamy konta, to je tworzymy wchodząc na https://github.com/signup. Następnie generujemy klucz SSH wpisując w terminalu:

ssh-keygen -t rsa -b 4096

tworzymy go w domyślnym katalogu oraz nadajemy hasło.

Wygenerują nam się dwa klucze, prywatny oraz publiczny. Kopiujemy zawartość pliku ~/.ssh/id_rsa.pub i wklejamy go do naszego konta na Githubie: https://github.com/settings/ssh/new. W terminalu dodajemy do konfiguracji GITa informacje o nas:

git config --global user.name >nazwa konta na githubie<
git config --global user.email >adres email<

Następnie tworzymy repozytorium, wchodząc na stronę: https://github.com/new.Podajemy nazwę oraz możemy zaznaczyć, aby było prywatne. Po zapisaniu ustawień wyświetli się nam instrukcja, jak połączyć się z repozytorium, wyświetla się ona tylko gdy jest puste. Klikamy na przycisk Code oraz wybieramy opcję SSH i kopiujemy link.

Dodajemy utworzone repozytorium jako origin:

$ git remote add origin >LINK<

Teraz możemy zrobić push naszego commita na zdalne repozytorium:

$ git push origin master

Po odświeżeniu strony, plik pojawi się w katalogu repozytorium.

8. gitignore

Bardzo często mamy rzeczy, które nie powinny lub nie mogą znaleźć się w repozytorium (np. logi). Możemy stworzyć plik .gitignore, w naszym głównym katalogu, zapisując w nim ścieżki do plików, których nie chcemy umieszczać w repozytorium. W internecie możemy znaleźć już gotowe pliki dla poszczególnych języków programowania ze specyficznymi dla nich ścieżkami. Nie znaczy to oczywiście, że w razie potrzeby nie możemy dodać czegoś swojego. https://github.com/github/gitignore/blob/master/Rails.gitignore

3. Branche oraz pull requesty

Branche pozwalają nam na uporządkowanie pracy; by stworzyć nową możemy użyć komendy:

$ git branch test_branch

a następnie, aby na nią przejść:

$ git checkout test_branch

Możemy również użyć skrótu i stworzyć ją i od razu na nią przejść:

$ git checkout -b test_branch

Aktualna branch i inne istniejące (lokalnie), możemy sprawdzić za pomocą komendy:

$ git branch

Branch jest tworzona na podstawie tej, na której aktualnie jesteśmy, tzn. będzie zawierać wszystkie jej commity.

Należy pamiętać, że bierzemy jedynie commity lokalne, a więc warto przed stworzeniem nowej branchy pobrać aktualną wersję za pomocą $ git pullorigin >Nazwa branchy<. (jeśli jesteśmy na głównej branchy to nazwą będzie master)

Gdy już jesteśmy na naszej nowej gałęzi, tworzymy nowy plik.

$ echo "hello world (again)" > some_file.txt

Analogicznie do operacji, które robiliśmy wcześniej, wrzucamy nowy plik do naszego zdalnego repozytorium za pomocą komend:

$ git add .
$ git commit -m "nowy plik testowy"
$ git push origin test_branch

Możemy teraz zaobserwować, że $ git log wyświetli nam dwa commity. Pierwszy, który zrobiliśmy na poprzedniej gałęzi i nasz aktualny.

Po przejściu na master i sprawdzeniu logów zobaczymy już tylko jeden commit, a w katalogu nie będzie naszego nowego pliku.

$ git checkout master
$ git log

Teraz przechodzimy na github i na stronie naszego repozytorium klikamy w Pull requests oraz w New pull request. Robimy tak, by base było master, a source test_branch. Czyli zawartość branchy test_branch będziemy dodawać do master . Wyświetlą się nam wszystkie różnice pomiędzy nimi. W tym momencie możemy zweryfikować, czy jest wszystko, co chcieliśmy dodać oraz czy nie ma rzeczy, których nie powinno być. Jeśli wszystko jest OK, klikamy Create pull request. Jeśli coś jest nie tak, naprawiamy to lokalnie i tworzymy nowy commit.

Nazwą pull requestu domyślnie jest wiadomość ostatniego commita. Możemy ją dowolnie zmienić, tak aby jak najlepiej opisywała co znajduje się w dodawanych zmianach. Możemy również dodać opis. Następnie klikamy Create pull request.

Code review polega na sprawdzeniu naszych zmian przez innych członków zespołu. W zakładce Files changed, klikając na konkretny numer linii, mamy opcję napisania komentarza i zaproponowania lepszych rozwiązań lub oznaczyć błędy, które znajdziemy. Cały ten proces pomaga utrzymać kod w lepszym stanie, z mniejszą ilością błędów, ale również pozwala innym członkom zespołu śledzić zmiany w aplikacji.

Jeśli jesteśmy gotowi dodać już nasze zmiany do głównej gałęzi, w zakładce Conversation znajduje się przycisk Merge pull request. Naciśnięcie go sprawi dodanie wszystkich naszych commitów do branchy master.

Teraz, znajdując się na głównej branchy w terminalu, sprawdzając zmiany

$ git log

nic nowego się nie pojawi, ponieważ musimy najpierw pobrać zawartość z repozytorium.

$ git pull origin master
$ git log

I teraz widzimy, że pojawiły się dwa commity. Jeden, który sami stworzyliśmy z nowym plikiem, a drugi pokazujący, że był to pull request wyświetlając jego nazwę oraz nazwę branchy.

4. Dobre praktyki

Najważniejsze w używaniu GIT’a jest utrzymywanie czystej historii zmian oraz kontrola tego, co znajdzie się na głównej branchy. Żeby tego dokonać, powinniśmy:

1. Zawsze pracować na osobnej branchy i tworzyć Pull Requesty do głównej gałęzi sprawdzając, czy nie znajduje się tam coś niepożądanego.

2. Nazwy branchy powinny opisywać, to co będą zawierać, tak samo jak commity i jeśli nie pracujemy sami, to opisy pull requestów.

Dodatkowo dobrą praktyką jest tworzenie wszystkich opisów i nazw po angielsku.

5. Konflikty

Pracując w repozytorium z innymi, możemy natrafić na konflikty. Jeśli podczas zaciągania zmian z repozytorium, posiadamy lokalnie edytowaną tę samą linijkę, wtedy git nie będzie potrafił nanieść zmian i będziemy zmuszeni sami zaznaczyć, jak ma wyglądać wynik. Analogiczna sytuacja wystąpi podczas istnienia dwóch PullRequestów ingerujących w tą samą linijkę. Gdy pierwszy z nich zostanie dołączony do głównej branchy, drugi będzie mieć konflikt.

Będąc na głównej branchy ($ git checkout master), która jest aktualna ($ git pull origin master), przejdźmy na nową gałąź:

$ git checkout -b branch_1

Dodajmy linijkę tekstu do naszego istniejącego pliku some_file.txt:

$ echo "Change from branch_1" >> some_file.txt

Taką zmianę pushujemy na naszą branch i zakładamy Pull Request. (nie mergujemy go)

$ git add .
$ git commit -m "Added line to some_file"
$ git push origin branch_1

Przełączamy się na główną branch ($ git checkout master) i tworzymy na jej podstawie drugą branch (nie będzie ona zawierać powyższej zmiany):

$ git checkout -b branch_2

Następnie dodajemy do tego samego pliku inny tekst:

$ echo "Change from branch_2" >> some_file.txt

I analogicznie robimy commit i push.

$ git add .
$ git commit -m "Added conflict line to some_file"
$ git push origin branch_2

Tworzymy Pull Request.

W takim stanie posiadamy dwa Pull Requesty, które możemy zmergować do głównej branchy. Klikamy Merge na pierwszym Pull Requeście (na bazie branch_1).

Teraz wchodząc w drugi Pull Request zobaczymy, że merge jest niemożliwy, ponieważ występuje konflikt w pliku some_file.txt.

Aby go rozwiązać, musimy pobrać aktualną wersję branchy master.

$ git checkout master
$ git pull origin master

Następnie, po powrocie na naszą branch, gdzie chcemy rozwiązać konflikt, dołączamy aktualny stan branchy master.

$ git checkout branch_1
$ git merge master

Dostaniemy komunikat, że po zmergowaniu branchy master, posiadamy konflikt. Nasz plik wygląda w tym momencie w następujący sposób:

hello world (again)
<<<<<<< HEAD
Change from branch_2

=======
Change from branch_1
>>>>>>> master

Pomiędzy znacznikami <<<<<<< HEAD i ======= znajduje się kod z naszej branchy, a od ====== do >>>>>>> master znajduje się kod z branchy nazwanej w znaczniku, w tym wypadku master.

Aby pozbyć się konfliktu, musimy usunąć te znaczniki, zachowując przy tym pożądany przez nas wynik. Możemy zachować obie zmiany, jedną z nich lub nawet połączyć je w nieoczywisty sposób, np.:

hello world (again)
Change from branch_1 and branch_2

Po rozwiązaniu konfliktu, robimy commit. Natomiast nie podając jego nazwy, utworzy się commit z komunikatem o operacji. W tym wypadku będzie to Merge branch 'master' into branch_2.

$ git add .
$ git commit
$ git push origin branch_2

(z edytora commitu wychodzimy poprzez wciśnięcie kombinacji klawiszy :wq)

Teraz nasz PullRequest jest gotowy do zmergowania.

6. Pobieranie istniejącego repozytorium

Na stronie repozytorium Github znajduje się zielony przycisk “Code”. Po jego wciśnięciu dostaniemy kilka opcji dostępu. Wybieramy opcję ssh i kopiujemy zawartość.

W terminalu, w miejscu w którym chcemy pobrać repozytorium, używamy komendy:

$ git clone >ssh link repozytorium<

Wszystkie pliki zostaną pobrane do katalogu o nazwie takiej samej jak repozytorium.

Tagi

Istnieje możliwość wersjonowania/tagowania konkretnych stanów repozytorium. Można je podejrzeć klikając na listę branchy i przełączając widok na tags. W dalszej części kursu będziemy wersjonować każdą lekcję, tak aby po skończeniu kursu, można było wybrać konkretną lekcję. Prace własne będą również udostępniane jako tag, ale też jako pull request, aby można było zobaczyć zmiany. Aby pobrać konkretną wersję do nowego katalogu, możemy użyć polecenia:

git clone >ssh link repozytorium< --branch >nazwa taga<

lub jeśli już posiadamy sklonowane repozytorium, możemy pobrać tag w ten sam sposób co branch:

git checkout >nazwa taga<

CheatSheet

$ git pull origin >branch< – pobiera zawartość gałęzi ze zdalnego repozytorium na gałąź, na której się aktualnie znajdujemy

$ git branch >branch< – tworzy nową gałąź

$ git checkout >branch< – zmienia gałąź

$ git checkout -b >branch< – tworzy i zmienia gałąź

$ git add – wszystkie wprowadzone zmiany zostaną przygotowane do commita

$ git commit -m >wiadomosc< – tworzy commit

$ git push origin >branch< – umieszcza nasze zmiany na zdalnym repozytorium

$ git merge >branch< – dołącza podaną lokalną branch do tej, na której się aktualnie znajdujemy

$ git clone >ssh link< – pobiera repozytorium

Dodatkowo:

$ git checkout . – wycofa wprowadzone zmiany, które są not staged do stanu z ostatniego commita

$ git fetch origin >branch< – pobierze branch ze zdalnego repozytorium, by móc się przełączyć na jej lokalną wersję.

Dodając origin/ przed nazwą branchy w dowolnym poleceniu, wymuszamy użycie branchy z remote origin .

Praca własna

1. Pobierz repozytorium z naszym kursem: https://github.com/binarapps/ruby-on-rails-course

2. Stwórz nową branch, której nazwa zaczyna się Twoimi inicjałami

3. Zedytuj plik requirements.txt, dodając w drugiej linijce “I don’t know it yet”, czwartej linijce “I know GIT”.

4. Domerguj branch git-branch-pr. (Uwaga: branch znajduje się na zdalnym repozytorium, nie posiadacie jej lokalnie. GIT Wam podpowie co zrobić.)

5. Rozwiąż konflikty wybierając w drugiej linijce tekst pochodzący z branchy git-branch-pr, a w czwartej ten, który został dodany przez Ciebie.

Repozytorium to będzie używane w dalszej części kursu. Na głównej branchy będziemy aktualizować stan aplikacji oraz umieszczać rozwiązania prac własnych. Możecie dodać sobie drugi remote, który będzie Waszym repozytorium, by przypadku większych problemów, podczas wykonywania instrukcji, dać nam możliwość zajrzenia w Wasz kod.

Przeczytaj również o...

Dołącz do naszego zespołu!

Zobacz oferty pracy