Przejdź do treści
Automatyzacja i DevOps · ~20 min czytania

Git od podstaw: commity, gałęzie i przenoszenie zmian do nowej gałęzi

Praktyczny przewodnik po codziennej pracy z Git — od pierwszego commita i gałęzi po bezpieczne cofanie oraz konserwację repozytorium. Każde polecenie opisujemy przez trzy pytania: co robi, po co go użyć i na co uważać.

Autor: Zespół LinuxLab

W LinuxLab traktujemy Git jak siatkę bezpieczeństwa dla każdej zmiany — w konfiguracji serwerów, w kodzie infrastruktury (Ansible, Terraform) i we wdrożeniach. Zanim cokolwiek trafi na produkcję, jest zacommitowane, opisane i możliwe do cofnięcia. Ten artykuł to praktyczne minimum, dzięki któremu Git przestaje być zbiorem magicznych zaklęć, a staje się przewidywalnym narzędziem.

W tym artykule przechodzimy przez podstawowy cykl pracy z Git (statusaddcommit), wyjaśniamy gałęzie i pokazujemy, jak przenieść na nową gałąź zarówno niezacommitowane, jak i już zapisane zmiany. Następnie omawiamy łączenie pracy, wymianę danych ze zdalnym repozytorium, bezpieczne cofanie oraz konserwację lokalnej bazy Git za pomocą git gc, git maintenance i git fsck.

Spis treści

  1. Czym jest Git?
  2. Podstawowy cykl pracy
  3. Gałęzie — po co i jak
  4. Z master do nowej gałęzi
  5. Gdy zmiany są już zacommitowane w master
  6. Przełączanie między gałęziami
  7. Łączenie zmian (merge)
  8. Zdalne repozytorium
  9. Cofanie zmian
  10. Konserwacja i diagnostyka
  11. Częste sytuacje — ściąga
  12. Jak wykorzystujemy to w LinuxLab
  13. Podsumowanie

Czym jest Git?

Git to rozproszony system kontroli wersji. „Rozproszony” oznacza, że zwykła kopia repozytorium zawiera nie tylko bieżące pliki, lecz także historię projektu. Możesz tworzyć commity, przeglądać wcześniejsze wersje i zakładać gałęzie bez połączenia z serwerem. Zdalne repozytorium jest potrzebne dopiero do wymiany pracy z innymi osobami lub urządzeniami.

Historię tworzą commity, czyli zapisane stany projektu. Każdy commit wskazuje zawartość plików w danym momencie, wcześniejszy commit lub commity oraz informacje takie jak autor, data i opis. Git nadaje mu identyfikator, nazywany potocznie hashem. Dzięki temu można dokładnie sprawdzić, co zmieniło się pomiędzy dwoma stanami, i wskazać wersję, do której chcemy wrócić.

Warto rozróżnić trzy „miejsca", w których żyją Twoje zmiany:

  • Katalog roboczy (working tree) — pliki, które właśnie edytujesz na dysku.
  • Poczekalnia (staging area / index) — zmiany przygotowane do zapisania, dodane przez git add.
  • Repozytorium — historia commitów i wewnętrzna baza obiektów przechowywana w ukrytym katalogu .git.

Na początku sprawdzamy wersję Git i ustawiamy dane autora. Opcja --global zapisuje je dla wszystkich repozytoriów bieżącego użytkownika. Bez tej opcji ustawienie dotyczy tylko repozytorium, w którym wykonano polecenie:

git --version
git config --global user.name "Twoje Imię"
git config --global user.email "adres@przyklad.pl"

Jeżeli projekt istnieje już na dysku, git init tworzy w nim puste repozytorium. Nie usuwa ani nie wysyła plików; dodaje katalog .git, w którym od tej chwili będzie przechowywana historia:

cd /sciezka/do/projektu
git init

Jeżeli repozytorium istnieje już na serwerze, używamy git clone. Polecenie tworzy nowy katalog, pobiera pliki i historię oraz zapisuje adres serwera pod nazwą origin:

git clone git@serwer:projekt.git
cd projekt

Podstawowy cykl pracy

