Git 기본을 넘어서: 매주 시간을 절약하는 워크플로우
인터랙티브 리베이스, 체리픽, bisect, worktree, reflog 복구, 그리고 실제로 작동하는 브랜칭 전략. 대부분의 개발자가 모르는 매일 사용하는 Git 명령어들.
대부분의 개발자는 다섯 개의 Git 명령어를 배우고 거기서 멈춥니다. add, commit, push, pull, merge. 모험심이 있다면 checkout과 branch도요. 첫 해는 그것으로 충분합니다. 그러다 브랜치에 "fix"와 "wip"와 "please work" 같은 메시지의 커밋이 47개가 되고, 실수로 리셋하면 안 되는 것을 리셋하고, Stack Overflow에서 잘못된 병합을 되돌리려고 40분을 보냅니다.
저는 수년간 Git을 사용해왔습니다. 가볍게가 아니라 — 집중적으로. 여러 브랜치, 여러 리포지토리, 여러 협업자, 하루 종일, 매일. 아래에 나오는 것은 제가 실제로 사용하는 명령어와 워크플로우입니다. 튜토리얼에서 멋져 보이는 것이 아니라, 매주 실제 시간을 절약해 주는 것들입니다.
인터랙티브 리베이스: 남들이 보기 전에 정리하기#
브랜치에 열두 개의 커밋이 있습니다. 절반은 "오타 수정"입니다. 하나는 "이전 커밋 취소"라고 합니다. 또 하나는 "이번에야말로 진짜 수정"이라고 합니다. PR을 열려고 합니다. 아무도 그 히스토리를 볼 필요가 없습니다.
인터랙티브 리베이스는 공유하기 전에 브랜치의 히스토리를 다시 작성하는 방법입니다. 커밋을 합치거나, 메시지를 수정하거나, 순서를 바꾸거나, 완전히 삭제할 수 있습니다.
기본 명령어#
git rebase -i HEAD~5이것은 마지막 5개의 커밋을 오래된 것부터 보여주는 편집기를 엽니다:
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 tests각 줄은 명령어로 시작합니다. pick을 다음 중 하나로 변경하세요:
squash(또는s) — 이 커밋을 위의 것과 병합, 메시지 결합fixup(또는f) — squash와 동일하지만, 이 커밋의 메시지를 버림reword(또는r) — 커밋은 유지하되 메시지를 변경drop(또는d) — 이 커밋을 완전히 삭제edit(또는e) — 이 커밋에서 리베이스를 일시 중지하여 수정 가능
실제 정리 세션#
실제로 하는 것입니다. 위의 지저분한 히스토리가 이렇게 됩니다:
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 tests저장하고 닫습니다. 이제 다섯 개 대신 세 개의 깔끔한 커밋이 있습니다. 오타 수정은 인증 커밋에 접히고, 속도 제한 버그 수정은 속도 제한 커밋에 접힙니다. PR 리뷰어는 깔끔하고 논리적인 진행을 봅니다.
커밋 순서 변경#
줄을 그대로 재배열할 수 있습니다. 테스트 커밋이 속도 제한 커밋 앞에 와야 한다면, 줄을 이동하면 됩니다:
pick a1b2c3d Add user authentication endpoint
pick p3q4r5s Update auth tests
pick h7i8j9k Add rate limitingGit이 이 새로운 순서로 커밋을 다시 재생합니다. 충돌이 있으면 일시 중지하고 해결하게 해줍니다.
Autosquash 단축키#
커밋이 이전 것의 수정이라는 것을 알면, 커밋 시에 표시하세요:
git commit --fixup=a1b2c3d이것은 fixup! Add user authentication endpoint이라는 메시지로 커밋을 만듭니다. 그런 다음 리베이스할 때:
git rebase -i --autosquash HEAD~5Git이 자동으로 fixup 커밋을 대상 바로 아래로 재정렬하고 fixup으로 표시합니다. 저장하고 닫기만 하면 됩니다. 수동 편집 불필요.
저는 이것을 끊임없이 사용합니다. 최종 히스토리를 깔끔하게 유지하면서 브랜치를 반복하는 가장 빠른 방법입니다.
황금 규칙#
공유 브랜치에 푸시된 커밋을 절대 리베이스하지 마세요. 다른 사람들이 그 커밋을 기반으로 작업했다면, 히스토리를 다시 쓰면 실제 문제가 생깁니다. 병합하기 전에 자신의 피처 브랜치를 리베이스하세요. 절대 main을 리베이스하지 마세요.
이미 피처 브랜치를 푸시했고 리베이스가 필요하다면:
git push --force-with-lease--force-with-lease 플래그는 --force보다 안전합니다. 마지막 fetch 이후 다른 사람이 같은 브랜치에 푸시했다면 거부합니다. 모든 문제를 방지하지는 않지만, 가장 흔한 문제를 잡아냅니다.
체리픽: 수술적 커밋 전송#
체리픽은 한 브랜치에서 특정 커밋을 가져와 다른 브랜치에 적용합니다. 병합이 아닙니다. 리베이스가 아닙니다. 하나의 커밋만, 깔끔하게 적용됩니다.
실제로 사용하는 때#
가장 흔한 시나리오: 피처 브랜치에서 버그를 수정했는데, 그 수정이 지금 당장 main이나 릴리스 브랜치에도 필요합니다. 전체 피처 브랜치를 병합하고 싶지 않습니다. 그 하나의 수정만 원합니다.
# 수정의 커밋 해시 찾기
git log --oneline feature/user-auth
# a1b2c3d Fix null pointer in session validation
# main으로 전환하고 체리픽
git checkout main
git cherry-pick a1b2c3d완료. 수정이 같은 변경 사항을 가진 새 커밋으로 main에 있습니다.
커밋 없이 체리픽#
변경 사항을 적용하되 아직 커밋하고 싶지 않은 경우가 있습니다. 여러 체리픽을 하나의 커밋으로 합치거나, 변경 사항을 약간 수정하고 싶을 수 있습니다:
git cherry-pick --no-commit a1b2c3d변경 사항은 스테이징되지만 커밋되지 않습니다. 수정하고, 추가 변경을 하고, 준비되면 커밋할 수 있습니다.
범위 체리픽#
연속된 여러 커밋이 필요한가요? 범위 구문을 사용하세요:
git cherry-pick a1b2c3d..f6g7h8i이것은 a1b2c3d 이후부터 f6g7h8i까지(포함) 모든 것을 체리픽합니다. a1b2c3d 자체는 제외됩니다. 포함하려면:
git cherry-pick a1b2c3d^..f6g7h8i충돌 처리#
체리픽 충돌은 병합 충돌처럼 작동합니다. 발생하면:
# Git이 충돌이 있다고 알려줍니다
# 충돌 파일을 수정한 후:
git add .
git cherry-pick --continue마음이 바뀌면:
git cherry-pick --abort주의할 점: 체리픽된 커밋은 새 커밋 해시를 만듭니다. 나중에 원래 브랜치를 병합하면, Git은 대개 중복을 잘 처리합니다. 하지만 결국 병합될 브랜치 사이에서 적극적으로 체리픽하면, 예상치 못한 충돌을 볼 수 있습니다. 수술적으로 사용하세요, 병합 전략으로가 아니라.
Git Bisect: 버그에 대한 이진 검색#
무언가가 망가졌습니다. 2주 전에는 작동했다는 것을 알고 있습니다. 그 이후로 200개의 커밋이 있습니다. 어떤 것이 망가뜨렸을까요?
각 커밋을 수동으로 확인할 수 있습니다. 아니면 git bisect를 사용할 수 있는데, 이진 검색을 사용하여 log2(n) 단계로 정확한 원인 커밋을 찾습니다. 200개 커밋이면 200개 대신 약 7-8번 확인입니다.
수동 방법#
# bisect 시작
git bisect start
# 현재 커밋을 나쁨으로 표시 (버그가 여기 존재)
git bisect bad
# 알려진 좋은 커밋 표시 (버그가 여기에는 없었음)
git bisect good v2.1.0Git이 좋음과 나쁨 사이의 중간 커밋을 체크아웃합니다. 테스트합니다. 그런 다음:
# 이 커밋에 버그가 존재하면:
git bisect bad
# 이 커밋에 버그가 존재하지 않으면:
git bisect goodGit이 매번 범위를 절반으로 좁힙니다. 7-8단계 후에 알려줍니다:
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 middleware이제 정확히 어떤 커밋이 버그를 도입했는지 알 수 있습니다. 완료되면:
git bisect reset시작했던 곳으로 돌아갑니다.
자동화된 방법 (진정한 힘)#
버그를 감지할 수 있는 테스트가 있다면, 전체를 자동화할 수 있습니다:
git bisect start
git bisect bad HEAD
git bisect good v2.1.0
git bisect run npm test -- --grep "session validation"Git이 자동으로 커밋을 체크아웃하고, 테스트를 실행하고, 종료 코드에 따라 좋음 또는 나쁨으로 표시합니다. 0은 좋음, 0이 아니면 나쁨. 자리를 비우고 돌아오면 정확한 커밋을 알려줍니다.
어떤 스크립트든 사용할 수 있습니다:
git bisect run ./test-regression.shtest-regression.sh는:
#!/bin/bash
npm run build 2>/dev/null || exit 125 # 125는 "이 커밋 건너뛰기"를 의미
npm test -- --grep "session" || exit 1 # 1은 "나쁨"을 의미
exit 0 # 0은 "좋음"을 의미종료 코드 125는 특별합니다 — bisect에 그 커밋을 건너뛰라고 알려줍니다(커밋이 컴파일되지 않을 때 유용). 필요하기 전까지는 틈새로 보이지만, 필요할 때 오후 전체를 절약하는 기능입니다.
실제 Bisect 세션#
실제로 어떻게 보이는지:
$ 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"
# 테스트 실패
$ git bisect bad
Bisecting: 48 revisions left to test after this (roughly 6 steps)
[ghi9012...] Another commit message
$ npm test -- --grep "login flow"
# 테스트 통과
$ git bisect good
Bisecting: 24 revisions left to test after this (roughly 5 steps)
...
# ~7번 반복 후:
abc1234def5678ghi is the first bad commit200개 커밋의 건초더미에서 바늘을 찾는 데 일곱 단계.
Git Worktree: 여러 브랜치, 스태시 없이#
가장 적게 사용되는 Git 기능입니다. 대부분의 개발자가 존재하는지 모른다고 확신합니다.
문제: 피처 브랜치에 깊이 빠져 있습니다. 어디서나 파일이 변경되어 있습니다. 그때 누군가 "이 프로덕션 버그 좀 빨리 봐줄 수 있어?"라고 합니다. 세 가지 선택지가 있습니다:
- 모든 것을 스태시하고, 브랜치를 전환하고, 수정하고, 다시 전환하고, 스태시를 팝합니다. 아무것도 잘못되지 않기를 바랍니다.
- 반쯤 된 작업을 "wip" 메시지로 커밋합니다. 보기 흉하지만 기능적.
- 다른 디렉토리에 리포를 다시 클론합니다.
또는 옵션 4: worktree.
Worktree란 무엇인가#
Worktree는 같은 리포지토리에 연결된 두 번째(또는 세 번째, 네 번째) 작업 디렉토리입니다. 각 worktree는 자체적으로 체크아웃된 브랜치, 자체 작업 파일, 자체 인덱스를 가집니다. 하지만 같은 .git 데이터를 공유하므로 전체 리포를 복제하지 않습니다.
Worktree 추가#
# feature/user-auth에서 작업 중, 깊이 빠져 있음
# main에서 버그를 수정해야 함:
git worktree add ../hotfix-session main이것은 main이 체크아웃된 새 디렉토리 ../hotfix-session을 만듭니다. 현재 디렉토리는 그대로 유지됩니다. 스태시 없이, 커밋 없이, 아무것도 방해받지 않습니다.
cd ../hotfix-session
# 버그 수정
git add .
git commit -m "Fix null pointer in session validation"
git push origin main
cd ../my-project
# 아무 일도 없었던 것처럼 피처 작업 계속Worktree에서 새 브랜치 만들기#
git worktree add ../hotfix-nav -b hotfix/nav-crash main이것은 worktree를 만들고 동시에 main을 기반으로 새 브랜치 hotfix/nav-crash를 만듭니다.
Worktree 관리#
# 모든 worktree 나열
git worktree list
# /home/dev/my-project abc1234 [feature/user-auth]
# /home/dev/hotfix-session def5678 [main]
# 완료 시 worktree 제거
git worktree remove ../hotfix-session
# 디렉토리가 이미 삭제된 경우:
git worktree prune스태시보다 나은 이유#
스태시는 빠른 컨텍스트 전환에 괜찮습니다. 하지만 worktree는 5분 이상 걸리는 모든 것에 더 좋습니다:
- IDE가 피처 브랜치에서 열린 상태로 유지됩니다. 재인덱싱 없이, 스크롤 위치를 잃지 않습니다.
- 두 디렉토리에서 동시에 테스트를 실행할 수 있습니다.
- 스태시 충돌이나 무엇을 스태시했는지 잊을 위험이 없습니다.
- 한 worktree에서 장기 실행 개발 서버를 두고 다른 것에서 깨끗한 빌드를 할 수 있습니다.
저는 일반적으로 두세 개의 worktree를 활성 상태로 유지합니다: 메인 피처 브랜치, 빠른 확인을 위한 main worktree, 때로는 다른 사람의 PR을 체크아웃하는 리뷰 worktree.
한 가지 주의점#
같은 브랜치를 두 worktree에서 체크아웃할 수 없습니다. 이것은 설계상 의도된 것입니다 — 같은 브랜치에 대해 두 곳에서 충돌하는 변경을 방지합니다. 시도하면 Git이 거부합니다.
Reflog: 모든 것의 실행 취소 버튼#
하드 리셋을 해서 커밋을 잃었습니다. 브랜치를 삭제했습니다. 리베이스를 했는데 끔찍하게 잘못됐습니다. 작업이 사라졌다고 생각합니다.
사라지지 않았습니다. Git은 실제로 거의 아무것도 삭제하지 않습니다. reflog가 안전망입니다.
Reflog란 무엇인가#
HEAD가 이동할 때마다 — 모든 커밋, 체크아웃, 리베이스, 리셋, 병합 — Git이 reflog에 기록합니다. HEAD가 있었던 모든 곳의 로그이며, 순서대로.
git reflog출력:
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 main각 항목에는 인덱스(HEAD@{0}, HEAD@{1} 등)와 어떤 일이 일어났는지에 대한 설명이 있습니다.
하드 리셋 후 복구#
실수로 git reset --hard HEAD~3을 실행하여 세 개의 커밋을 잃었습니다. reflog에 바로 있습니다:
# 잃은 것 확인
git reflog
# 리셋 전 커밋은 HEAD@{1}
git reset --hard f4e5d6c세 커밋이 모두 돌아왔습니다. 위기 해소.
삭제된 브랜치 복구#
병합되지 않은 작업이 있는 브랜치를 삭제했습니다:
git branch -D feature/experimental
# 맙소사, 거기에 2주간의 작업이 있었는데커밋은 여전히 존재합니다. 찾으세요:
git reflog | grep "feature/experimental"
# 또는 그 브랜치의 마지막 커밋을 reflog에서 찾으세요
# 찾았습니다. 그 커밋에서 브랜치를 다시 만들기:
git branch feature/experimental a1b2c3d모든 커밋과 함께 브랜치가 돌아왔습니다.
나쁜 리베이스 후 복구#
리베이스를 했는데 모든 것이 잘못됐습니다. 충돌 전부, 잘못된 커밋, 혼돈:
# reflog가 리베이스 전 위치를 보여줍니다
git reflog
# a1b2c3d HEAD@{0}: rebase (finish): ...
# ...
# f4e5d6c HEAD@{5}: rebase (start): checkout main
# b7a8c9d HEAD@{6}: commit: Your last good commit
# 리베이스 전으로 돌아가기
git reset --hard b7a8c9d리베이스 시작 전 정확히 있었던 곳으로 돌아왔습니다. 일어나지 않은 것처럼.
30일 안전망#
기본적으로 Git은 reflog 항목을 30일간(도달 가능한 커밋은 90일) 유지합니다. 그 후에는 가비지 컬렉션될 수 있습니다. 실수를 인식할 시간이 한 달 있습니다. 실제로 이것은 충분합니다.
만료를 확인할 수 있습니다:
git config gc.reflogExpire
# 기본값: 90.days.ago (도달 가능)
git config gc.reflogExpireUnreachable
# 기본값: 30.days.ago (도달 불가능)걱정된다면 늘리세요:
git config --global gc.reflogExpireUnreachable "180.days.ago"개인적인 규칙#
모든 파괴적 작업 — 하드 리셋, 강제 푸시, 브랜치 삭제 — 전에 먼저 git log --oneline -10을 실행합니다. 현재 HEAD를 머릿속에 기록합니다. 2초가 걸리며, 불필요한 공포에서 한 번 이상 구해줬습니다.
스태시 제대로 하기: 그냥 git stash가 아닙니다#
대부분의 사람들은 스태시를 이렇게 사용합니다:
git stash
# 뭔가 하기
git stash pop작동하지만, 모든 것을 "물건"이라고 적힌 상자에 던지는 것과 같습니다. 스태시가 세 개 있으면 어느 것이 어느 것인지 알 수 없습니다.
스태시에 이름 붙이기#
git stash push -m "WIP: user auth form validation"이제 스태시를 나열하면:
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 webhooks각 스태시에 무엇이 들어있는지 정확히 볼 수 있습니다.
비추적 파일 포함#
기본적으로 git stash는 추적된 파일만 스태시합니다. 아직 추가하지 않은 새 파일은 남겨집니다:
# 새 파일을 포함하여 모든 것 스태시
git stash push --include-untracked -m "WIP: new auth components"
# 무시된 파일까지 포함 (거의 필요 없음)
git stash push --all -m "Full workspace snapshot"저는 거의 매번 --include-untracked를 사용합니다. 브랜치를 전환할 때 새 파일을 남겨두면 혼란을 일으킵니다.
부분 스태시#
대부분의 사람이 모르는 것입니다. 특정 파일을 스태시할 수 있습니다:
# 특정 파일만 스태시
git stash push -m "Just the auth changes" src/auth/ src/middleware.ts또는 패치 모드를 사용하여 파일 내의 특정 헝크를 스태시:
git stash push --patch -m "Partial: only the validation logic"Git이 각 변경을 인터랙티브하게 검토하고 스태시할지 물어봅니다. y는 예, n은 아니오, s는 헝크를 더 작은 조각으로 분할.
Apply vs Pop#
# Pop: 적용하고 스태시 목록에서 제거
git stash pop stash@{2}
# Apply: 적용하되 스태시 목록에 유지
git stash apply stash@{2}스태시가 깔끔하게 적용될지 확신이 없을 때 apply를 사용합니다. 충돌이 있으면 스태시가 보존됩니다. pop에서는 충돌이 있어도 스태시가 목록에 남아있지만(많은 사람이 이것을 모릅니다), 저는 명시적인 의도의 apply를 선호합니다.
스태시 내용 보기#
# 스태시에서 변경된 파일 보기
git stash show stash@{0}
# 전체 diff 보기
git stash show -p stash@{0}스태시에서 브랜치 만들기#
스태시가 뭔가 더 실질적인 것으로 성장했다면:
git stash branch feature/auth-validation stash@{0}이것은 원래 스태시했던 커밋에서 새 브랜치를 만들고, 스태시를 적용하고, 드롭합니다. 깔끔합니다.
브랜칭 전략: 실제로 작동하는 것#
세 가지 주류 브랜칭 전략이 있습니다. 각각 빛나는 맥락과 고통을 유발하는 맥락이 있습니다.
Gitflow#
클래식. main, develop, feature/*, release/*, hotfix/*. 2010년 Vincent Driessen이 만듦.
# 피처 브랜치
git checkout -b feature/user-auth develop
# ... 작업 ...
git checkout develop
git merge --no-ff feature/user-auth
# 릴리스 브랜치
git checkout -b release/2.1.0 develop
# ... 최종 수정 ...
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
# 핫픽스
git checkout -b hotfix/session-fix main
# ... 수정 ...
git checkout main
git merge --no-ff hotfix/session-fix
git checkout develop
git merge --no-ff hotfix/session-fix작동하는 때: 모바일 앱, 데스크톱 소프트웨어, 이름 붙은 버전 릴리스가 있고 여러 버전을 동시에 지원해야 하는 모든 것. v2.1과 v3.0을 출시하고 둘 다 패치해야 한다면 Gitflow가 처리합니다.
작동하지 않는 때: 지속적 배포가 있는 웹 애플리케이션. 하루에 5번 프로덕션에 배포한다면, 릴리스 브랜치와 develop 브랜치의 의식은 순수한 오버헤드입니다. Gitflow를 채택한 대부분의 웹 팀은 영구적으로 망가진 develop 브랜치와 아무도 이해하지 못하는 릴리스 브랜치를 가지게 됩니다.
GitHub Flow#
단순합니다. main이 있습니다. 피처 브랜치를 만듭니다. PR을 엽니다. main에 병합합니다. main을 배포합니다.
git checkout -b feature/user-auth main
# ... 작업 ...
git push origin feature/user-auth
# PR 열기, 리뷰 받기, 병합
# main은 항상 배포 가능작동하는 때: 웹 앱을 출시하는 소규모에서 중규모 팀. 지속적 배포. main이 항상 배포된다면 이것만 있으면 됩니다. 이 사이트가 사용하는 방식입니다.
작동하지 않는 때: 여러 릴리스 버전을 유지해야 하거나, 배포 전 긴 QA 주기가 있을 때. GitHub Flow는 main이 빠르게 프로덕션으로 간다고 가정합니다.
트렁크 기반 개발#
모든 사람이 main("트렁크")에 직접 커밋하거나 매우 짧은 수명의 브랜치(하루 미만)를 통해 커밋합니다. 장기 피처 브랜치 없음.
# 짧은 수명 브랜치 (당일 병합)
git checkout -b fix/auth-token main
# ... 작고 집중된 변경 ...
git push origin fix/auth-token
# PR 리뷰 및 몇 시간 내 병합, 며칠이 아님작동하는 때: 좋은 CI/CD, 포괄적인 테스트 스위트, 피처 플래그를 가진 고성능 팀. Google, Meta, 대부분의 대형 테크 회사가 트렁크 기반 개발을 사용합니다. 작고 점진적인 변경을 강제하고 머지 헬을 제거합니다.
작동하지 않는 때: 좋은 테스트 커버리지나 CI가 없는 팀. 트렁크에 병합하는 것이 테스트되지 않은 코드를 배포하는 것을 의미한다면, 끊임없이 프로덕션을 망가뜨릴 것입니다. 하루 이상 걸리는 모든 것에 피처 플래그도 필요합니다:
# 코드의 피처 플래그
if (featureFlags.isEnabled('new-checkout-flow')) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}나의 권장사항#
대부분의 웹 개발 팀에게: GitHub Flow로 시작하세요. 간단하고 작동하며 GitHub/GitLab이 이미 제공하는 것 이상의 도구가 필요 없습니다.
팀이 15-20명 엔지니어를 넘고 하루에 여러 번 배포한다면, 피처 플래그와 함께 트렁크 기반 개발을 살펴보세요. 피처 플래그 인프라에 대한 투자는 감소된 병합 충돌과 빠른 반복으로 회수됩니다.
버전이 있는 소프트웨어를 출시한다면(모바일 앱, CLI 도구, 라이브러리): Gitflow 또는 단순화된 버전. 실제로 릴리스 브랜치가 필요합니다.
블로그 글이 최고라고 했다고 전략을 선택하지 마세요. 실제로 출시하는 방식에 맞는 것을 선택하세요.
Git Hooks: 계속 잊는 것 자동화하기#
Git 훅은 Git 워크플로우의 특정 시점에서 자동으로 실행되는 스크립트입니다. 로컬 머신에 있으므로(리모트에 푸시되지 않음), 팀과 공유할 방법이 필요합니다.
실제로 중요한 훅#
pre-commit — 모든 커밋 전에 실행됩니다. 린팅과 포맷팅에 사용:
#!/bin/bash
# .git/hooks/pre-commit
# 스테이징된 파일에만 ESLint 실행
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
# 스테이징된 파일에 Prettier 실행
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 — 커밋 메시지 형식을 검증합니다. 컨벤셔널 커밋을 강제하는 데 완벽:
#!/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 — 푸시 전에 실행됩니다. 테스트에 사용:
#!/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 네이티브 훅#
네이티브 훅은 .git/hooks/에 있습니다. 문제: .git 디렉토리는 Git이 추적하지 않으므로, 리포를 통해 훅을 공유할 수 없습니다. 모든 사람이 수동으로 설정해야 합니다.
Husky가 이것을 해결합니다. 훅 설정을 리포에 저장하고 npm install 시 자동으로 .git/hooks를 설정합니다:
npx husky init이것은 .husky/ 디렉토리를 만듭니다. 파일로 훅을 추가합니다:
# .husky/pre-commit
npx lint-stagedlint-staged와 결합하면, 빠르고 타겟팅된 pre-commit 검사를 얻습니다:
{
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.css": ["prettier --write"],
"*.json": ["prettier --write"]
}
}이것은 실제로 커밋하는 파일에만 ESLint와 Prettier를 실행합니다. 전체 코드베이스가 아닙니다. 빠릅니다.
훅을 건너뛰어야 할 때#
때로는 훅 실행 없이 커밋해야 합니다. 긴급 핫픽스, 자신의 브랜치에서의 작업 진행 중 커밋:
git commit --no-verify -m "WIP: debugging production issue"아껴서 사용하세요. 훅을 정기적으로 건너뛰고 있다면, 훅이 너무 느리거나 너무 엄격할 수 있습니다.
시간을 절약하는 별칭#
제 .gitconfig는 수년에 걸쳐 발전했습니다. 살아남은 별칭입니다 — 실제로 매일 사용하는 것들이지, 멋있어 보여서 추가한 것이 아닙니다.
예쁜 로그#
기본 git log는 장황합니다. 이것은 깔끔하고, 컬러풀하고, 그래프 기반 뷰를 제공합니다:
git config --global alias.lg "log --oneline --graph --all --decorate"사용법:
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 dashboard하루에 20번 실행합니다. 리포지토리의 상태를 이해하는 가장 빠른 방법입니다.
마지막 커밋 취소#
변경 사항은 유지하고 커밋만 취소:
git config --global alias.undo "reset HEAD~1 --mixed"사용법:
git undo
# 커밋은 사라졌지만 모든 변경 사항은 작업 디렉토리에 여전히 있음너무 일찍 커밋했거나, 파일을 잊었거나, 변경 사항을 재구성하고 싶을 때 사용합니다.
모두 언스테이지#
git config --global alias.unstage "reset HEAD --"사용법:
git unstage src/auth/session.ts
# 파일이 언스테이지되지만 변경 사항은 보존모든 별칭 나열#
설정한 것을 잊을 것이기 때문에:
git config --global alias.aliases "config --get-regexp ^alias\\."더 많이 사용하는 별칭#
# 커밋하려는 것 보기
git config --global alias.staged "diff --staged"
# 짧은 상태
git config --global alias.st "status -sb"
# 메시지 변경 없이 수정
git config --global alias.amend "commit --amend --no-edit"
# 마지막 커밋 보기
git config --global alias.last "log -1 HEAD --stat"
# 병합 대신 리베이스로 풀
git config --global alias.up "pull --rebase --autostash"
# main에 병합된 브랜치 삭제
git config --global alias.cleanup "!git branch --merged main | grep -v '^[ *]*main$' | xargs git branch -d"up 별칭이 특히 좋습니다. pull --rebase는 매 풀마다 병합 커밋을 만드는 대신 히스토리를 선형으로 유지합니다. --autostash는 더러운 파일이 있으면 자동으로 스태시하고 복원합니다. pull이 기본적으로 해야 할 것입니다.
cleanup 별칭은 main에 병합된 로컬 브랜치를 삭제합니다. 시간이 지나면 수십 개의 오래된 브랜치가 쌓입니다. 매주 실행하세요.
매일 사용하는 명령어#
별칭이나 고급 기능이 아닙니다. 끊임없이 실행하는 명령어인데 많은 개발자가 모르는 것 같습니다.
궁극의 로그 명령어#
git log --oneline --graph --all모든 브랜치, 모든 병합, 리포의 전체 토폴로지를 컴팩트한 뷰로 보여줍니다. 변경 사항을 풀할 때 가장 먼저 실행합니다. "이 리포에서 지금 무슨 일이 일어나고 있나?"에 답합니다.
스테이징된 변경사항 Diff#
git diff --staged커밋되려는 것을 보여줍니다. 작업 디렉토리에서 변경된 것이 아니라 — 실제로 스테이징된 것입니다. 커밋 전에 항상 이것을 실행합니다. 항상. 실수로 포함된 것, 디버그 문, 있으면 안 되는 console.log를 잡아냅니다.
# 언스테이징된 변경 보기
git diff
# 스테이징된 변경 보기
git diff --staged
# 둘 다 보기
git diff HEAD특정 커밋 보기#
git show a1b2c3d단일 커밋의 전체 diff를 보여줍니다. 히스토리를 리뷰하거나 커밋이 실제로 무엇을 변경했는지 이해할 때 유용합니다.
# 변경된 파일만 보기
git show --stat a1b2c3d
# 특정 커밋의 특정 파일 보기
git show a1b2c3d:src/auth/session.ts마지막 것이 매우 유용합니다. 그 커밋을 체크아웃하지 않고 히스토리의 어느 시점에서든 파일을 볼 수 있습니다.
줄 범위로 Blame#
git blame -L 42,60 src/auth/session.ts42-60줄을 마지막으로 수정한 사람을 보여줍니다. 파일 전체를 blame하는 것보다 유용합니다. 파일 전체는 대개 압도적입니다.
# 해시뿐만 아니라 커밋 메시지도 보기
git blame -L 42,60 --show-name src/auth/session.ts
# 공백 변경 무시 (매우 유용)
git blame -w -L 42,60 src/auth/session.ts
# blame된 것 이전의 커밋 보기 (더 깊이 파기)
git log --follow -p -- src/auth/session.ts-w 플래그가 중요합니다. 없으면 blame이 파일을 마지막으로 재포맷한 사람에게 줄을 귀속시키며, 이것은 보통 찾는 사람이 아닙니다.
전체 히스토리에서 문자열 찾기#
# 함수가 언제 추가되거나 제거되었는지 찾기
git log -S "validateSession" --oneline
# 정규식 패턴이 언제 나타났는지 찾기
git log -G "session.*timeout" --oneline-S("pickaxe")는 문자열의 발생 횟수가 변경된 커밋을 찾습니다. -G는 diff가 정규식과 일치하는 커밋을 찾습니다. 둘 다 고고학에 강력합니다 — 무언가가 언제 도입되거나 제거되었는지 파악하기.
두 지점 사이의 변경 보기#
# 두 브랜치 사이의 변경
git diff main..feature/auth
# 이 브랜치가 main에서 분기된 이후의 변경
git diff main...feature/auth
# 변경된 파일만 나열
git diff main...feature/auth --name-only
# 통계 뷰 (파일 + 삽입/삭제)
git diff main...feature/auth --stat점 두 개 vs 점 세 개가 중요합니다. 점 두 개는 두 브랜치의 팁 사이의 차이를 보여줍니다. 점 세 개는 오른쪽이 왼쪽에서 분기된 이후 무엇이 변경되었는지 보여줍니다. 피처 브랜치를 리뷰할 때는 보통 점 세 개가 원하는 것입니다.
비추적 파일 정리#
# 무엇이 삭제될지 보기 (드라이 런)
git clean -n
# 비추적 파일 삭제
git clean -f
# 비추적 파일과 디렉토리 삭제
git clean -fd
# 비추적 파일과 무시된 파일 삭제 (핵 옵션)
git clean -fdx항상 -n으로 먼저 실행하세요. git clean -fdx는 node_modules, .env, 빌드 아티팩트 — Git이 추적하지 않는 모든 것을 삭제합니다. 진정한 새 시작에 유용하지만 파괴적입니다.
다른 브랜치에서 단일 파일 복원#
# 브랜치를 전환하지 않고 main 브랜치 버전의 파일 가져오기
git restore --source main -- src/config/database.ts또는 특정 커밋에서:
git restore --source a1b2c3d -- src/config/database.ts이것은 이전 git checkout main -- path/to/file 구문보다 깔끔하며, HEAD에 영향을 주지 않습니다.
모든 것 종합하기: 실제 워크플로우#
이 도구들로 전형적인 하루가 어떻게 보이는지:
# 아침: 무슨 일이 일어나고 있는지 확인
git lg
git fetch --all
# 피처 시작
git checkout -b feature/session-refresh main
# 작업, 점진적으로 커밋
git add -p # 전체 파일이 아닌 특정 헝크를 스테이지
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"
# 중단: 프로덕션 버그를 수정해야 함
git worktree add ../hotfix main
cd ../hotfix
# ... 수정, 커밋, 푸시, PR 병합 ...
cd ../my-project
git worktree remove ../hotfix
# 피처 작업으로 복귀
git commit -m "Fix edge case in token validation"
# PR 준비: 히스토리 정리
git rebase -i main
# 수정 커밋을 squash, 명확하게 reword
# 푸시하고 PR 열기
git push -u origin feature/session-refresh
# 스테이징에서 뭔가 망가졌다? 어떤 커밋인지 찾기:
git bisect start
git bisect bad HEAD
git bisect good main
git bisect run npm test
# 이런, 잘못된 것을 하드 리셋함
git reflog
git reset --hard HEAD@{2}이 명령어 각각은 몇 초가 걸립니다. 합치면 시간을 절약합니다. 가상의 시간이 아니라 — 매주 Git 엉킴을 풀거나, 수동으로 버그를 찾거나, 컨텍스트 전환으로 작업을 잃는 데 사용했을 실제 시간입니다.
Git은 깊이에 보상하는 도구입니다. 기본은 하루를 보낼 수 있게 해줍니다. 하지만 이 글의 명령어들이 "Git을 사용한다"와 "Git이 실제로 나를 더 빠르게 만든다"를 구분합니다. 점진적으로 배우세요. 이번 주에 새로운 기법 하나를 골라 근육 기억이 될 때까지 사용하세요. 그런 다음 다른 하나를 고르세요.
밤 11시에 프로덕션 버그를 바라보고 있을 미래의 자신이 git bisect를 안다는 것에 감사할 것입니다.