Git - pierwsze kroki

System kontroli wersji to oprogramowanie, które służy do śledzenia zmian wprowadzanych w plikach, na przykład kodzie źródłowym aplikacji, a także wspierania łączenia zmian dokonanych przez wiele osób w różnym czasie, tak jak w przypadku pracy zespołu programistów nad jednym projektem. Git to darmowe oprogramowanie open source, rozproszony system kontroli wersji, który stał się de facto standardem w branży. Więcej można się o nim dowiedzieć ze strony projektu. Warto go znać i stosować w swoich projektach, dlatego w tym artykule pokażę kilka poleceń, które pozwolą na szybki i skuteczny start z tym narzędziem.

Tip: Do zrealizowania przedstawionych kroków niezbędny jest system kontroli wersji Git, przyda się też emulator konsoli cmder. Aby je zainstalować, możesz skorzystać ze wskazówek w naszym wcześniejszym artykule.

Pierwszy projekt

Projekty oparte o Git są rozproszone, ponieważ każdy programista ma na swojej maszynie lokalną kopię repozytorium, która zawiera pełną historię. Może w niej wprowadzać zmiany i okresowo synchronizować je z innymi osobami. Zazwyczaj działa to w oparciu o repozytorium zdalne (ang. remote), które może być utrzymywane na serwerze dostępnym w sieci.

W Internecie jest wiele serwisów, które oferują hosting repozytoriów Git, zazwyczaj dodając do tego liczne funkcjonalności, dostępne przez przyjazny interfejs aplikacji internetowej. Do najbardziej popularnych serwisów należą GitHub, GitLab czy Bitbucket. Na nasze potrzeby skorzystamy z drugiego z wymienionych.

Konto na GitLab

Aby skorzystać z bogatej oferty serwisu GitLab, należy założyć w nim konto użytkownika. Z menu na stronie głównej trzeba wybrać pozycję Register, a następnie wypełnić prosty formularz rejestracji.

Dostęp przez SSH

Do efektywnej pracy z serwerem Git, warto skonfigurować dostęp przez SSH (ang. secure shell), który zapewni wygodne i bezpieczne połączenie.

Tip: Więcej o działaniu SSH możesz dowiedzieć się z artykułu Digital Ocean Understanding the SSH Encryption and Connection Process.

Potrzebne jest do tego utworzenie dwóch specjalnych plików, zawierających parę kluczy (publiczny i prywatny), które posłużą do uwierzytelnienia klienta na serwerze. Do utworzenia kluczy użyjemy schematu Ed25519, opartego o kryptografię asymetryczną krzywych eliptycznych. Aby to zrobić, w emulatorze konsoli cmder wykonaj poniższe kroki:

  1. Wykonaj polecenie (pamiętaj, żeby zmienić adres poczty elektronicznej na własny):
    ssh-keygen -t ed25519 -C "[email protected]"
  2. Potwierdź domyślną ścieżkę /.ssh/id_ed25519 dla plików kluczy wciskając Enter.
  3. Pomiń tworzenie hasła dla klucza wciskając dwa razy Enter.

Następnie musisz skopiować zawartość pliku z kluczem publicznym i zapisać ją w serwisie GitLab. Aby to zrobić, w emulatorze konsoli cmder wykonaj poniższe kroki:

  1. Wykonaj polecenie, aby skopiować klucz publiczny do schowka:
    cat %userprofile%\.ssh\id_ed25519.pub | clip
  2. W serwisie GitLab wejdź w ustawienia użytkownika, klikając swój awatar w prawym górnym rogu i wybierając Settings.
  3. W ustawieniach użytkownika z menu po lewej stronie wybierz pozycję SSH Keys.
  4. Wklej klucz publiczny ze schowka w pole Key, podaj nazwę dla klucza w polu Title i kliknij Add key.

Tip: Klucz prywatny, który jest zapisany w pliku o nazwie id_ed25519, musisz chronić przed ujawnieniem niepowołanym osobom, ponieważ umożliwi on podszycie się pod ciebie. Jeśli masz podejrzenie, że ktoś używa twojego klucza, skasuj wpis o nim w serwisie GitLab i wygeneruj nową parę.

