W poprzedniej lekcji zapisaliśmy trzy wersje pliku README.md i porównaliśmy je w historii Gita. Cała praca została na maszynie wirtualnej z Debianem. Teraz połączysz repozytorium devops-lab z GitHubem, pobierzesz jego drugą kopię i prześlesz zmiany między nimi. Następnie rozbudujesz projekt o skrypty, nad którymi popracujesz na osobnych gałęziach. Git i Github to dwie różne rzeczy dlatego warto je zrozumieć.
Punkt startowy: repozytorium z poprzedniej lekcji
Pracuj na tym samym koncie użytkownika Debiana, na którym masz repozytorium. W tym przykładzie katalog projektu znajduje się w ~/devops-lab. Jeśli zapisałeś go gdzie indziej, dostosuj ścieżkę.
cd ~/devops-lab
git status
git branch --show-current
git log --oneline -3
Przed dalszymi ćwiczeniami doprowadź repozytorium do stanu z końca poprzedniej lekcji: README powinien zawierać trzy commity, a git status nie powinien zgłaszać oczekujących zmian.

Git, GitHub i zdalne repozytorium
Jak już wiesz Git to narzędzie do zarządzania wersjami projektu. Kiedy używasz Gita, inicjujesz go w dowolnym folderze, w którym chcesz zapisać pracę, co zamienia ten folder w repozytorium Gita, umożliwiając śledzenie zmian i zapewniając doskonałą historię plików podczas pracy. Oprócz tego istnieje również Github, czyli miejsce do przechowywania repozytoriów git online, dzięki czemu inni ludzie mogą go zobaczyć. Github ułatwia również współpracę nad projektami.
Do istniejącego projektu dodamy teraz zdalne repozytorium.

Konfiguracja SSH na Debianie
W tym labie użyjemy SSH, czyli Secure Shell. Protokół zapewnia szyfrowane połączenie między Debianem a GitHubem. Za pomocą pary kluczy potwierdzisz dostęp do swojego konta bez podawania przy każdej operacji danych logowania do GitHuba. SSH nie jest jedyną dostępną metodą: GitHub obsługuje również połączenia przez HTTPS.
Potrzebujesz konta na GitHubie oraz pary kluczy na swojej maszynie wirtualnej.
Sprawdź zawartość katalogu:
ls -la ~/.ssh
Jeżeli katalog nie istnieje, przejdź do generowania klucza. Jeśli masz już klucz, którego używasz do GitHuba, możesz go wykorzystać. Nie nadpisuj istniejących plików.
Utworzenie klucza
Wygeneruj klucz Ed25519. Ed25519 jest obsługiwany przez GitHuba i pojawia się w jego instrukcji generowania nowych kluczy.
ssh-keygen -t ed25519
Przy pytaniu o lokalizację zaakceptuj domyślną ścieżkę, o ile nie znajduje się tam inny klucz. Ustaw też hasło chroniące klucz prywatny.
Po zakończeniu otrzymasz dwa pliki, które razem tworzą parę:
| Plik | Przeznaczenie |
|---|---|
~/.ssh/id_ed25519 | Klucz prywatny, używany lokalnie do potwierdzania dostępu |
~/.ssh/id_ed25519.pub | Klucz publiczny, który dodasz do konta na GitHubie |
Klucz chroniony hasłem trzeba odblokować przed użyciem. Bez dodatkowej konfiguracji możesz więc zobaczyć pytanie o passphrase podczas kolejnych operacji wymagających połączenia. Aby tego uniknąć skorzystamy z ssh-agent, który pozwala korzystać z odblokowanego klucza bez wpisywania hasła przy każdym wywołaniu. Działa lokalnie i przechowuje klucze w pamięci. Nie przesyła ich na GitHuba.
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
Przy dodawaniu klucza agent poprosi o wpisanie ustawionego wcześniej hasła. Robimy to tylko raz i później podczas każdorazowego połączenia nie będziemy pytani.
Dodanie klucza do GitHuba
Na Debianie masz już parę kluczy. Teraz dodasz klucz publiczny na GitHubie.
cat ~/.ssh/id_ed25519.pub
Skopiuj całą linię, zaczynającą się od ssh-ed25519. Na GitHubie otwórz ustawienia swojego konta:
Settings → SSH and GPG keys → New SSH key
Nadaj mu nazwę, na przykład debian-devops-lab, wybierz typ Authentication Key i wklej klucz publiczny w pole Key. Zapisz go przyciskiem Add SSH key.
Sprawdzenie połączenia
Wróć do terminala Debiana i uruchom:
ssh -T git@github.com
W adresie pozostaw użytkownika git. Nie zastępuj go nazwą konta labadmin ani swoim loginem z GitHuba. GitHub rozpozna Twoje konto na podstawie klucza użytego do uwierzytelnienia.
Przy pierwszym połączeniu SSH może zapytać, czy ufasz serwerowi. Po poprawnym uwierzytelnieniu zobaczysz komunikat podobny do tego:
Hi TWOJ_LOGIN! You've successfully authenticated, but GitHub does not provide shell access.
Informacja o braku dostępu do powłoki jest oczekiwana. Sprawdzamy dostęp do operacji Git, a nie logowanie do terminala na serwerze GitHuba.
Utworzenie repozytorium na GitHubie
Utwórz na swoim koncie repozytorium o nazwie devops-lab.
Pozostaw je puste. Nie dodawaj podczas tworzenia pliku README, .gitignore ani licencji. Masz już lokalną historię i za chwilę ją wyślesz.
Wróć do terminala i dodaj adres GitHuba, zastępując TWOJ_LOGIN nazwą swojego konta:
git remote add origin git@github.com:TWOJ_LOGIN/devops-lab.git
git remote -v
Wynik powinien zawierać dwa wpisy:
origin git@github.com:TWOJ_LOGIN/devops-lab.git (fetch)
origin git@github.com:TWOJ_LOGIN/devops-lab.git (push)
To adres do pobierania i wysyłania zmian. W naszym labie obie operacje korzystają z tego samego repozytorium.