Codzienna praca z Git sprowadza się do powtarzalnej pętli: sprawdź stan, obejrzyj różnice, wybierz zawartość następnego commita i dopiero wtedy go utwórz. git status jest punktem wyjścia — pokazuje bieżącą gałąź oraz pliki zmienione, przygotowane do commita i jeszcze nieśledzone.

# 1. Zobacz, co się zmieniło i na jakiej gałęzi jesteś
git status

# 2. Obejrzyj zmiany, które nie są jeszcze w poczekalni
git diff

# 3. Dodaj wybrane pliki do poczekalni (staging)
git add plik.txt              # konkretny plik
git add .                     # zmiany w bieżącym katalogu i podkatalogach
git add -A                    # wszystkie zmiany w całym katalogu roboczym

# 4. Sprawdź dokładnie, co znajdzie się w commicie
git diff --staged

# 5. Zapisz przygotowany stan jako commit z czytelnym opisem
git commit -m "Dodaj konfigurację rate limitingu w nginx"

# 6. Zobacz historię
git log --oneline --graph --decorate

git add nie wysyła plików na serwer. Kopiuje ich bieżącą wersję do poczekalni, z której powstanie następny commit. Dzięki temu można mieć w katalogu roboczym kilka zmian, ale do danego commita wybrać tylko te, które tworzą jedną logiczną całość.

Dobry opis commita ma znaczenie. Za pół roku (albo dla kolegi z zespołu) to jedyna wskazówka, dlaczego zmiana powstała. Piszemy krótko, w trybie rozkazującym i konkretnie: „Napraw wyciek pamięci w workerze", a nie „poprawki".

Gałęzie — po co i jak

Gałąź (branch) jest nazwą wskazującą jeden commit. Gdy tworzysz kolejny commit na tej gałęzi, jej wskaźnik przesuwa się do nowego stanu. Utworzenie gałęzi nie kopiuje całego projektu — powstaje tylko nowy, lekki wskaźnik. Dzięki temu można rozwijać funkcję poza stabilną wersją i połączyć ją dopiero po sprawdzeniu.

Główna gałąź historycznie często nazywa się master, a w nowszych projektach zwykle main. W przykładach używamy master, ponieważ tak nazywa się gałąź w opisywanym scenariuszu. Przed wykonaniem polecenia zawsze warto potwierdzić własną nazwę przez git branch --show-current.

Kilka poleceń, które warto znać:

git branch                    # lista gałęzi, gwiazdką oznaczona bieżąca
git branch nowa-galaz         # utwórz gałąź (bez przełączania się na nią)
git switch nazwa-galezi       # przełącz się na istniejącą gałąź
git switch -c nowa-galaz      # utwórz gałąź I od razu się na nią przełącz

Starsze poradniki używają do przełączania polecenia git checkout. Nowszy podział jest czytelniejszy: git switch służy do pracy z gałęziami, a git restore do przywracania zawartości plików. checkout nadal działa, ale początkującemu łatwiej pomylić jego kilka różnych zastosowań.

Z master do nowej gałęzi — najczęstszy scenariusz

Oto sytuacja, w którą wpada niemal każdy: jesteś na gałęzi master, zacząłeś coś zmieniać i dopiero teraz orientujesz się, że te zmiany powinny żyć w osobnej, nowej gałęzi — której jeszcze nie ma. Zależnie od tego, czy zdążyłeś już zrobić commit, obsługa różni się nieznacznie.

Wariant A — zmiany nie są jeszcze zacommitowane

To najprostszy przypadek. Zmiany są jeszcze w katalogu roboczym lub poczekalni, a więc nie należą do żadnego commita. Utworzenie nowej gałęzi w bieżącym punkcie nie usuwa ich: pozostają w katalogu roboczym i można zapisać je już na nowej gałęzi. Ta metoda działa niezależnie od tego, czy zaczynasz z master, main, czy innej gałęzi:

# Ogólna postać — zadziała z master LUB dowolnej innej gałęzi,
# o ile zmiany nie zostały jeszcze zacommitowane:
git switch -c "nazwa_brancha"

A oto ten sam schemat na konkretnym przykładzie, krok po kroku:

# Jesteś na master (lub innej gałęzi) z niezapisanymi zmianami.
# Sprawdź to:
git status