Tip: Przy pierwszym połączeniu z danym serwerem przez SSH konieczne jest potwierdzenie jego tożsamości. W takiej sytuacji pojawia się komunikat The authenticity of host can't be established oraz podany przez serwer odcisk palca klucza SSH. Przed potwierdzeniem nawiązania połączenia powinno się sprawdzić zgodność odcisków palca w wiarygodnym źródle u właściciela serwera. Serwis GitLab publikuje te informacje w dokumentacji na stronie SSH host keys fingerprints.

Utworzenie projektu

Do utworzenia repozytorium Git wystarczy w katalogu, w którym zamierzasz tworzyć pliki projektu, wywołać polecenie git init. Następnie można repozytorium lokalne spushować (wysłać na serwer z użyciem polecenia git push) i powiązać z repozytorium zdalnym.

Tip: Więcej informacji znajdziesz w dokumentacji git init oraz git push (do tego drugiego polecenia jeszcze wrócimy).

Ale nieco szybciej jest utworzyć najpierw puste repozytorium zdalne i sklonować je do swojego repozytorium lokalnego (ściągnąć z serwera z użyciem polecenia git clone). W tym celu w serwisie GitLab na zakładce Projects kliknij przycisk New project:

…a następnie wpisz nazwę sleepy-printer w pole Project name:

…i kliknij przycisk Create project na dole strony:

git config

Na stronie głównej nowoutworzonego projektu znajdziesz kilka podpowiedzi z przydatnymi poleceniami. Pierwsze z nich dotyczą globalnych ustawień Git, które definiuje się przy pomocy polecenia git config. Przy użyciu dwóch podanych komend wskażesz swoje imię i adres poczty elektronicznej, które Git będzie zapisywał przy każdej dodanej przez ciebie zmianie (tak zwanym commit’cie) na potrzeby późniejszej identyfikacji autora (pamiętaj, żeby w poleceniach zmienić dane na własne):

git config --global user.name "John Doe"

git config --global user.email "[email protected]"

Polecenie git config umożliwia także dodawanie aliasów dla komend Git, co zazwyczaj wykorzystuje się w celu zdefiniowania skróconych komend dla najczęściej wykonywanych operacji. Przykładem jest poniższe polecenie:

git log --graph --oneline --all --decorate

…które pokazuje historię wszystkich zmian w repozytorium w postaci czytelnego drzewa. Można je skrócić na przykład do trzech liter dag:

git config --global alias.dag "log --graph --oneline --all --decorate"

Od tego momentu tę samą operację można wykonać wpisując skróconą komendę git dag.

Tip: Więcej informacji znajdziesz w dokumentacji git config.

git clone

W celu utworzenia repozytorium lokalnego, posłużymy się poleceniem sklonowania repozytorium zdalnego (pamiętaj, żeby zmienić nazwę {workspace} na własną):

git clone [email protected]:{workspace}/sleepy-printer.git

W efekcie w konsoli powinieneś zobaczyć wynik działania analogiczny jak poniżej, zostanie też utworzony katalog z pustym projektem:

1
2
3
Cloning into 'sleepy-printer'...
warning: You appear to have cloned an empty repository.
Checking connectivity... done.

Tip: Więcej informacji znajdziesz w dokumentacji git clone.

git commit

Dobrą praktyką jest utworzenie na samym początku pracy z projektem inicjalnego commita bez żadnych istotnych informacji. Taki “pusty” commit może być przydatny, jeśli będziesz chciał w przyszłości wykonać rebase swojej pierwszej właściwej zmiany. Dlatego GitLab podpowiada polecenia, dzięki którym do projektu dodasz pusty plik README.md:

Poniżej nieco je zmodyfikowałem:

cd sleepy-printer

touch README.md

git add README.md

git commit -m "Initial commit"

To po kolei zmiana katalogu na katalog projektu, utworzenie pustego pliku, dodanie go do plików śledzonych przez Git i utworzenie commita z opisem zmian (ang. commit message) Initial commit.

W efekcie w konsoli powinieneś zobaczyć wynik działania analogiczny jak poniżej:

1
2
3
[master (root-commit) ccb6094] Initial commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.