Pierwszy git push
Wyślij lokalną gałąź master:
git push -u origin master
Polecenie publikuje historię gałęzi na GitHubie. Opcja -u ustawia również jej upstream, czyli powiązanie używane przy dalszej synchronizacji. Dzięki temu git dodatkowo zapamiętał powiązanie między lokalnym master a origin/master. Przy standardowej konfiguracji kolejne wysłanie zmian z tej gałęzi będzie wymagało już tylko git push.

Odśwież stronę repozytorium. Powinieneś zobaczyć README oraz commity z poprzedniej lekcji. Pamiętaj, że push dotyczy zapisanej historii. Zmodyfikowane pliki, których nie uwzględniłeś w commitach, nie trafią na GitHuba razem z gałęzią.

master i origin/master: dwie różne referencje
Po pierwszym push sprawdź historię:
git log --oneline --decorate --graph --all
Przy najnowszym commicie zobaczysz oznaczenia podobne do:
(HEAD -> master, origin/master)
master to lokalna gałąź, na której pracujesz.
origin/master jest lokalną informacją o stanie gałęzi master na zdalnym repozytorium origin. Git aktualizuje ją podczas komunikacji z remote. Nie jest to podgląd GitHuba działający w czasie rzeczywistym.

Przećwiczmy różnicę. Dopisz informację do README i zapisz ją w nowym commicie:
echo 'Repository: GitHub connected' >> README.md
git diff
git add README.md
git diff --staged
git commit -m "Document remote repository"
Sprawdź:
git log --oneline --decorate -2
Lokalna gałąź wskazuje już nowy commit. origin/master pozostaje przy poprzednim, bo zmiana nie została jeszcze wysłana.

Po wykonaniu kolejnego git pusha obie referencje powinny ponownie wskazywać ten sam commit.
git push
git log --oneline --decorate -2

Druga kopia projektu: git clone
Do sprawdzenia pobierania zmian potrzebujemy drugiej kopii repozytorium. Utworzymy ją na tej samej maszynie, w osobnym katalogu:
cd ~
git clone --branch master git@github.com:TWOJ_LOGIN/devops-lab.git devops-lab-clone
cd ~/devops-lab-clone
Opcja --branch master wskazuje gałąź, od której zaczniemy pracę w nowej kopii. Klonowanie tworzy repozytorium, pobiera historię i konfiguruje połączenie origin ze źródłem. Układ naszego laba wygląda teraz tak:

Dane autora w nowej kopii
Jeżeli w pierwszym repozytorium ustawiłeś dane autora lokalnie, nie zakładaj, że nowa kopia odziedziczy te ustawienia. Konfiguracja lokalna dotyczy konkretnego repozytorium.
Sprawdź wartości:
git config user.name
git config user.email
Gdy ich brakuje lub są nieprawidłowe, ustaw własne dane:
git config user.name "Jan Kowalski"
git config user.email "jan@example.com"
Obie kopie mogą korzystać z tego samego autora. Symulujemy osobne miejsca pracy, nie potrzebujemy drugiego konta na GitHubie.
Zmiana wykonana z drugiej kopii
Będąc w ~/devops-lab-clone, dopisz linię do README:
echo 'Modified from second clone' >> README.md
git add README.md
git diff --staged
git commit -m "Update README from second clone"
git push
Nowy commit znajduje się na GitHubie. Pierwsze repozytorium jeszcze go nie pobrało.
git fetch i git pull: pobranie zmian a aktualizacja gałęzi
Wróć do pierwszej kopii:
cd ~/devops-lab
cat README.md
W pierwszej kopii projektu nadal nie ma linii Modified from second clone. Dodaliśmy ją w drugiej kopii i wysłaliśmy na GitHuba, ale pierwsze repozytorium jeszcze nie pobrało tego commita.

Aby pobrać zmiany, przejdź do pierwszej kopii projektu i skorzystaj z polecenia git fetch:
git fetch origin
Git pobierze nowe commity z repozytorium origin. Nie zmieni jednak Twoich plików ani nie przesunie lokalnej gałęzi master. Dlatego po wykonaniu tego polecenia w README.md nadal nie zobaczysz dopisanej linii.
Pobrany commit możesz już obejrzeć w historii:
git log --oneline --decorate --graph --all
Sytuacja wygląda teraz tak:
...---D---E
| |
| origin/master
master (HEAD)
master nadal wskazuje commit D, czyli wersję, na której pracujesz. Natomiast origin/master wskazuje już commit E, pobrany z GitHuba. To ten, w którym dodaliśmy linię w drugiej kopii projektu.
Commit E jest już dostępny w Twoim lokalnym repozytorium, ale nie jest jeszcze częścią gałęzi master. Możesz więc sprawdzić, co zawiera, zanim zaktualizujesz swoją gałąź. Właśnie do tego przydaje się git fetch: najpierw pobierasz zmiany i je przeglądasz, a dopiero później włączasz je do swojej pracy.
Sprawdzenie tego, co przyszło z GitHuba
Zmiany z drugiej kopii repozytorium zostały już pobrane za pomocą git fetch, ale nie trafiły jeszcze do naszej lokalnej gałęzi master. Zanim je połączymy, sprawdźmy, co dokładnie pobraliśmy z GitHuba.
Aby wyświetlić nowe commity, skorzystaj z polecenia:
git log --oneline master..origin/master
Powinieneś zobaczyć commit, który utworzyliśmy wcześniej w drugiej kopii projektu.
Wiemy już, że pojawił się nowy commit. Sprawdźmy teraz, jakie zmiany wprowadza.
git diff master origin/master
Git porówna zawartość plików z naszej lokalnej gałęzi master ze stanem pobranym z GitHuba. W wyniku zobaczymy między innymi:
+Modified from second clone
To linia, którą dopisaliśmy wcześniej do README.md w drugiej kopii repozytorium. Dzięki temu możemy sprawdzić zawartość pobranych zmian, zanim włączymy je do lokalnej gałęzi. Na razie nasz README.md pozostaje bez zmian. W kolejnym kroku skorzystamy z git pull, żeby zaktualizować lokalną gałąź master.

