Git poza podstawami: Workflow'y, które oszczędzają godziny co tydzień
Interactive rebase, cherry-pick, bisect, worktree, reflog rescue i strategie branchowania, które naprawdę działają. Komendy Git, których używam każdego dnia, a o których większość deweloperów nie wie.
Większość deweloperów uczy się pięciu komend Git i na tym kończy. add, commit, push, pull, merge. Może checkout i branch, jeśli czują się odważni. To wystarczy na pierwszy rok. Potem twój branch ma 47 commitów z wiadomościami typu "fix" i "wip" i "proszę zadziałaj", przypadkowo resetujesz coś, czego nie powinieneś, i spędzasz 40 minut na Stack Overflow, próbując cofnąć nieudanego merge'a.
Używam Gita od lat. Nie okazjonalnie — intensywnie. Wiele branchy, wiele repozytoriów, wielu współpracowników, cały dzień, każdego dnia. Poniżej znajdziesz komendy i workflow'y, których naprawdę używam. Nie te, które dobrze wyglądają w tutorialu. Te, które oszczędzają mi realny czas, każdego tygodnia.
Interactive Rebase: Posprzątaj bałagan zanim ktokolwiek go zobaczy#
Twój branch ma dwanaście commitów. Połowa to "fix typo". Jeden mówi "undo previous commit". Kolejny mówi "actually fix it this time". Zaraz otworzysz PR. Nikt nie musi widzieć tej historii.
Interactive rebase pozwala przepisać historię na twoim branchu przed udostępnieniem go. Możesz squashować commity razem, zmienić wiadomości, zmienić kolejność albo je całkowicie usunąć.
Podstawowa komenda#
git rebase -i HEAD~5To otwiera edytor pokazujący twoje 5 ostatnich commitów, od najstarszego:
pick a1b2c3d Add user authentication endpoint
pick d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
pick l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth testsKażda linia zaczyna się od komendy. Zmień pick na jedno z tych:
squash(lubs) — Połącz ten commit z tym powyżej, połącz wiadomościfixup(lubf) — Tak samo jak squash, ale odrzuć wiadomość tego commitareword(lubr) — Zachowaj commit, ale zmień jego wiadomośćdrop(lubd) — Usuń ten commit całkowicieedit(lube) — Zatrzymaj rebase na tym commicie, żebyś mógł go zmodyfikować
Prawdziwa sesja porządkowania#
Oto co naprawdę robię. Ta bałaganiarska historia powyżej staje się:
pick a1b2c3d Add user authentication endpoint
fixup d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
fixup l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth testsZapisz i zamknij. Teraz masz trzy czyste commity zamiast pięciu. Fix literówki trafia do commita auth. Fix błędu rate limitu trafia do commita rate limitu. Twój recenzent PR widzi czystą, logiczną progresję.
Zmiana kolejności commitów#
Możesz dosłownie przestawić linie. Jeśli commit testów powinien być przed commitem rate limitingu, po prostu przesuń linię:
pick a1b2c3d Add user authentication endpoint
pick p3q4r5s Update auth tests
pick h7i8j9k Add rate limitingGit odtworzy twoje commity w nowej kolejności. Jeśli będą konflikty, zatrzyma się i pozwoli ci je rozwiązać.
Skrót Autosquash#
Jeśli wiesz, że commit jest fixem dla poprzedniego, zaznacz to w momencie commita:
git commit --fixup=a1b2c3dTo tworzy commit z wiadomością fixup! Add user authentication endpoint. Potem przy rebase:
git rebase -i --autosquash HEAD~5Git automatycznie przestawia commity fixup zaraz pod ich cele i oznacza je jako fixup. Po prostu zapisujesz i zamykasz. Żadnego ręcznego edytowania.
Używam tego non-stop. To najszybszy sposób na iterowanie na branchu przy zachowaniu czystej historii końcowej.
Złota zasada#
Nigdy nie robiąc rebase na commitach, które zostały wypchnięte na wspólny branch. Jeśli inni ludzie oparli pracę na tych commitach, przepisywanie historii spowoduje realne problemy. Robiąc rebase na swoich feature branchach przed mergem. Nigdy na main.
Jeśli już wypchnąłeś swój feature branch i musisz zrobić rebase:
git push --force-with-leaseFlaga --force-with-lease jest bezpieczniejsza niż --force. Odmawia wypchnięcia, jeśli ktoś inny wypchnął na ten sam branch od twojego ostatniego fetch. Nie zapobiegnie wszystkim problemom, ale łapie ten najczęstszy.
Cherry-Pick: Chirurgiczne przenoszenie commitów#
Cherry-pick bierze konkretny commit z jednego brancha i aplikuje go na inny. Nie merge. Nie rebase. Tylko jeden commit, czysto zaaplikowany.
Kiedy naprawdę tego używam#
Najczęstszy scenariusz: naprawiłem buga na moim feature branchu, ale fix musi też trafić na main albo branch releasu natychmiast. Nie chcę mergować całego feature brancha. Chcę tylko ten jeden fix.
# Find the commit hash of the fix
git log --oneline feature/user-auth
# a1b2c3d Fix null pointer in session validation
# Switch to main and cherry-pick it
git checkout main
git cherry-pick a1b2c3dGotowe. Fix jest na main jako nowy commit z tymi samymi zmianami.
Cherry-Pick bez commitowania#
Czasem chcesz zaaplikować zmiany, ale jeszcze ich nie commitować. Może chcesz połączyć kilka cherry-picków w jeden commit albo lekko zmodyfikować zmiany:
git cherry-pick --no-commit a1b2c3dZmiany są zastageowane, ale nie commitowane. Możesz je zmodyfikować, dodać więcej zmian, potem commitować gdy gotowe.
Cherry-Pick zakresu#
Potrzebujesz wielu kolejnych commitów? Użyj składni zakresu:
git cherry-pick a1b2c3d..f6g7h8iTo cherry-pickuje wszystko po a1b2c3d aż do i włącznie z f6g7h8i. Zauważ, że sam a1b2c3d jest wykluczony. Jeśli chcesz go włączyć:
git cherry-pick a1b2c3d^..f6g7h8iObsługa konfliktów#
Konflikty cherry-pick działają jak konflikty merge. Kiedy jeden wystąpi:
# Git will tell you there's a conflict
# Fix the conflicting files, then:
git add .
git cherry-pick --continueAlbo jeśli zmienisz zdanie:
git cherry-pick --abortJedna rzecz do obserwowania: cherry-pickowane commity tworzą nowe hashe commitów. Jeśli później zmergujesz oryginalny branch, Git jest zwykle na tyle sprytny, żeby obsłużyć duplikację. Ale jeśli agresywnie cherry-pickujesz między branchami, które w końcu się zmergują, możesz zobaczyć nieoczekiwane konflikty. Używaj chirurgicznie, nie jako strategii mergowania.
Git Bisect: Wyszukiwanie binarne dla bugów#
Coś się zepsuło. Wiesz, że dwa tygodnie temu działało. Od tego czasu było 200 commitów. Który to zepsuł?
Mógłbyś sprawdzić każdy commit ręcznie. Albo możesz użyć git bisect, które używa wyszukiwania binarnego, żeby znaleźć dokładny commit łamiący w log2(n) krokach. Dla 200 commitów to około 7-8 sprawdzeń zamiast 200.
Sposób ręczny#
# Start bisecting
git bisect start
# Mark the current commit as bad (the bug exists here)
git bisect bad
# Mark a known good commit (the bug didn't exist here)
git bisect good v2.1.0Git checkoutuje commit w połowie między dobrym a złym. Testuj go. Potem:
# If the bug exists at this commit:
git bisect bad
# If the bug doesn't exist at this commit:
git bisect goodGit zawęża zakres o połowę za każdym razem. Po 7-8 krokach mówi ci:
a1b2c3d4e5f6g7h is the first bad commit
commit a1b2c3d4e5f6g7h
Author: Some Developer <dev@example.com>
Date: Tue Feb 18 14:23:01 2026 +0300
Refactor session handling to use async middlewareTeraz wiesz dokładnie, który commit wprowadził buga. Kiedy skończysz:
git bisect resetTo zabiera cię z powrotem tam, gdzie zacząłeś.
Sposób automatyczny (tu jest prawdziwa siła)#
Jeśli masz test, który potrafi wykryć buga, możesz zautomatyzować cały proces:
git bisect start
git bisect bad HEAD
git bisect good v2.1.0
git bisect run npm test -- --grep "session validation"Git automatycznie checkoutuje commity, uruchamia test i oznacza je jako dobre lub złe na podstawie kodu wyjścia. Zero oznacza dobry, nie-zero oznacza zły. Odejdź, wróć, a powie ci dokładny commit.
Możesz użyć dowolnego skryptu:
git bisect run ./test-regression.shGdzie test-regression.sh to:
#!/bin/bash
npm run build 2>/dev/null || exit 125 # 125 means "skip this commit"
npm test -- --grep "session" || exit 1 # 1 means "bad"
exit 0 # 0 means "good"Kod wyjścia 125 jest specjalny — mówi bisectowi, żeby pominął ten commit (przydatne, jeśli commit się nie kompiluje). To jedna z tych funkcji, która wydaje się niszowa, aż jej potrzebujesz, a wtedy ratuje ci całe popołudnie.
Prawdziwa sesja Bisect#
Oto jak to wygląda w praktyce:
$ git bisect start
$ git bisect bad HEAD
$ git bisect good abc1234
Bisecting: 97 revisions left to test after this (roughly 7 steps)
[def5678...] Commit message here
$ npm test -- --grep "login flow"
# Tests fail
$ git bisect bad
Bisecting: 48 revisions left to test after this (roughly 6 steps)
[ghi9012...] Another commit message
$ npm test -- --grep "login flow"
# Tests pass
$ git bisect good
Bisecting: 24 revisions left to test after this (roughly 5 steps)
...
# After ~7 iterations:
abc1234def5678ghi is the first bad commitSiedem kroków, żeby znaleźć igłę w stogu siana z 200 commitów.
Git Worktrees: Wiele branchy, zero stashowania#
To jest najbardziej niedoceniana funkcja Gita. Jestem przekonany, że większość deweloperów nie wie o jej istnieniu.
Problem: jesteś głęboko w feature branchu. Pliki są pozmienianie wszędzie. Wtedy ktoś mówi "możesz zerknąć na tego buga produkcyjnego?" Masz trzy opcje:
- Stashujesz wszystko, przełączasz branch, naprawiasz, przełączasz z powrotem, popujesz stash. Mając nadzieję, że nic nie pójdzie źle.
- Commitujesz swoją niedokończoną pracę z wiadomością "wip". Brzydkie, ale funkcjonalne.
- Klonujesz repo ponownie do innego katalogu.
Albo opcja 4: worktrees.
Czym jest worktree#
Worktree to drugi (albo trzeci, albo czwarty) katalog roboczy podłączony do tego samego repozytorium. Każdy worktree ma swój własny checkout brancha, swoje pliki robocze, swój własny indeks. Ale dzielą te same dane .git, więc nie duplikujesz całego repo.
Dodawanie worktree#
# You're on feature/user-auth, deep in work
# Need to fix a bug on main:
git worktree add ../hotfix-session mainTo tworzy nowy katalog ../hotfix-session z checkoutowanym main. Twój obecny katalog pozostaje dokładnie taki, jaki był. Nic nie stashowane, nic nie commitowane, nic nie zakłócone.
cd ../hotfix-session
# Fix the bug
git add .
git commit -m "Fix null pointer in session validation"
git push origin main
cd ../my-project
# Continue working on your feature as if nothing happenedTworzenie nowego brancha w worktree#
git worktree add ../hotfix-nav -b hotfix/nav-crash mainTo tworzy worktree ORAZ tworzy nowy branch hotfix/nav-crash oparty na main.
Zarządzanie worktree#
# List all worktrees
git worktree list
# /home/dev/my-project abc1234 [feature/user-auth]
# /home/dev/hotfix-session def5678 [main]
# Remove a worktree when done
git worktree remove ../hotfix-session
# If the directory was already deleted:
git worktree pruneDlaczego to lepsze niż stashowanie#
Stashowanie jest w porządku na szybkie przełączenia kontekstu. Ale worktrees są lepsze do czegokolwiek, co trwa dłużej niż pięć minut:
- Twoje IDE zostaje otwarte na feature branchu. Bez reindeksowania, bez utraty pozycji scrolla.
- Możesz uruchamiać testy w obu katalogach jednocześnie.
- Żadnego ryzyka konfliktów stasha ani zapomnienia, co zastashowałeś.
- Możesz mieć długo działający dev server w jednym worktree i czysty build w innym.
Zwykle trzymam dwa lub trzy aktywne worktrees: mój główny feature branch, worktree z main do szybkich sprawdzeń i czasem worktree do review, gdzie checkout-uję czyjś PR.
Jedno zastrzeżenie#
Nie możesz mieć tego samego brancha checkout-owanego w dwóch worktrees. To celowe — zapobiega dokonywaniu sprzecznych zmian na tym samym branchu w dwóch miejscach. Jeśli spróbujesz, Git odmówi.
Reflog: Przycisk cofnij do wszystkiego#
Zrobiłeś hard reset i straciłeś commity. Usunąłeś branch. Zrobiłeś rebase i coś poszło strasznie źle. Myślisz, że twoja praca przepadła.
Nie przepadła. Git prawie nigdy niczego nie kasuje. Reflog to twoja siatka bezpieczeństwa.
Czym jest reflog#
Za każdym razem, gdy HEAD się przesuwa — każdy commit, checkout, rebase, reset, merge — Git zapisuje to w reflogu. To log każdego miejsca, w którym był twój HEAD, w kolejności.
git reflogOutput:
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
f4e5d6c HEAD@{1}: commit: Add payment processing
b7a8c9d HEAD@{2}: commit: Update user dashboard
e0f1g2h HEAD@{3}: commit: Fix auth token refresh
i3j4k5l HEAD@{4}: checkout: moving from feature/payments to mainKażdy wpis ma indeks (HEAD@{0}, HEAD@{1} itp.) i opis tego, co się stało.
Odzyskiwanie po hard reset#
Przypadkowo uruchomiłeś git reset --hard HEAD~3 i straciłeś trzy commity. Są tam, w reflogu:
# See what you lost
git reflog
# The commit before the reset is HEAD@{1}
git reset --hard f4e5d6cWszystkie trzy commity wróciły. Kryzys zażegnany.
Odzyskiwanie usuniętego brancha#
Usunąłeś branch, który miał niezmerowaną pracę:
git branch -D feature/experimental
# Oh no, that had two weeks of workCommity nadal istnieją. Znajdź je:
git reflog | grep "feature/experimental"
# Or just look through the reflog for the last commit on that branch
# Found it. Recreate the branch at that commit:
git branch feature/experimental a1b2c3dBranch wrócił, ze wszystkimi swoimi commitami.
Odzyskiwanie po złym rebase#
Zrobiłeś rebase i wszystko poszło źle. Konflikty wszędzie, złe commity, chaos:
# The reflog shows where you were before the rebase
git reflog
# a1b2c3d HEAD@{0}: rebase (finish): ...
# ...
# f4e5d6c HEAD@{5}: rebase (start): checkout main
# b7a8c9d HEAD@{6}: commit: Your last good commit
# Go back to before the rebase
git reset --hard b7a8c9dWracasz dokładnie tam, gdzie byłeś przed rozpoczęciem rebase. Jakby się nigdy nie wydarzył.
30-dniowa siatka bezpieczeństwa#
Domyślnie Git zachowuje wpisy refloga przez 30 dni (90 dni dla osiągalnych commitów). Po tym czasie mogą zostać zebrane przez garbage collector. Więc masz miesiąc na zorientowanie się, że popełniłeś błąd. W praktyce to więcej niż wystarczy.
Możesz sprawdzić termin wygaśnięcia:
git config gc.reflogExpire
# default: 90.days.ago (for reachable)
git config gc.reflogExpireUnreachable
# default: 30.days.ago (for unreachable)Jeśli jesteś paranoikiem, zwiększ:
git config --global gc.reflogExpireUnreachable "180.days.ago"Osobista zasada#
Przed każdą destrukcyjną operacją — hard reset, force push, usunięcie brancha — uruchamiam git log --oneline -10 najpierw. Mentalnie notuję obecny HEAD. To zajmuje dwie sekundy i uratowało mnie nie raz od paniki, której nie musiałem mieć.
Stashuj poprawnie: To nie tylko git stash#
Większość ludzi używa stasha tak:
git stash
# do something
git stash popTo działa, ale to jak wrzucenie wszystkiego do pudełka z etykietą "rzeczy". Kiedy masz trzy stashe, nie masz pojęcia, który jest który.
Nazywaj swoje stashe#
git stash push -m "WIP: user auth form validation"Teraz kiedy listujesz stashe:
git stash list
# stash@{0}: On feature/auth: WIP: user auth form validation
# stash@{1}: On main: Quick fix attempt for nav bug
# stash@{2}: On feature/payments: Experiment with Stripe webhooksWidzisz dokładnie, co każdy stash zawiera.
Uwzględniaj nieśledzone pliki#
Domyślnie git stash stashuje tylko śledzone pliki. Nowe pliki, których jeszcze nie dodałeś, zostają:
# Stash everything, including new files
git stash push --include-untracked -m "WIP: new auth components"
# Or even include ignored files (rarely needed)
git stash push --all -m "Full workspace snapshot"Używam --include-untracked prawie za każdym razem. Pozostawienie nowych plików przy przełączaniu branchy powoduje zamieszanie.
Częściowe stashowanie#
To jest to, o czym większość ludzi nie wie. Możesz stashować konkretne pliki:
# Stash only specific files
git stash push -m "Just the auth changes" src/auth/ src/middleware.tsAlbo użyć trybu patch, żeby stashować konkretne kawałki w plikach:
git stash push --patch -m "Partial: only the validation logic"Git przejdzie przez każdą zmianę interaktywnie i zapyta, czy chcesz ją stashować. y na tak, n na nie, s żeby podzielić kawałek na mniejsze części.
Apply vs Pop#
# Pop: apply and remove from stash list
git stash pop stash@{2}
# Apply: apply but keep in stash list
git stash apply stash@{2}Używam apply, kiedy nie jestem pewien, czy stash zaaplikuje się czysto. Jeśli jest konflikt, stash jest zachowany. Przy pop, jeśli jest konflikt, stash i tak zostaje na liście (wielu ludzi o tym nie wie), ale wolę jawną intencję apply.
Przeglądanie zawartości stasha#
# See what files changed in a stash
git stash show stash@{0}
# See the full diff
git stash show -p stash@{0}Tworzenie brancha ze stasha#
Jeśli twój stash urósł do czegoś poważniejszego:
git stash branch feature/auth-validation stash@{0}To tworzy nowy branch z commita, na którym pierwotnie stashowałeś, aplikuje stash i go usuwa. Czysto.
Strategie branchowania: Która naprawdę działa#
Są trzy główne strategie branchowania. Każda ma kontekst, w którym świeci, i konteksty, w których powoduje ból.
Gitflow#
Klasyka. main, develop, feature/*, release/*, hotfix/*. Stworzone przez Vincenta Driessena w 2010.
# Feature branch
git checkout -b feature/user-auth develop
# ... work ...
git checkout develop
git merge --no-ff feature/user-auth
# Release branch
git checkout -b release/2.1.0 develop
# ... final fixes ...
git checkout main
git merge --no-ff release/2.1.0
git tag -a v2.1.0 -m "Release 2.1.0"
git checkout develop
git merge --no-ff release/2.1.0
# Hotfix
git checkout -b hotfix/session-fix main
# ... fix ...
git checkout main
git merge --no-ff hotfix/session-fix
git checkout develop
git merge --no-ff hotfix/session-fixKiedy działa: Aplikacje mobilne, oprogramowanie desktopowe, cokolwiek z nazwanymi wersjonowanymi releasy i wieloma wersjami utrzymywanymi jednocześnie. Jeśli wysyłasz v2.1 i v3.0 i musisz łatać obie, Gitflow to obsługuje.
Kiedy nie działa: Aplikacje webowe z ciągłym wdrażaniem. Jeśli deployujesz na produkcję 5 razy dziennie, ceremonia release branchy i develop branchy to czysty narzut. Większość zespołów webowych, które adoptują Gitflow, kończy z branchem develop, który jest wiecznie zepsuty, i release branchami, których nikt nie rozumie.
GitHub Flow#
Proste. Masz main. Tworzysz feature branche. Otwierasz PR-y. Mergujesz do main. Deployujesz main.
git checkout -b feature/user-auth main
# ... work ...
git push origin feature/user-auth
# Open PR, get reviewed, merge
# main is always deployableKiedy działa: Małe i średnie zespoły wysyłające aplikacje webowe. Ciągłe wdrażanie. Jeśli main jest zawsze deployowany, to wszystko, czego potrzebujesz. Z tego korzysta ta strona.
Kiedy nie działa: Kiedy musisz utrzymywać wiele wersji release, albo kiedy masz długi cykl QA przed wdrożeniem. GitHub Flow zakłada, że main szybko trafia na produkcję.
Trunk-Based Development#
Wszyscy commitują do main ("trunk") bezpośrednio albo przez bardzo krótko żyjące branche (krócej niż dzień). Bez długo żyjących feature branchy.
# Short-lived branch (merged same day)
git checkout -b fix/auth-token main
# ... small, focused change ...
git push origin fix/auth-token
# PR reviewed and merged within hours, not daysKiedy działa: Wysoko wydajne zespoły z dobrym CI/CD, kompleksowymi zestawami testów i feature flagami. Google, Meta i większość dużych firm technologicznych używa trunk-based development. Wymusza to małe, przyrostowe zmiany i eliminuje merge hell.
Kiedy nie działa: Zespoły bez dobrego pokrycia testami ani CI. Jeśli merge do trunk oznacza deployowanie nietestowanego kodu, będziesz łamać produkcję stale. Potrzebujesz też feature flag do czegokolwiek, co trwa dłużej niż dzień:
# Feature flag in code
if (featureFlags.isEnabled('new-checkout-flow')) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}Moja rekomendacja#
Dla większości zespołów web developmentowych: zacznij od GitHub Flow. Jest prosty, działa i nie wymaga toolingu poza tym, co GitHub/GitLab już zapewnia.
Jeśli twój zespół urośnie powyżej 15-20 inżynierów i deployujecie wiele razy dziennie, rozważ trunk-based development z feature flagami. Inwestycja w infrastrukturę feature flag zwraca się w zmniejszonej liczbie merge konfliktów i szybszej iteracji.
Jeśli wysyłasz wersjonowane oprogramowanie (aplikacje mobilne, narzędzia CLI, biblioteki): Gitflow albo jego uproszczona wersja. Faktycznie potrzebujesz tych release branchy.
Nie wybieraj strategii, bo post na blogu powiedział, że jest najlepsza. Wybierz tę, która pasuje do tego, jak naprawdę wysyłasz.
Git Hooks: Automatyzuj rzeczy, o których ciągle zapominasz#
Git hooks to skrypty, które uruchamiają się automatycznie w określonych momentach workflow'u Git. Są lokalne na twojej maszynie (nie są pushowane na remote), co oznacza, że potrzebujesz sposobu na dzielenie się nimi z zespołem.
Hooki, które naprawdę mają znaczenie#
pre-commit — Uruchamia się przed każdym commitem. Użyj do lintingu i formatowania:
#!/bin/bash
# .git/hooks/pre-commit
# Run ESLint on staged files only
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|tsx)$')
if [ -n "$STAGED_FILES" ]; then
echo "Running ESLint on staged files..."
npx eslint $STAGED_FILES --quiet
if [ $? -ne 0 ]; then
echo "ESLint failed. Fix errors before committing."
exit 1
fi
fi
# Run Prettier on staged files
if [ -n "$STAGED_FILES" ]; then
echo "Running Prettier..."
npx prettier --check $STAGED_FILES
if [ $? -ne 0 ]; then
echo "Prettier check failed. Run 'npx prettier --write' first."
exit 1
fi
ficommit-msg — Waliduje format wiadomości commita. Idealny do wymuszania conventional commits:
#!/bin/bash
# .git/hooks/commit-msg
COMMIT_MSG=$(cat "$1")
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,72}"
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
echo "Invalid commit message format."
echo "Expected: type(scope): description"
echo "Example: feat(auth): add session refresh endpoint"
echo ""
echo "Valid types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
exit 1
fipre-push — Uruchamia się przed pushem. Użyj do testów:
#!/bin/bash
# .git/hooks/pre-push
echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Push aborted."
exit 1
fiHusky vs natywne hooki#
Natywne hooki żyją w .git/hooks/. Problem: katalog .git nie jest śledzony przez Git, więc nie możesz dzielić się hookami przez repo. Każdy musi je skonfigurować ręcznie.
Husky rozwiązuje to. Przechowuje konfiguracje hooków w repo i automatycznie ustawia .git/hooks przy npm install:
npx husky initTo tworzy katalog .husky/. Dodawaj hooki jako pliki:
# .husky/pre-commit
npx lint-stagedW połączeniu z lint-staged dostajesz szybkie, celowane sprawdzenia pre-commit:
{
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.css": ["prettier --write"],
"*.json": ["prettier --write"]
}
}To uruchamia ESLint i Prettier tylko na plikach, które faktycznie commitujesz. Nie na całym codebase. Szybko.
Kiedy pomijać hooki#
Czasem musisz commitować bez uruchamiania hooków. Awaryjne hotfixy, commity work-in-progress na swoim branchu:
git commit --no-verify -m "WIP: debugging production issue"Używaj tego oszczędnie. Jeśli regularnie pomijasz hooki, twoje hooki są prawdopodobnie zbyt wolne albo zbyt restrykcyjne.
Aliasy, które oszczędzają czas#
Mój .gitconfig ewoluował przez lata. To są aliasy, które przetrwały — te, których faktycznie używam codziennie, nie te, które dodałem bo wyglądały sprytnie.
Ładny log#
Domyślny git log jest gadatliwy. To daje ci czysty, kolorowy, grafowy widok:
git config --global alias.lg "log --oneline --graph --all --decorate"Użycie:
git lg
# * a1b2c3d (HEAD -> main) Fix session validation
# | * d4e5f6g (feature/payments) Add Stripe integration
# | * h7i8j9k Update payment models
# |/
# * l0m1n2o Merge PR #42
# * p3q4r5s Add user dashboardUruchamiam to 20 razy dziennie. To najszybszy sposób na zrozumienie stanu repozytorium.
Cofnij ostatni commit#
Zachowaj zmiany, po prostu cofnij commit:
git config --global alias.undo "reset HEAD~1 --mixed"Użycie:
git undo
# Commit is gone, but all changes are still in your working directoryUżywam tego, kiedy commitnę za wcześnie, zapomnę o pliku albo chcę zrestrukturyzować zmiany.
Unstage'uj wszystko#
git config --global alias.unstage "reset HEAD --"Użycie:
git unstage src/auth/session.ts
# File is unstaged but changes are preservedLista wszystkich aliasów#
Bo zapomnisz, co ustawiłeś:
git config --global alias.aliases "config --get-regexp ^alias\\."Więcej aliasów, których używam#
# Show what you're about to commit
git config --global alias.staged "diff --staged"
# Short status
git config --global alias.st "status -sb"
# Amend without changing the message
git config --global alias.amend "commit --amend --no-edit"
# Show the last commit
git config --global alias.last "log -1 HEAD --stat"
# Pull with rebase instead of merge
git config --global alias.up "pull --rebase --autostash"
# Delete branches that have been merged to main
git config --global alias.cleanup "!git branch --merged main | grep -v '^[ *]*main$' | xargs git branch -d"Alias up jest szczególnie dobry. pull --rebase utrzymuje twoją historię liniową zamiast tworzenia merge commitów przy każdym pull. --autostash automatycznie stashuje i przywraca twoje zmiany, jeśli masz brudne pliki. To jest to, czym pull powinien być domyślnie.
Alias cleanup kasuje lokalne branche, które zostały zmergowane do main. Z czasem gromadzisz dziesiątki nieaktualnych branchy. Uruchamiaj to co tydzień.
Komendy, których używam codziennie#
To nie są aliasy ani zaawansowane funkcje. To po prostu komendy, które uruchamiam ciągle, a o których wielu deweloperów wydaje się nie wiedzieć.
Najlepsza komenda logu#
git log --oneline --graph --allTo pokazuje każdy branch, każdy merge, całą topologię twojego repo w kompaktowym widoku. To pierwsza rzecz, którą uruchamiam po pullowaniu zmian. Odpowiada na "co się dzieje w tym repo teraz?"
Diff zmian na stage#
git diff --stagedTo pokazuje, co ma zostać commitowane. Nie co się zmieniło w twoim katalogu roboczym — co jest faktycznie na stage. Zawsze uruchamiam to przed commitem. Zawsze. To łapie przypadkowe włączenia, debug statementy, console.logi, których tam nie powinno być.
# See unstaged changes
git diff
# See staged changes
git diff --staged
# See both
git diff HEADPokaż konkretny commit#
git show a1b2c3dPokazuje pełny diff pojedynczego commita. Przydatne przy przeglądaniu historii, rozumieniu, co commit naprawdę zmienił.
# Show just the files that changed
git show --stat a1b2c3d
# Show a specific file at a specific commit
git show a1b2c3d:src/auth/session.tsTa ostatnia jest niesamowicie przydatna. Możesz przeglądać dowolny plik w dowolnym punkcie historii bez checkout-owania tego commita.
Blame z zakresami linii#
git blame -L 42,60 src/auth/session.tsPokazuje, kto ostatnio zmodyfikował linie 42-60. Bardziej przydatne niż blame na całym pliku, co jest zwykle przytłaczające.
# Show the commit message too, not just the hash
git blame -L 42,60 --show-name src/auth/session.ts
# Ignore whitespace changes (very useful)
git blame -w -L 42,60 src/auth/session.ts
# Show the commit before the blamed one (dig deeper)
git log --follow -p -- src/auth/session.tsFlaga -w jest ważna. Bez niej blame przypisze linie osobie, która ostatnio przeformatowała plik, co rzadko jest osobą, której szukasz.
Znajdź string w całej historii#
# Find when a function was added or removed
git log -S "validateSession" --oneline
# Find when a regex pattern appeared
git log -G "session.*timeout" --oneline-S ("pickaxe") znajduje commity, w których zmieniła się liczba wystąpień stringa. -G znajduje commity, w których diff pasuje do regexa. Oba są potężne do archeologii — ustalania, kiedy coś zostało wprowadzone lub usunięte.
Pokaż, co się zmieniło między dwoma punktami#
# What changed between two branches
git diff main..feature/auth
# What changed on this branch since it diverged from main
git diff main...feature/auth
# List just the changed files
git diff main...feature/auth --name-only
# Stat view (files + insertions/deletions)
git diff main...feature/auth --statDwie kropki vs trzy kropki ma znaczenie. Dwie kropki pokazują różnicę między wierzchołkami obu branchy. Trzy kropki pokazują, co się zmieniło po prawej stronie od momentu, gdy rozeszła się z lewą stroną. Trzy kropki to zwykle to, czego chcesz przy przeglądaniu feature brancha.
Posprzątaj nieśledzone pliki#
# See what would be deleted (dry run)
git clean -n
# Delete untracked files
git clean -f
# Delete untracked files and directories
git clean -fd
# Delete untracked and ignored files (nuclear option)
git clean -fdxZawsze uruchamiaj z -n najpierw. git clean -fdx usunie twoje node_modules, .env, artefakty builda — wszystko, co nie jest śledzone przez Git. Przydatne do naprawdę świeżego startu, ale destrukcyjne.
Przywróć pojedynczy plik z innego brancha#
# Get the main branch version of a file without switching branches
git restore --source main -- src/config/database.tsAlbo z konkretnego commita:
git restore --source a1b2c3d -- src/config/database.tsTo jest czystsze niż stara składnia git checkout main -- path/to/file i nie wpływa na HEAD.
Składając wszystko razem: prawdziwy workflow#
Oto jak wygląda typowy dzień z tymi narzędziami:
# Morning: check what's happening
git lg
git fetch --all
# Start a feature
git checkout -b feature/session-refresh main
# Work, commit incrementally
git add -p # Stage specific hunks, not entire files
git commit -m "Add token refresh endpoint"
git commit -m "Add refresh token rotation"
git commit -m "Fix: handle expired refresh tokens"
git commit -m "Add integration tests"
# Interrupted: need to fix a production bug
git worktree add ../hotfix main
cd ../hotfix
# ... fix, commit, push, PR merged ...
cd ../my-project
git worktree remove ../hotfix
# Back to feature work
git commit -m "Fix edge case in token validation"
# Ready for PR: clean up history
git rebase -i main
# Squash the fix commits, reword for clarity
# Push and open PR
git push -u origin feature/session-refresh
# Something broke in staging? Find out which commit:
git bisect start
git bisect bad HEAD
git bisect good main
git bisect run npm test
# Oops, I hard-reset the wrong thing
git reflog
git reset --hard HEAD@{2}Każda z tych komend zajmuje sekundy. Razem oszczędzają godziny. Nie hipotetyczne godziny — prawdziwe godziny, każdego tygodnia, które w innym przypadku spędziłbym na rozplątywaniu bałaganu w Git, ręcznym szukaniu bugów albo traceniu pracy na przełączeniach kontekstu.
Git to narzędzie, które nagradza głębokość. Podstawy wystarczą na codzień. Ale komendy w tym poście to to, co oddziela "używam Gita" od "Git faktycznie mnie przyspiesza". Ucz się ich przyrostowo. Wybierz jedną nową technikę w tym tygodniu i używaj jej, aż stanie się pamięcią mięśniową. Potem wybierz następną.
Twoje przyszłe ja, wpatrujące się w buga produkcyjnego o 23, podziękuje ci za znajomość git bisect.