# Utwórz nową gałąź i od razu się na nią przełącz.
# Zmiany pozostają w katalogu roboczym:
git switch -c funkcja-logowanie

# (starszy, równoważny zapis:)
# git checkout -b funkcja-logowanie

# Teraz jesteś na 'funkcja-logowanie'. Zapisz zmiany tutaj:
git add .
git commit -m "Dodaj obsługę logowania użytkownika"

Efekt: commit powstaje na funkcja-logowanie, a wskaźnik gałęzi wyjściowej nie zostaje przesunięty. Po powrocie na master nie zobaczysz tego commita. Samo utworzenie gałęzi w bieżącym punkcie nie nadpisuje plików. Dopiero przy późniejszym przełączaniu pomiędzy różniącymi się gałęziami Git może zatrzymać operację, jeżeli niezapisane zmiany zostałyby utracone; wtedy trzeba je zacommitować albo odłożyć przez git stash.

Cudzysłowy nie pozwalają użyć spacji. git switch -c "nazwa_brancha" i git switch -c nazwa_brancha przekazują Gitowi tę samą nazwę, ponieważ powłoka usuwa cudzysłowy. Git nie dopuszcza spacji w nazwach gałęzi, więc zapis "moja gałąź" i tak zostanie odrzucony. Używaj prostych nazw, np. funkcja-logowanie albo fix/timeout-nginx.

Skąd biorą się nazwy gałęzi? Nazwa powinna mówić, czego dotyczy praca: funkcja-logowanie, fix/timeout-nginx, upgrade-php-8.4. Unikaj spacji i polskich znaków — myślnik lub ukośnik w zupełności wystarczą.

Wariant B — utworzenie i przełączenie gałęzi w dwóch krokach

git switch -c łączy dwie czynności: tworzy gałąź i od razu ją wybiera. Można wykonać je osobno, na przykład gdy chcesz tylko przygotować nazwę wskazującą bieżący commit, ale pozostać jeszcze na obecnej gałęzi. Rezultat po wykonaniu obu poleceń jest taki sam:

git branch funkcja-logowanie   # utwórz gałąź w bieżącym punkcie
git switch funkcja-logowanie   # przełącz się na nią
# ...zmiany z katalogu roboczego dalej są przy Tobie...
git add .
git commit -m "Dodaj obsługę logowania użytkownika"

Gdy zmiany są już zacommitowane w master

Trudniejszy wariant: jeden lub więcej commitów powstało bezpośrednio na master, ale powinny znajdować się na osobnej gałęzi. Najpierw trzeba zachować ich bieżący stan pod nową nazwą, a dopiero potem cofnąć master do wcześniejszego commita.

Kluczowa idea: utworzenie nowej gałęzi zabezpiecza bieżący commit pod dodatkową nazwą. Późniejsze cofnięcie wskaźnika master nie odbiera nowych commitów gałęzi funkcja-logowanie. Przed operacją sprawdzamy historię i liczbę commitów, aby nie cofnąć zbyt wielu:

# Punkt wyjścia: jesteś na master i masz np. 2 lokalne commity,
# które powinny być na osobnej gałęzi.

# 0. Sprawdź bieżącą gałąź, stan plików i historię.
git branch --show-current
git status
git log --oneline -5

# 1. Utwórz nową gałąź wskazującą na OBECNY stan (z commitami).
#    To NIE przełącza Cię — tylko zapisuje bieżący punkt:
git branch funkcja-logowanie

# 2. Cofnij WSKAŹNIK master o 2 commity wstecz.
#    --keep zachowuje niezapisane pliki, jeśli nie kolidują z celem;
#    w razie kolizji przerywa operację zamiast je nadpisać:
git reset --keep HEAD~2

# 3. Przełącz się na nową gałąź — tam Twoje commity nadal są:
git switch funkcja-logowanie

Po tych krokach oba commity nadal są osiągalne przez funkcja-logowanie, a master wskazuje na stan sprzed nich. reset niczego tutaj nie kopiuje: przesuwa tylko nazwę master. Sprawdź efekt, wyświetlając obie gałęzie w jednej historii:

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