Aktualizacja lokalnej gałęzi
Sprawdziliśmy już, jakie zmiany zostały pobrane z GitHuba. Nowy commit jest dostępny w naszym repozytorium, ale lokalna gałąź master nadal wskazuje starszą wersję projektu.
Aby zaktualizować lokalną gałąź i włączyć do niej pobrane zmiany, skorzystaj z polecenia:
git pull
Git ponownie sprawdzi zdalne repozytorium, pobierze ewentualne nowe zmiany i spróbuje połączyć je z aktualną gałęzią.
W naszym przypadku wykona operację fast-forward, czyli po prostu przesunie wskaźnik lokalnej gałęzi master na nowszy commit. Nie musi tworzyć dodatkowego commita, ponieważ od ostatniej synchronizacji nie wprowadzaliśmy żadnych nowych commitów w pierwszej kopii projektu.
Możemy to przedstawić tak:
Przed git pull:
A---B---C---D---E
↑ ↑
master origin/master
Po git pull:
A---B---C---D---E
↑
master, origin/master
Sprawdźmy, czy zmiana pojawiła się w naszym pliku:
cat README.md
git status
W README.md powinna znajdować się teraz linia:
Modified from second clone
Oznacza to, że zmiana wprowadzona wcześniej w drugiej kopii repozytorium została pobrana z GitHuba i uwzględniona w naszej lokalnej gałęzi.
Poznaliśmy w ten sposób dwa polecenia, które służą do pracy ze zdalnym repozytorium, ale zachowują się nieco inaczej.
git fetchpobiera nowe commity i aktualizuje informacje o zdalnych gałęziach. Nie zmienia przy tym aktualnej gałęzi ani plików w katalogu roboczym.git pullrównież pobiera zmiany, ale dodatkowo próbuje włączyć je do gałęzi, na której pracujemy.
W naszym ćwiczeniu wystarczyło przesunąć master do nowszego commita. Jeśli jednak lokalna i zdalna historia rozwiną się niezależnie, połączenie zmian będzie wymagało innej operacji. Taką sytuację przećwiczymy podczas pracy z branchami i poleceniem git merge.

Pierwsza gałąź: rozbudowa skryptu
Przejdźmy od README do skryptów. Wszystkie następne operacje wykonuj w pierwszym repozytorium:
cd ~/devops-lab
git switch master
W poprzedniej części nie tworzyliśmy scripts/system-info.sh, więc najpierw dodamy jego podstawową wersję na master.
mkdir -p scripts
cat > scripts/system-info.sh <<'EOF'
#!/usr/bin/env bash
printf 'Host: '
hostname
EOF
Blok od cat do końcowego EOF wklej w całości. Tworzy on plik o pokazanej zawartości. Zapiszmy skrypt w historii commitów.
bash scripts/system-info.sh
git add scripts/system-info.sh
git diff --staged
git commit -m "Add system information script"
git push origin master
Mamy punkt wyjścia do dalszej pracy: skrypt wyświetlający nazwę maszyny.
Utworzenie feature/system-info
Gałąź w Git jest referencją wskazującą commit. Utworzenie nowej gałęzi nie wymaga kopiowania projektu do osobnego katalogu. Na początku obie gałęzie mogą wskazywać dokładnie ten sam commit.
Utwórz gałąź i przełącz się na nią:
git switch -c feature/system-info
git branch
switch -c łączy utworzenie gałęzi z przełączeniem na nią. Gwiazdka na liście oznacza bieżącą gałąź.
* feature/system-info
master
W nazwach naszego laba prefiks feature/ oznacza pracę nad nową funkcjonalnością. Dla poprawek możemy używać bugfix/, a dla dokumentacji docs/. To przyjęta tutaj konwencja.

Commit na osobnej gałęzi
Na osobnej gałęzi rozbuduj skrypt o raportowanie pamięci:
cat >> scripts/system-info.sh <<'EOF'
printf '\nMemory:\n'
free -h
EOF
Sprawdź działanie oraz dodajmy commit. Nowy commit należy do gałęzi feature/system-info. master pozostał przy wcześniejszej wersji.
bash scripts/system-info.sh
git diff
git add scripts/system-info.sh
git diff --staged
git commit -m "Add memory information to system report"
Gałąź master pozostała przy wcześniejszej wersji. Przełącz się na nią z powrotem:
git switch master
cat scripts/system-info.sh
Zobaczysz skrypt bez części dotyczącej pamięci. Przy przełączaniu Git aktualizuje katalog roboczy do stanu danej gałęzi. W tym ćwiczeniu robimy to po zapisaniu zmian w commicie, więc nie przenosimy między gałęziami niezapisanej pracy.