Pozostaje już tylko wykonać komendę, która prześle utworzoną zmianę na serwer GitLab:

git push -u origin master

…co powinno mieć efekt jak poniżej:

1
2
3
4
5
6
Counting objects: 3, done.
Writing objects: 100% (3/3), 215 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To [email protected]:qalabs/blog/git-intro-sleepy-printer.git
* [new branch] master -> master
Branch master set up to track remote branch master from origin.

W poleceniu git commit parametr -m służy do utworzenia commita z samym tytułem zmiany, co nie wymaga otwierania edytora tekstu. Ale commit message zazwyczaj zawiera nie tylko tytuł, ale także szerszy opis zmiany.

Na blogu Chrisa Beama można znaleźć siedem zasad, według których powinien być napisany dobry commit message:

  1. Oddziel tytuł zmiany od reszty opisu pustą linią.
  2. Ogranicz tytuł do 50 znaków.
  3. Zacznij tytuł od wielkiej litery.
  4. Nie dodawaj kropki na końcu tytułu.
  5. Użyj w tytule trybu rozkazującego.
  6. Zawijaj linie opisu na 72 znakach.
  7. W opisie wyjaśnij co i dlaczego zamiast jak.

Tip: Commit messages są kluczowe dla dokumentowania historii rozwoju projektu, a pisać je dobrze wcale nie jest łatwo. Więcej o tej trudnej sztuce dowiesz się z artykułu Davida Thompsona My favourite Git commit, prezentacji Joela Chippindale’a Telling stories through your commits i prezentacji Tekina Süleymana A branch in time.

W commit message bardzo często umieszcza się także dane, które mają na celu powiązanie zmiany w kodzie z informacjami przechowywanymi w innych systemach. Na przykład tytuł zmiany często rozpoczyna się od identyfikatora zgłoszenia w JIRA, w opisie może się pojawić Change-Id, które Gerrit wykorzystuje do powiązania commitów w ramach jednej rewizji itp. Tego typu informacje niejednokrotnie ułatwiają złapanie szerszego kontekstu powiązanego ze zmianą.

Pełne informacje, jakie Git przechowuje dla każdego commita można zobaczyć wywołując komendę git log:

1
2
3
4
5
commit 8a6e93b84adb6a3d3dbd073277358ecfed49ef6b
Author: Maciej Chmielarz <[email protected]>
Date: Sun Jan 26 23:27:00 2020 +0100

Initial commit

Jest to identyfikator commita w postaci hasha SHA-1 (8a6e93b...), informacje o autorze, dokładna data i godzina oraz opis zmian.

Tip: Więcej informacji znajdziesz w dokumentacji git commit.

Domyślny edytor

Dodanie pełnego opisu zmiany jest możliwe przy pomocy edytora tekstu. Po wykonaniu polecenia git commit domyślnie w emulatorze konsoli otwiera się edytor Vim. Aby dodać w nim opis, trzeba przejść do trybu wpisywania (ang. insert) wciskając klawisz i, a po wpisaniu treści zapisać i wyjść z edytora przy pomocy sekwencji klawiszy Esc, :wq, Enter (aby anulować zmiany i wyjść bez zapisywania, zamiast :wq należy wpisać :q!).

Tip: Vim to specyficzny edytor tekstu, który często wywołuje skrajne emocje, to znaczy można go kochać albo nienawidzić. Jego największą zaletą jest to, że jest wszechobecny na systemach z rodziny Unix. Więcej na jego temat dowiesz się ze strony projektu lub artykułu w Wikipedii, a w nauce sprawnego posługiwania się tym edytorem pomoże Interactive Vim tutorial

Domyślny edytor można zmienić według swoich osobistych preferencji. Na przykład aby używać Notepad++, można wykonać polecenie:

$ git config --global core.editor "'C:/Program Files (x86)/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin"

Tip: Wskazówki dla konfiguracji kilku innych popularnych edytorów znajdziesz w pomocy serwisu GitHub na stronie Associating text editors with Git.

.gitignore