Uwaga na reset i gałęzie współdzielone. git reset zmienia historię wskazywaną przez gałąź. Ten wariant jest przeznaczony dla commitów, które istnieją tylko lokalnie. Jeżeli master został już wysłany i korzystają z niego inni, cofnięcie wymagałoby nadpisania historii na serwerze. Bezpieczniej utworzyć nową gałąź, a skutki błędnych commitów odwrócić przez git revert, który dopisuje nowy commit i nie usuwa wspólnej historii.

Przełączanie między gałęziami

Przy przełączeniu Git aktualizuje śledzone pliki w katalogu roboczym tak, aby odpowiadały wybranej gałęzi. Nieśledzone pliki zwykle pozostają na miejscu, o ile nie kolidują z plikiem, który Git musi odtworzyć:

git switch master               # wróć na master
git switch funkcja-logowanie    # i z powrotem na gałąź roboczą
git switch -                    # przełącz na POPRZEDNIĄ gałąź (jak 'cd -')

Jeżeli niezapisane zmiany zostałyby nadpisane, Git zatrzyma przełączenie. Można je zapisać w commicie albo tymczasowo odłożyć przez git stash. Warto nadać schowkowi opis. Opcja -u dołącza także nowe, nieśledzone pliki; bez niej zwykły git stash ich nie chowa:

git stash push -u -m "Niedokończone logowanie"
git stash list       # sprawdź zapisane schowki
git switch master    # wykonaj pilną pracę na master
git switch funkcja-logowanie
git stash pop        # przywróć schowek; po sukcesie usuń go z listy

git stash pop może zgłosić konflikt, jeżeli pliki zmieniły się w międzyczasie. Gdy chcesz najpierw sprawdzić rezultat i zachować kopię w schowku, użyj git stash apply; wpis można później usunąć świadomie przez git stash drop.

Łączenie zmian (merge)

Gdy praca na gałęzi jest gotowa i sprawdzona, łączymy ją z master operacją merge. Znaczenie kierunku jest ważne: najpierw wybieramy gałąź docelową, a potem wskazujemy Gitowi nazwę gałęzi, której zmiany ma do niej dołączyć:

git switch master                 # gałąź, DO której wcielamy
git merge funkcja-logowanie       # wciągnij zmiany z gałęzi roboczej

Jeżeli gałęzie nie rozeszły się, Git może wykonać fast-forward: tylko przesunie master do nowszego commita. Gdy obie gałęzie mają własne commity, powstanie commit scalający. Jeżeli te same fragmenty plików zmieniły się w niezgodny sposób, Git zatrzyma operację i oznaczy konflikt. Po ręcznym poprawieniu plików dodajemy rozwiązanie do poczekalni i kontynuujemy scalenie:

# po ręcznym rozwiązaniu konfliktów w plikach:
git add .
git merge --continue              # tworzy commit scalający

# Jeśli chcesz zrezygnować przed zakończeniem:
# git merge --abort

Gałąź, która została już scalona i nie jest potrzebna, można bezpiecznie usunąć:

git branch -d funkcja-logowanie   # usuń scaloną gałąź

Zdalne repozytorium

Do tej pory wszystko działo się lokalnie. Zdalne repozytorium, na przykład na GitLabie, GitHubie lub własnym serwerze, służy do wymiany commitów i gałęzi. Pierwszy zdalny adres zwykle otrzymuje nazwę origin. Nie należy jednak traktować go jako pełnej kopii zapasowej: lokalne commity i nieśledzone pliki nie znajdą się na serwerze, dopóki ich świadomie nie zapiszesz i nie wyślesz.

# Podłącz zdalne repozytorium
git remote add origin git@serwer:projekt.git

# Wyślij lokalną gałąź na zdalne (-u zapamiętuje powiązanie)
git push -u origin master
git push -u origin funkcja-logowanie

# Pobierz informacje i commity, ale nie zmieniaj bieżącej gałęzi
git fetch origin

# Zobacz commity obecne na origin/master, których nie ma lokalnie
git log --oneline HEAD..origin/master

# Pobierz i dołącz zmiany tylko wtedy, gdy wystarczy fast-forward
git pull --ff-only