Łączenie zmian z master: Merge fast-forward
Zmiany w skrypcie system-info.sh są już gotowe i zapisane na gałęzi feature/system-info. Teraz chcemy przenieść je do głównej gałęzi master.
Aby to zrobić, najpierw przełącz się na master:
git switch master
Następnie połącz ją z gałęzią, na której pracowaliśmy:
git merge --ff-only feature/system-info
Git wykona teraz operację nazywaną fast-forward. O co w niej chodzi? Kiedy tworzyliśmy feature/system-info, obie gałęzie wskazywały ten sam commit. Następnie dodaliśmy informację o pamięci RAM i utworzyliśmy nowy commit na gałęzi feature/system-info. W tym czasie na master nie zapisywaliśmy żadnych nowych commitów.
Przed połączeniem historia wygląda więc tak:
A---B---C---D
↑ ↑
master feature/system-info
master wskazuje commit C, a feature/system-info wskazuje nowszy commit D. Ponieważ commit D jest bezpośrednim następcą C, Git może po prostu przesunąć wskaźnik master do przodu.
Po wykonaniu merge otrzymamy:
A---B---C---D
↑
master
feature/system-info
Obie gałęzie wskazują teraz ten sam commit. Git nie musiał tworzyć dodatkowego commita łączącego historie, ponieważ master nie miał własnych nowych zmian.

Usunięcie zakończonej gałęzi
Zmiana jest już na master, więc lokalna gałąź zadania nie jest potrzebna. Dodatkowo wypychamy zmiany na github:
git branch -d feature/system-info
git push origin master
Usunięcie tej nazwy nie usuwa commitów, które należą już do historii master. Gdy -d odmawia usunięcia gałęzi, sprawdź jej historię zamiast od razu sięgać po wymuszenie przez -D

Co zrobić, gdy podczas merge pojawi się konflikt?
Opisane ćwiczenie nie wymaga ręcznego rozwiązywania konfliktów. Jeżeli jednak wykonałeś inne zmiany i podczas git merge zobaczysz CONFLICT, zatrzymaj się i sprawdź:
git status
Niedokończone scalanie możesz przerwać przez:
git merge --abort
Git spróbuje odtworzyć stan sprzed merge. Dlatego zaczynaliśmy scalanie z czystym drzewem roboczym: niezapisane wcześniej modyfikacje mogą utrudnić przywrócenie tego stanu.
Dwie gałęzie rozwijane równolegle
Teraz przygotujemy sytuację, w której obie gałęzie otrzymają własne commity. Na feature/monitoring powstanie nowy skrypt, a na main zmienimy README.
Skrypt na feature/monitoring
Będąc na master, utwórz gałąź:
git switch -c feature/monitoring
Dodaj podstawowy raport:
cat > scripts/monitoring.sh <<'EOF'
#!/usr/bin/env bash
printf 'Host: '
hostname
printf '\nUptime:\n'
uptime
printf '\nDisk usage:\n'
df -h /
EOF
Uruchom go i utwórz pierwszy commit:
bash scripts/monitoring.sh
git add scripts/monitoring.sh
git diff --staged
git commit -m "Add basic monitoring script"
Jako drugą zmianę dodaj raportowanie pamięci:
cat >> scripts/monitoring.sh <<'EOF'
printf '\nMemory:\n'
free -h
EOF
Zapisz ją osobno:
git diff
git add scripts/monitoring.sh
git diff --staged
git commit -m "Add memory usage to monitoring script"
Mamy dwa commity: utworzenie skryptu oraz jego rozbudowę.
Publikowanie gałęzi na GitHubie
Gałąź zadania możesz wysłać przed połączeniem jej z master:
git push -u origin feature/monitoring
git branch -a
Na liście zobaczysz między innymi:
* feature/monitoring
master
remotes/origin/feature/monitoring
remotes/origin/master