Niektóre pliki, które znajdują się w katalogu projektu, nie muszą lub wręcz nie powinny być śledzone w repozytorium Git. Przykładem są skompilowane skrypty Python, które są zapisywane w katalogu __pycache__ (przyspieszają kolejne uruchomienia tego samego skryptu).

Przy aktywnej pracy nad projektem i wprowadzaniu zmian w wielu plikach trudno byłoby cały czas pamiętać i pomijać te pliki, które nie mają być dodane do repozytorium. Dlatego można w katalogu projektu utworzyć specjalny plik .gitignore i umieścić w nim nazwy ścieżek, które mają być ignorowane przez Git.

Tip: Więcej na temat dobrych praktyk korzystania z .gitignore znajdziesz na blogu Juliena Danjou w artykule Properly managing your .gitignore file.

Plik .gitignore powinien być umieszczony w repozytorium wraz z kodem projektu, dlatego utworzymy taki plik z poleceniem ignorowania katalogu __pycache__ i dodamy go do repozytorium. Posłużymy się przy tym poniższymi komendami:

echo __pycache__/ > .gitignore

git add .gitignore

git commit -m "Add __pycache__ to files ignored by Git"

Pierwsze polecenie przekierowuje ciąg znaków __pycache__/ do pliku o nazwie .gitignore i przy okazji go tworzy, bo plik ten dotychczas nie istniał. Pozostałe dwa polecenia to omawiane wyżej dodanie pliku do śledzonych przez Git oraz utworzenie commita ze skróconym opisem.

Pierwsza wersja kodu

Wreszcie przyszedł czas na implementację pierwszej wersji skryptu. sleepy-printer to prosty program, który ma za zadanie wypisać na ekran wszystkie cyfry w sekundowych odstępach czasu. Poniższy kod umieścimy w pliku sleepy_printer.py:

1
2
3
4
5
6
7
8
9
10
11
12
import string
import time

def print_and_sleep(character):
print(character)
time.sleep(1)

def print_digits():
for digit in string.digits:
print_and_sleep(digit)

print_digits()

Skrypt importuje potrzebne moduły z biblioteki standardowej string i type. Następnie definiuje uniwersalną funkcję print_and_sleep, która wypisuje na ekran znak przekazany jako argument wywołania i zatrzymuje wykonanie skryptu na jedną sekundę. Definiuje także funkcję print_digits, która iteruje po znakach zawartych w stałej string.digits i dla każdego z nich wywołuje funkcję print_and_sleep. W ostatniej linii zawarte jest wywołanie funkcji print_digits.

Teraz wystarczy uruchomić skrypt, żeby zweryfikować, czy działa poprawnie:

python sleepy_printer.py

Wywołanie komendy git status pokaże, że mamy jeden plik, który nie jest śledzony przez Git, to znaczy jest na liście untracked files. Git podpowiada też nam (i to w dwóch miejscach), że dla dodania pliku powinnśmy użyć komendy git add.

1
2
3
4
5
6
7
8
9
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Untracked files:
(use "git add <file>..." to include in what will be committed)

sleepy_printer.py

nothing added to commit but untracked files present (use "git add" to track)

Implementację pierwszej wersji zakończymy dodaniem commita ze zmianami:

git add sleepy_printer.py

git commit

To otworzy edytor tekstu, w którym wprowadzimy commit message:

1
2
3
4
5
6
7
8
9
10
11
12
13
Add printing digits in one-second intervals

Add script that prints each digit from 0-9 range every second.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
# (use "git push" to publish your local commits)
#
# Changes to be committed:
# new file: sleepy_printer.py
#

Po zapisaniu pliku i zamknięciu edytora, utworzy się commit, co potwierdza komunikat w terminalu:

1
2
3
[master 80e5e40] Add printing digits in one-second intervals
1 file changed, 12 insertions(+)
create mode 100644 sleepy_printer.py

git diff

W kolejnym kroku dodamy do skryptu podobną funkcjonalność wypisania wszystkich małych i wielkich liter alfabetu łacińskiego, będzie miał następującą postać:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import string
import time

def print_and_sleep(character):
print(character)
time.sleep(1)

def print_digits():
for digit in string.digits:
print_and_sleep(digit)