# Zobacz, jakie gałęzie istnieją zdalnie
git branch -r

git fetch aktualizuje lokalny obraz serwera, na przykład origin/master, ale nie modyfikuje plików ani bieżącej gałęzi. git pull najpierw wykonuje fetch, a następnie próbuje dołączyć pobraną historię. Jawne --ff-only pozwala na prostą aktualizację, lecz zatrzymuje polecenie, gdy lokalna i zdalna historia się rozeszły — zamiast samodzielnie wybierać sposób ich połączenia.

Opcja -u przy pierwszym push zapisuje powiązanie lokalnej gałęzi ze zdalną. Następne wysyłki mogą używać krótkiego git push, a git status pokaże, czy gałąź jest przed lub za swoim odpowiednikiem na serwerze.

Cofanie zmian — bezpieczna siatka

„Cofnij” może oznaczać kilka różnych rzeczy: porzucenie zmian w pliku, wyjęcie pliku z poczekalni, poprawienie lokalnego commita albo odwrócenie commita, który widzą już inni. Przed wykonaniem polecenia ustal, w którym z trzech miejsc znajduje się zmiana: w katalogu roboczym, poczekalni czy historii.

# Porzuć niezapisane zmiany w JEDNYM śledzonym pliku
# i przywróć jego wersję z poczekalni:
git restore plik.txt
# (starszy zapis: git checkout -- plik.txt)

# Wyjmij plik z poczekalni, ale zachowaj zmiany w katalogu roboczym
git restore --staged plik.txt

# Popraw OSTATNI lokalny commit lub dodaj do niego zapomniany plik
git commit --amend

# Odwróć efekt wskazanego commita przez utworzenie NOWEGO commita
git revert <hash-commita>

git restore plik.txt usuwa niezapisane zmiany w tym pliku i zwykle nie da się ich odzyskać przez Git, ponieważ nigdy nie trafiły do commita. git restore --staged jest łagodniejsze: zmienia tylko zawartość poczekalni, a edytowany plik pozostaje na dysku.

git commit --amend nie poprawia commita „w miejscu”, lecz tworzy jego nową wersję z innym identyfikatorem. Używamy go swobodnie przed wysłaniem commita. Po publikacji na współdzielonej gałęzi bezpieczniejszy jest zwykle git revert, ponieważ dopisuje czytelną korektę bez nadpisywania historii.

Zgubiłeś commit po resecie? Git często zachowuje niedawny commit, nawet jeśli żadna gałąź już na niego nie wskazuje. git reflog pokazuje lokalną historię ruchów HEAD — można znaleźć w niej identyfikator i zabezpieczyć commit przez git switch -c ratunek <hash>. Reflog nie jest jednak wieczystą kopią zapasową: stare wpisy wygasają, a późniejsza konserwacja może usunąć obiekty, do których nic już nie prowadzi. Gdy zauważysz pomyłkę, utwórz gałąź ratunkową od razu.

Konserwacja i diagnostyka repozytorium

Katalog .git zawiera commity, drzewa katalogów i zawartość plików w postaci wewnętrznych obiektów. Podczas codziennej pracy Git zapisuje część nowych obiektów osobno, ponieważ ma to być szybkie. Z czasem może połączyć je w skompresowane pliki paczek. Zwykle robi to automatycznie, ale po dużym imporcie, przepisaniu historii albo przy wyraźnym spowolnieniu warto umieć sprawdzić stan repozytorium i świadomie uruchomić konserwację.

Najpierw sprawdź, ile miejsca zajmuje baza obiektów

git count-objects -vH

Polecenie niczego nie zmienia. count i size opisują luźne, jeszcze niespakowane obiekty. in-pack, packs i size-pack pokazują obiekty już zapisane w paczkach oraz ich rozmiar. Duża liczba luźnych obiektów albo wielu małych paczek może uzasadniać porządkowanie, ale duży size-pack sam w sobie nie oznacza problemu — repozytorium może po prostu zawierać dużo historii lub duże pliki.

git gc — co robi i po co jest

# Standardowa, jednorazowa konserwacja bieżącego repozytorium
git gc

# Uruchom prace tylko wtedy, gdy Git uzna je za potrzebne
git gc --auto