feature/monitoring jest lokalną gałęzią. origin/feature/monitoring przechowuje lokalną informację o jej odpowiedniku na GitHubie.
Ten push publikuje wskazaną gałąź, nie włącza jej zmian do master. Do połączenia przejdziemy osobno.
Niezależna zmiana na master
Przełącz się na główną gałąź:
git switch master
Nie edytuj tutaj monitoring.sh. Zamiast tego dopisz opis projektu do README:
cat >> README.md <<'EOF'
## Project purpose
Scripts and Git exercises for the DevOps course.
EOF
Zapisz zmianę:
git diff
git add README.md
git diff --staged
git commit -m "Document project purpose"
Obejrzyj historię:
git log --oneline --decorate --graph --all
Ma teraz dwie linie rozwoju. W poniższym schemacie C oznacza stan sprzed utworzenia gałęzi monitoringu:
D---E feature/monitoring
/
...---C
\
F master (HEAD)
D i E dotyczą skryptu. F zawiera zmianę README.

Merge commit: połączenie rozgałęzionej historii
Na początku upewnij się czy na pewno dalej jesteś na gałęzi master.
git branch --show-current
git status
Połącz gałąź monitoringu:
git merge --ff feature/monitoring -m "Merge monitoring feature"
Opcja --ff pozwala wykonać fast-forward, gdy historia na to pozwala, albo utworzyć merge commit, gdy potrzebne jest połączenie rozgałęzień. Tutaj master ma własny commit F, więc proste przesunięcie wskaźnika do E nie wystarczy. W tym scenariuszu gałęzie zmieniały różne pliki. Po połączeniu powinieneś otrzymać zarówno opis projektu w README, jak i nowy skrypt.
Historia będzie wyglądać tak:
D---E
/ \
...---C---F---M master (HEAD)
M to merge commit. Ma dwóch rodziców: F, czyli wcześniejszy koniec master, oraz E, czyli koniec gałęzi monitoringu. W ten sposób łączy obie linie historii.
Sprawdź wynik:
git log --oneline --decorate --graph --all -8
git status
cat README.md
bash scripts/monitoring.sh
Fast-forward z poprzedniego ćwiczenia nie wymagał nowego commita. Tutaj w historii pojawił się osobny commit łączący.

Wysłanie połączonej historii
Merge wykonaliśmy lokalnie. Wyślij zaktualizowane master na GitHuba:
git push origin master
Możesz teraz usunąć lokalną gałąź zakończonego zadania. Jej odpowiednik na GitHubie pozostanie. Usunięcie lokalnej gałęzi nie jest poleceniem usunięcia gałęzi z serwera.
git branch -d feature/monitoring
Na koniec zaktualizuj drugą kopię:
cd ~/devops-lab-clone
git switch master
git pull --ff-only
git status
cd ~/devops-lab
Obie kopie main powinny teraz zawierać tę samą historię. W projekcie masz:
devops-lab/
├── README.md
└── scripts/
├── system-info.sh
└── monitoring.sh
Ściągawka z poleceń
Poniższe komendy odpowiadają operacjom wykonanym w labie.
| Polecenie | Zastosowanie |
|---|---|
git remote -v | Sprawdzenie adresów zdalnych repozytoriów |
git remote add origin ADRES | Dodanie zdalnego repozytorium |
git clone ADRES KATALOG | Utworzenie lokalnej kopii repozytorium |
git push -u origin main | Publikacja main i ustawienie upstream |
git fetch origin | Pobranie danych bez integracji z bieżącą gałęzią |
git pull --ff-only | Pobranie i aktualizacja wyłącznie przez fast-forward |
git switch -c NAZWA | Utworzenie gałęzi i przełączenie się na nią |
git switch main | Przełączenie na main |
git merge --ff-only GAŁĄŹ | Połączenie wyłącznie przez fast-forward |
git merge --ff GAŁĄŹ | Fast-forward albo merge commit, zależnie od historii |
git branch -a | Lista gałęzi lokalnych i referencji remote-tracking |
git branch -d NAZWA | Usunięcie lokalnej gałęzi z kontrolą włączenia jej historii |
Następny krok: Masz dwie kopie repozytorium i przećwiczone łączenie gałęzi bez konfliktów. W kolejnej lekcji celowo doprowadzimy do konfliktu, odczytamy oznaczenia w pliku i zapiszemy poprawne rozwiązanie.
Kolejna lekcja: Konflikty w Git — jak je rozwiązywać krok po kroku.
← Poprzednia lekcja: Git od podstaw – git add, git commit i historia zmian · Wróć do spisu kursu