def print_letters():
for letter in string.ascii_letters:
print_and_sleep(letter)

print_digits()
print_letters()

Do sprawdzenia wprowadzonych zmian wykorzystamy polecenie git diff. Jest to uniwersalne narzędzie do czytelnego przedstawiania różnic pomiędzy plikami, a jednym z jego najczęstszych zastosowań jest podgląd zmian wprowadzonych w śledzonych plikach od ostatniego commita (linie oznaczone + to te dodane, oznaczone - to usunięte).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
diff --git a/sleepy_printer.py b/sleepy_printer.py
index f924306..bc77a2b 100644
--- a/sleepy_printer.py
+++ b/sleepy_printer.py
@@ -9,4 +9,9 @@ def print_digits():
for digit in string.digits:
print_and_sleep(digit)

+def print_letters():
+ for letter in string.ascii_letters:
+ print_and_sleep(letter)
+
print_digits()
+print_letters()

Po zweryfikowaniu, że wprowadzone zmiany są zgodne z oczekiwaniami, możemy dodać kolejny commit:

git add sleepy_printer.py

git commit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Add printing letters in one-second intervals

Update script to print lowercase and uppercase letters from Latin alphabet
every second.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is based on 'origin/master', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
#
# Changes to be committed:
# new file: sleepy_printer.py
#

Tip: Więcej informacji znajdziesz w dokumentacji git diff.

git rebase

Dzięki poleceniu git rebase można przesunąć część historii repozytorium, tak żeby wychodziła z innego miejsca w drzewie zmian. Jednym z zastosowań tej komendy jest także wprowadzanie poprawek we wcześniejszych commitach, kiedy powstały już na nich kolejne zmiany.

W tej chwili historia zmian naszego repozytorium jest następująca:

git dag

1
2
3
4
* d3f8f26 (HEAD -> master) Add printing letters in one-second intervals
* 80e5e40 Add printing digits in one-second intervals
* be8669e Add __pycache__ to files ignored by Git
* 8a6e93b (origin/master) Initial commit

Załóżmy, że w tym momencie zorientowaliśmy się, że wartość parametru funkcji sleep nie powinna być podana na sztywno jako 1 sekunda, ale przekazywana do funkcji print_and_sleep jako argument, z możliwością dostosowania do przypadku użycia. Wymaga to wprowadzenia zmian w commit’cie Add printing digits in one-second intervals.

Musimy się cofnąć przy użyciu git rebase do ostatniego commita, który chcemy pozostawić bez zmian, czyli Add __pycache__ to files ignored by Git. W tym celu skopiujemy jego identyfikator i podamy w komendzie:

git rebase --interactive be8669e

Otworzy to edytor tekstu, w którym wskażemy, które commity i w jaki sposób zamierzamy zmodyfikować:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pick 80e5e40 Add printing digits in one-second intervals
pick d3f8f26 Add printing letters in one-second intervals

# Rebase be8669e..d3f8f26 onto be8669e (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Ponieważ chcemy edytować commit Add printing digits in one-second intervals, więc jego linia musi wyglądać następująco:

1
edit 80e5e40 Add printing digits in one-second intervals

Zapisujemy informacje i wychodzimy z edytora. Git informuje nas o wykonanej operacji i podpowiada dalsze kroki:

1
2
3
4
5
6
7
8
Stopped at 80e5e400bf5701e6b0981903216d58c08e2501ff... Add printing digits in one-second intervals
You can amend the commit now, with

git commit --amend

Once you are satisfied with your changes, run

git rebase --continue

W tej chwili stan plików w repozytorium jest taki, jak po dodaniu commita Add printing digits.... Możemy przejść do edytora i wprowadzić konieczne zmiany:

1
2
3
4
5
6
7
8
9
10
11
12
import string
import time

def print_and_sleep(character, interval=1):
print(character)
time.sleep(interval)

def print_digits():
for digit in string.digits:
print_and_sleep(digit)

print_digits()

Komenda git diff pokaże nam, co się zmieniło:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
diff --git a/sleepy_printer.py b/sleepy_printer.py
index f924306..57468bf 100644
--- a/sleepy_printer.py
+++ b/sleepy_printer.py
@@ -1,12 +1,12 @@
import string
import time