# Porównaj stan bazy obiektów po zakończeniu
git count-objects -vH

Skrót gc pochodzi od garbage collection. Polecenie wykonuje kilka prac porządkowych: pakuje i kompresuje obiekty, porządkuje wewnętrzne odwołania oraz po okresie ochronnym usuwa stare obiekty, do których nie prowadzi już żadna gałąź, etykieta ani reflog. Celem jest mniejszy katalog .git i szybsze operacje na historii. git gc nie usuwa bieżących, śledzonych plików ani commitów nadal wskazywanych przez gałęzie.

Większość użytkowników nie musi uruchamiać go regularnie ręcznie. Polecenia tworzące dużo danych same wywołują lekką konserwację, gdy repozytorium przekroczy odpowiednie progi. Ręczne git gc ma sens jako jednorazowe porządkowanie po masowym imporcie, filtrowaniu historii albo wtedy, gdy pomiar pokazuje wiele luźnych obiektów i operacje Git wyraźnie zwolniły.

Nie zaczynaj od git gc --aggressive ani git gc --prune=now. Tryb --aggressive przelicza kompresję znacznie dłużej i dla większości repozytoriów nie daje korzyści wartej kosztu. --prune=now usuwa okres ochronny i może natychmiast skasować niedawne obiekty, których nie chroni już gałąź, etykieta ani wpis refloga. Zwiększa też ryzyko przy równoległym zapisie do repozytorium. Standardowe git gc jest właściwym wyborem do zwykłej konserwacji. Nie uruchamiaj go jednocześnie z importem, rebase'em ani innym procesem intensywnie zapisującym obiekty.

git maintenance — konserwacja rozłożona w czasie

# Uruchom domyślny zestaw prac konserwacyjnych teraz
git maintenance run

# Włącz okresową konserwację tego repozytorium w tle
git maintenance start

# Wyłącz utworzony harmonogram
git maintenance stop

git maintenance jest nowszym mechanizmem, szczególnie przydatnym w dużych repozytoriach. Może wykonywać mniejsze zadania okresowo, aby użytkownik nie czekał później na jedną dużą operację. start rejestruje bieżące repozytorium i konfiguruje harmonogram odpowiedni dla systemu, na przykład timer użytkownika systemd albo zadanie cron. Przed użyciem na serwerze sprawdź, pod jakim kontem i kiedy zadania będą uruchamiane. Nie planuj ręcznego git gc w tym samym czasie co git maintenance run.

git fsck — kontrola spójności, a nie automatyczna naprawa

git fsck --full

git fsck sprawdza, czy obiekty są poprawne i czy powiązania pomiędzy commitami, drzewami oraz zawartością plików są kompletne. Przydaje się po awarii dysku, przerwanym kopiowaniu katalogu .git albo gdy Git zgłasza brakujący lub uszkodzony obiekt. Komunikat o obiekcie dangling nie musi oznaczać awarii — może opisywać niedawny commit, na który nie wskazuje już żadna gałąź. fsck przede wszystkim diagnozuje; brakujące dane trzeba odzyskać z innej kopii repozytorium, refloga lub kopii zapasowej.

Usuń nieaktualne nazwy gałęzi zdalnych

# Najpierw pokaż, co zostałoby usunięte z lokalnej listy
git remote prune --dry-run origin

# Pobierz aktualny stan i usuń lokalne odwołania do gałęzi,
# których nie ma już na serwerze
git fetch --prune origin

Ta operacja nie usuwa gałęzi z serwera ani lokalnych gałęzi roboczych. Porządkuje tylko nazwy typu origin/stara-galaz, które pozostały po gałęziach usuniętych z repozytorium zdalnego.

Częste sytuacje — ściąga

Poniżej zebraliśmy problemy, które w codziennej pracy wracają najczęściej, wraz z krótkim „lekiem" na każdy z nich. Traktuj to jako ściągę do zerknięcia w potrzebie.

Zacommitowałem na złej gałęzi — chcę przenieść commit gdzie indziej

Gdy pojedynczy commit ma trafić na inną, już istniejącą gałąź, używamy cherry-pick. Git odtwarza zmiany wskazanego commita na bieżącej gałęzi i tworzy tam nowy commit. Treść będzie odpowiadała oryginałowi, ale identyfikator zwykle będzie inny, ponieważ nowy commit ma innego rodzica:

# Zapamiętaj hash commita (z 'git log --oneline'), potem:
git switch wlasciwa-galaz
git cherry-pick <hash-commita>

# Jeżeli błędny commit był OSTATNIM lokalnym, niewysłanym commitem,
# usuń go z gałęzi źródłowej przez cofnięcie jej wskaźnika:
git switch master
git reset --keep HEAD~1

Ostatni krok jest bezpieczny tylko dla lokalnego commita na końcu gałęzi. Jeśli commit został już wysłany albo po nim powstały kolejne zmiany, użyj git revert <hash-commita> lub uzgodnij sposób uporządkowania historii z zespołem.

Chcę zobaczyć, co dokładnie zmieniłem

git diff              # zmiany jeszcze niedodane do poczekalni
git diff --staged     # zmiany już dodane (git add), gotowe do commita
git diff master funkcja-logowanie   # różnica między dwiema gałęziami

git diff bez argumentów nie pokazuje zmian, które zostały już dodane przez git add. Dlatego przed commitem sprawdzamy osobno git diff --staged; to właśnie jego wynik odpowiada zawartości przygotowywanego commita.

Chcę zapisać tylko część zmian z pliku

Gdy w jednym pliku znajdują się zmiany dotyczące dwóch różnych zadań, tryb interaktywny pozwala dodać tylko wybrane fragmenty. Litera y akceptuje pokazany fragment, n go pomija, a s próbuje podzielić go na mniejsze części:

git add -p            # Git pyta o każdy fragment: dodać? (y/n/s...)

Przypadkiem dodałem plik do commita (albo do repo)

# Wyjmij plik z poczekalni PRZED commitem (zmiany w pliku zostają):
git restore --staged sekrety.env

# Przestań śledzić plik, który już wpadł do repo, ale zostaw go na dysku:
git rm --cached sekrety.env

# ...i dopisz go do .gitignore, żeby nie wracał:
echo "sekrety.env" >> .gitignore

Plik .gitignore to lista wzorców, których Git ma nie śledzić — pliki z hasłami, katalogi zależności (vendor/, node_modules/), logi czy pliki tymczasowe. Zakładamy go na początku projektu, żeby wrażliwe i nadmiarowe pliki nigdy nie trafiły do historii.

.gitignore nie usuwa sekretu z wcześniejszych commitów. git rm --cached przestaje śledzić plik od następnego commita, ale jego starsza zawartość nadal istnieje w historii. Jeżeli znalazły się w niej hasło, token albo klucz, najpierw unieważnij i zmień dane dostępowe. Ewentualne usuwanie pliku z całej historii jest osobną, uzgadnianą operacją, ponieważ zmienia identyfikatory wielu commitów.

Wpisałem literówkę w opisie ostatniego commita

git commit --amend -m "Poprawny, czytelny opis"

--amend tworzy nowy commit w miejsce poprzedniego, dlatego zmienia jego identyfikator. Jest wygodny dla ostatniego lokalnego commita. Po wysłaniu na współdzieloną gałąź wymagałby nadpisania historii zdalnej, więc zwykle lepiej dopisać kolejny commit korygujący.

Chcę zmienić nazwę gałęzi

git branch -m stara-nazwa nowa-nazwa   # dowolnej gałęzi
git branch -m nowa-nazwa               # bieżącej (będąc na niej)

Mój katalog to bałagan — chcę wrócić dokładnie do stanu ze zdalnego repo

Ten scenariusz celowo porzuca lokalną pracę. Najpierw sprawdź, co zostanie utracone. Jeśli masz choć cień wątpliwości, utwórz commit na gałęzi ratunkowej albo schowek przez git stash push -u. Następnie pobierz aktualny stan serwera i wykonaj podgląd nieśledzonych plików:

git status
git diff
git diff --staged

git fetch origin
git clean -nd                   # TYLKO pokaż nieśledzone pliki do usunięcia