-def print_and_sleep(character):
+def print_and_sleep(character, interval=1):
print(character)
- time.sleep(1)
+ time.sleep(interval)

def print_digits():
for digit in string.digits:

Po zweryfikowaniu wprowadzonych zmian, możemy dodać plik i poprawić commit:

git add sleepy_printer.py

git commit --amend

Ponieważ nie mamy potrzeby zmieniania commit message, to po otwarciu edytora tekstu wystarczy zapisać proponowaną treść i z niego wyjść. Wykonana operacja zostanie potwierdzona komunikatem:

1
2
3
4
[detached HEAD 97833cc] Add printing digits in one-second intervals
Date: Sun Jan 26 23:32:02 2020 +0100
1 file changed, 12 insertions(+)
create mode 100644 sleepy_printer.py

Zakończenie operacji wymaga jeszcze wywołania komendy:

git rebase --continue

…potwierdzonej komunikatem:

1
Successfully rebased and updated refs/heads/master.

Rzut oka na historię pozwala stwierdzić, że dwa najnowsze commity zmieniły swoje identyfikatory:

git dag

1
2
3
4
* 709504d (HEAD -> master) Add printing letters in one-second intervals
* 000c965 Add printing digits in one-second intervals
* be8669e Add __pycache__ to files ignored by Git
* 8a6e93b (origin/master) Initial commit

Tip: Więcej informacji znajdziesz w dokumentacji git rebase.

git blame

git blame to bardzo ciekawe polecenie, które umożliwia sprawdzenie, kto i kiedy utworzył lub zmodyfikował każdą z linii kodu. Jest ono niezastąpione na przykład gdy analizujemy jakiś fragment programu i szukamy szerszego kontekstu dla jego zrozumienia, ponieważ dzięki git blame można z łatwością odnaleźć odpowiedni commit message oraz osobę, która miała udział w powstaniu tego kodu.

Dla przykładu, poniższa komenda:

git blame sleepy_printer.py

…pokaże informacje o commitach dla każdej z linii skryptu sleepy_printer.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100  1) import string
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 2) import time
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 3)
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 4) def print_and_sleep(character, interval=1):
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 5) print(character)
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 6) time.sleep(interval)
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 7)
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 8) def print_digits():
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 9) for digit in string.digits:
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 10) print_and_sleep(digit)
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 11)
709504d6 (Maciej Chmielarz 2020-01-26 23:34:42 +0100 12) def print_letters():
709504d6 (Maciej Chmielarz 2020-01-26 23:34:42 +0100 13) for letter in string.ascii_letters:
709504d6 (Maciej Chmielarz 2020-01-26 23:34:42 +0100 14) print_and_sleep(letter)
709504d6 (Maciej Chmielarz 2020-01-26 23:34:42 +0100 15)
000c9651 (Maciej Chmielarz 2020-01-26 23:32:02 +0100 16) print_digits()
709504d6 (Maciej Chmielarz 2020-01-26 23:34:42 +0100 17) print_letters()

Jak widać w kolumnie po lewej stronie, większość z nich powstała wraz z commitem o identyfikatorze 000c9651, natomiast linie 12-15 oraz 17 wraz z commitem o identyfikatorze 709504d6.

Tip: Więcej informacji znajdziesz w dokumentacji git blame.

git push

Po utworzeniu skryptu z dwoma istotnymi funkcjonalnościami możemy umieścić kod w zdalnym repozytorium w serwisie GitLab. Służy do tego komenda git push. Po jej wykonaniu w terminalu powinien pojawić się komunikat analogiczny do poniższego:

1
2
3
4
5
6
7
Counting objects: 9, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 1.02 KiB | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To [email protected]:qalabs/blog/git-intro-sleepy-printer.git
8a6e93b..709504d master -> master

Historia pokaże wtedy, że HEAD lokalnego repozytorium, lokalny branch master i zdalny branch origin/master wskazują na ten sam commit

1
2
3
4
* 709504d (HEAD -> master, origin/master) Add printing letters in one-second intervals
* 000c965 Add printing digits in one-second intervals
* be8669e Add __pycache__ to files ignored by Git
* 8a6e93b Initial commit