git reset --hard origin/master   # UWAGA: kasuje lokalne, niezapisane zmiany
git clean -fd                    # usuń pokazane nieśledzone pliki i katalogi

To operacja nieodwracalna dla pracy, której Git nigdy nie zapisał. reset --hard przywraca śledzone pliki i poczekalnię do wskazanego commita. clean -fd usuwa nieśledzone pliki i katalogi. Reflog może pomóc odzyskać wcześniejszy commit, ale nie odtworzy treści, która istniała wyłącznie jako niezapisana zmiana lub nieśledzony plik. Nie pomijaj podglądu git clean -nd.

Kto i kiedy zmienił tę konkretną linię?

git blame plik.conf          # przy każdej linii: commit, autor, data
git log -p plik.conf         # pełna historia zmian danego pliku

git blame wskazuje ostatni commit, który zmienił obecną postać każdej linii. Nie zawsze wskazuje autora pierwotnego pomysłu, zwłaszcza po przenoszeniu lub formatowaniu kodu. Traktujemy go jako drogę do commita i jego uzasadnienia, a nie narzędzie do szukania winnego.

Chcę na chwilę zajrzeć do starej wersji pliku

# Podejrzyj plik z konkretnego commita, nic nie zmieniając:
git show <hash>:sciezka/do/pliku.conf

# Przywróć POJEDYNCZY plik do wersji z danego commita:
git restore --source=<hash> sciezka/do/pliku.conf

git show tylko wypisuje dawną zawartość i nie dotyka katalogu roboczego. git restore --source zmienia plik na dysku, ale nie tworzy commita. Po sprawdzeniu różnicy trzeba świadomie dodać i zacommitować tę zmianę albo ją porzucić.

Jak wykorzystujemy to w LinuxLab

Git jest u nas fundamentem powtarzalnej, bezpiecznej pracy z infrastrukturą. W praktyce:

  • Wersjonujemy konfigurację i kod infrastruktury — playbooki Ansible, pliki Terraform i konfiguracje usług trzymamy w repozytoriach, więc każda zmiana ma autora, datę i uzasadnienie.
  • Pracujemy na gałęziach — nowe zmiany powstają w izolacji od stabilnej wersji i trafiają do niej dopiero po przeglądzie, co ogranicza ryzyko awarii.
  • Commitujemy przed wdrożeniem — dzięki temu w razie problemu mamy do czego wrócić w minutę, a nie odtwarzamy stan z pamięci.
  • Traktujemy historię jak dokumentację — czytelne commity mówią, dlaczego serwer wygląda tak, jak wygląda.

Efekt jest ten sam, co przy całej naszej filozofii automatyzacji: mniej pomyłek, szybszy powrót do działającego stanu i przewidywalna infrastruktura.

Podsumowanie

Git opiera się na kilku prostych pojęciach: commit zapisuje stan projektu, gałąź jest nazwą wskazującą commit, a merge łączy dwie linie pracy. Przeniesienie niezapisanych zmian na nową gałąź zwykle wymaga git switch -c nazwa. Gdy commity powstały już lokalnie na złej gałęzi, najpierw zabezpieczamy je przez git branch, a dopiero potem świadomie przesuwamy wskaźnik przez git reset --keep. restore, revert i reflog rozwiązują różne rodzaje pomyłek, dlatego przed „cofaniem” zawsze sprawdzamy, gdzie znajduje się zmiana.

Repozytorium wymaga również wewnętrznego porządkowania. Git zwykle robi to automatycznie, ale git count-objects -vH pozwala zmierzyć stan bazy, git gc wykonuje jednorazową konserwację, git maintenance może rozłożyć ją w czasie, a git fsck sprawdza spójność obiektów. Znajomość celu tych poleceń jest ważniejsza niż zapamiętanie wszystkich opcji: najpierw diagnozujemy, potem wybieramy najmniej ryzykowną operację i sprawdzamy jej efekt.

Chcesz infrastrukturę wersjonowaną i odtwarzalną?

Wdrażamy Git, automatyzację i dobre praktyki, dzięki którym każda zmiana na serwerach jest bezpieczna i możliwa do cofnięcia. Porozmawiajmy o Twojej infrastrukturze — bezpłatnie i bez zobowiązań.