Trzeba pamiętać, że od momentu umieszczenia zmian w zdalnym repozytorium nie można “bezkarnie” zmieniać lokalnej historii.

Tip: Więcej informacji znajdziesz w dokumentacji git push.

git pull

W zdalnym repozytorium mogą pojawić się zmiany, których nie ma w lokalnym, na przykład dostarczone przez inne osoby pracujące nad projektem. Można je pobrać i połączyć ze swoim lokalnym repozytorium przy pomocy komendy git pull.

Ponieważ w tej chwili lokalne i zdalne repozytorium są takie same, więc wywołanie komendy nie będzie miało żadnego efektu, co Git oznajmi poniższym komunikatem:

1
Already up-to-date.

Tip: Więcej informacji znajdziesz w dokumentacji git pull.

git checkout

W repozytorium jest już kilka commitów, może być też więcej branchy niż tylko master. Do przełączania się pomiędzy nimi służy polecenie git checkout. Możemy je wykorzystać na przykład do tego, aby przywołać stan katalogu z projektem z dowolnego momentu w historii zmian.

git checkout be8669e

Po wykonaniu tej komendy katalog będzie w stanie z chwili dodania commita Add __pycache__ to files ignored by Git.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Note: switching to 'be8669e'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

git switch -c <new-branch-name>

Or undo this operation with:

git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at be8669e... Add __pycache__ to files ignored by Git

Jest to tak zwany stan detached HEAD w którym nie można dodawać zmian, chyba że utworzy się w tym celu nowy branch, co uczynnie podpowiada Git w powyższym komunikacie.

Tip: Komenda git switch pojawiła się jako eksperymentalna w wersji Git 2.23 w sierpniu 2019. We wcześniejszych wersjach Git proponował w tym samym celu użycie komendy git checkout -b <new-branch-name> i taka możliwość nadal istnieje. Więcej na temat git switch możesz przeczytać w artykule HHighlights from Git 2.23 oraz dokumentacji git switch.

Do dalszej pracy z głównym branchem repozytorium można powrócić wykonując komendę git checkout master.

Tip: Więcej informacji znajdziesz w dokumentacji git checkout.

git branch

Modelem wykorzystywanym do przedstawienia historii zmian w repozytorium Git jest drzewo z gałęziami. Każde repozytorium ma zazwyczaj główny branch (nazywany master lub czasem mainline), który zawiera stabilną wersję kodu. Funkcjonalności rozwijane w innych branchach są z czasem łączone do mastera. Jest to często wykorzystywana funkcja Git, który pozwala na utrzymywanie w repozytorium wielu branchy, ułatwiających zarządzanie zmianami.

Wykorzystamy tę funkcję do stworzenia tak zwanego feature brancha, na którym będziemy rozwijać funkcjonalność zmiany odstępu czasowego pomiędzy wypisaniem na ekran kolejnych znaków w zależności od ich typu. Cyfry będą wypisywane na ekran co 750 ms, małe litery co 500 ms, a wielkie litery co 250 ms.

Najpierw utworzymy branch o nazwie związanej z rozwijaną funkcjonalnością:

git checkout -b dynamic_interval

…co zostanie potwierdzone komunikatem:

1
Switched to a new branch 'dynamic_interval'

Następnie wprowadzimy do kodu zmianę dla cyfr:

git diff

1
2
3
4
5
6
7
8
9
10
11
12
13
diff --git a/sleepy_printer.py b/sleepy_printer.py
index 7706c88..baa553d 100644
--- a/sleepy_printer.py
+++ b/sleepy_printer.py
@@ -7,7 +7,7 @@ def print_and_sleep(character, interval=1):

def print_digits():
for digit in string.digits:
- print_and_sleep(digit)
+ print_and_sleep(digit, 0.75)

def print_letters():
for letter in string.ascii_letters:

…i potwierdzimy stosownym commitem:

git add sleepy_printer.py

git commit -m "Update time interval for digits to 750 ms"

Następnie wprowadzimy zmiany dla liter, uwzględniając różne wartości odstępu czasowego dla małych i wielkich liter:

git diff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
diff --git a/sleepy_printer.py b/sleepy_printer.py
index baa553d..d362a79 100644
--- a/sleepy_printer.py
+++ b/sleepy_printer.py
@@ -11,7 +11,10 @@ def print_digits():

def print_letters():
for letter in string.ascii_letters:
- print_and_sleep(letter)
+ if letter.islower():
+ print_and_sleep(letter, 0.5)
+ else:
+ print_and_sleep(letter, 0.25)

print_digits()
print_letters()

…i również potwierdzimy stosownym commitem:

git add sleepy_printer.py

git commit -m "Update time interval for letters to 500/250 ms"

W tym momencie na branchu dynamic_interval jest kompletna funkcjonalność, gotowa do połączenia z głównym branchem master.

Tip: Więcej informacji znajdziesz w dokumentacji git branch.

Współpraca wielu autorów

Jak wspomniałem na początku, jednym z głównych założeń systemu kontroli wersji jest wsparcie dla łączenia zmian wprowadzanych w projekcie przez różne osoby w różnym czasie. Poniżej opisuję standardowy workflow, który stosuje się w tym celu w projektach utrzymywanych w serwisie GitLab.

Feature branch i merge request

GitLab korzysta z koncepcji feature branchy, które można umieszczać w zdalnym repozytorium i tworzyć dla nich merge requests. Dzięki temu inne osoby pracujące nad projektem uzyskują dostęp do nowego kodu, mają możliwość jego przejrzenia i zgłoszenia uwag, a na koniec połączenia z głównym branchem.

Aby utworzyć merge request, trzeba najpierw umieścić feature branch w zdalnym repozytorium:

git push -u origin dynamic_interval

Komunikat w konsoli zawiera potwierdzenie utworzenia brancha w zdalnym repozytorium, a nawet link do strony, na której można utworzyć merge request:

1
2
3
4
5
6
7
8
9
10
11
12
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 613 bytes | 0 bytes/s, done.
Total 6 (delta 4), reused 2 (delta 2)
remote:
remote: To create a merge request for dynamic_interval, visit:
remote: https://gitlab.com/qalabs/blog/git-intro-sleepy-printer/-/merge_requests/new?merge_request%5Bsource_branch%5D=dynamic_interval
remote:
To [email protected]:qalabs/blog/git-intro-sleepy-printer.git
* [new branch] dynamic_interval -> dynamic_interval
Branch dynamic_interval set up to track remote branch dynamic_interval from origin.

Merge request tworzy się w interfejsie aplikacji GitLab:

Po utworzeniu, na stronie merge request można znaleźć wszystkie szczegóły związane z wprowadzanymi zmianami:

W tym na przykład dodawać komentarze:

A ostatecznie połączyć zmiany z głównym branchem klikając przycisk Merge. Teraz pozostaje ściągnąć merge commit do lokalnego repozytorium przy pomocy komendy git pull.

Klient Git z GUI

Do pracy z lokalnymi repozytoriami można wykorzystywać nie tylko terminal i komendy linii poleceń, ale także aplikacje z graficznym interfejscem użytkownika. Git ma wbudowane narzędzia git-gui i gitk (które Git for Windows integruje jako Git GUI), wsparcie dla repozytoriów Git mają też zintegrowane środowiska programistyczne (ang. IDE, integrated development environment).

Lista najbardziej popularnych aplikacji dla różnych platform dostępna jest na stronie dokumentacji Git.

Dalsze informacje

Git to system o wielu funkcjach i ogromnych możliwościach. Do dalszej nauki można wykorzystać:

Maciej Chmielarz

Nazywam się Maciej Chmielarz. Pracuję w branży informatycznej od 2008 roku. Doświadczenie zdobywałem u pracodawców należących do polskiej i światowej czołówki firm technologicznych, producentów unikatowych rozwiązań inżynierskich. Chętnie dzielę się wiedzą, prowadzę wykłady z testowania oprogramowania, występuję na konferencjach i spotkaniach branżowych oraz aktywnie uczestniczę w ich organizacji. Jestem pasjonatem cyberbezpieczeństwa i budowania świadomości informatycznej w społeczeństwie.