RicoCheesethe studio log · v2.0
Live · KRRead posts
목록으로
뉴스PUBLISHED · 2026년 8월 26일·22 MIN READ

Git 중급 명령어 10가지 — 진작 알았으면 좋았을 것들

merge와 rebase의 차이부터 reflog로 날린 브랜치를 되살리는 방법까지, 실무에서 실제로 쓰이는 Git 중급 명령어 10가지를 정리했다.

#git#productivity#beginners#programming
10 Git Commands You'll Wish You Knew Earlier

개요 #

add, commit, pull, push는 눈 감고도 친다. 그런데 리베이스 도중 충돌이 세 번 연속 터지거나, git reset --hard로 이틀치 작업을 날렸을 때는 손이 멈춘다. 개발자 Sylwia Laskowska가 주니어 시절 테크리드에게 배웠고 지금은 자기 팀 주니어·미들 개발자에게 가르치고 있는 Git 명령어 10가지를 정리해 공개했다.

기초와 고급 사이, 딱 중급에 해당하는 것들이다. 정작 회사에서는 필요한데 아무도 제대로 알려주지는 않는 영역이기도 하다.

1·2. git merge vs git rebase #

두 명령을 한 항목으로 묶은 이유가 있다. 경력이 꽤 있는 개발자들도 이 둘을 거의 같은 것으로 취급하는 경우가 많기 때문이다.

큰 목적은 비슷하다. 한 브랜치의 히스토리를 다른 브랜치에 통합하는 것. 보통은 main이나 develop의 변경사항을 내 기능 브랜치로 가져올 때 쓴다. 하지만 처리 방식은 꽤 다르다.

이런 상황을 가정해보자.

untitled
text
A---B---C main
     \
      D---E feature

내가 feature에서 작업하는 동안 누군가 main에 커밋 C를 추가했다.

merge #

feature에 있는 상태에서 이렇게 실행한다.

untitled
bash
git merge main

Git이 양쪽 히스토리를 합쳐서 이런 결과를 만든다.

untitled
text
A---B---C------M
     \        /
      D---E---

M이 머지 커밋이다. 원래 히스토리는 그대로 남는다. 브랜치가 언제 갈라졌고, 각자 얼마나 진행됐고, 언제 다시 합쳐졌는지 Git이 전부 기억한다. 실제로 일어난 일을 보존한다는 점에서 장점이다. 단점은 main을 기능 브랜치에 반복해서 머지하다 보면 히스토리가 스파게티처럼 얽힌다는 것.

rebase #

대신 이렇게 하면?

untitled
bash
git rebase main

Git이 커밋 DE를 잠시 떼어내고, 브랜치를 최신 main 위로 옮긴 다음, 커밋을 다시 얹는다.

untitled
text
A---B---C---D'---E'

훨씬 깔끔하다. 다만 작은따옴표를 눈여겨보자. D'E'는 엄밀히 말해 D, E와 같은 커밋이 아니다. Git이 새 해시를 가진 새 커밋을 만든 것이다. 리베이스는 히스토리를 다시 쓴다.

그래서 이런 경험칙이 유용하다.

내 작업은 리베이스하라. 공유된 히스토리는 조심하라.

혼자 쓰는 기능 브랜치라면 리베이스해도 대체로 문제없다. 반면 다른 개발자 셋이 이미 내 커밋 위에서 작업 중인데 그 커밋들을 리베이스하면, 모두의 하루가 상당히 흥미진진해진다. 좋은 의미로는 아니다.

충돌은 어떻게 되나 #

머지든 리베이스든 충돌은 난다. 머지는 두 히스토리를 한 번에 합치려고 시도한다. 충돌 파일을 해결하고 스테이징한 뒤 이어가면 된다.

untitled
bash
git add .
git merge --continue

리베이스는 조금 더 성가시다. 커밋을 하나씩 다시 적용하기 때문이다.

untitled
bash
git add .
git rebase --continue

충돌 하나를 해결하고 계속했는데 다음 커밋에서 또 충돌이 나고, 또 나고, 또 난다. 그쯤 되면 여기까지 오게 된 모든 커리어 선택을 되짚어보게 된다.

작은 보너스도 있다. 완전히 꼬여서 이 서커스를 그냥 중단하고 싶다면 이렇게 한다.

untitled
bash
git merge --abort

또는

untitled
bash
git rebase --abort

Git이 시작 전 상태로 되돌려준다.

3. git commit --amend #

방금 예쁘고 동그란 커밋 하나를 만들었다. 그런데 코드를 다시 보니 뭔가 빠뜨렸다. 저자의 경우 대개 이런 거였다고 한다.

untitled
javascript
console.log("WTF");

선택지는 둘이다. 하나는 이런 위대한 커밋을 하나 더 만드는 것.

untitled
text
Remove console log

Git 히스토리 미학의 정점이라고 보기는 어렵다. 다른 방법은 빠뜨린 변경을 이전 커밋에 그냥 얹는 것이다.

untitled
bash
git add forgotten-file.ts
git commit --amend --no-edit

--no-edit은 "이 변경을 이전 커밋에 추가하되 커밋 메시지는 그대로 두라"는 뜻이다. 메시지도 바꾸고 싶으면 --no-edit 없이 실행하면 편집기가 열린다.

여기서 중요한 지점이 하나 있다. amend는 기존 커밋을 말 그대로 수정하는 게 아니다. 갱신된 내용을 담은 새 커밋을 만든다. 커밋 해시는 내용과 메타데이터에서 나오므로 해시가 바뀐다.

아직 푸시하지 않았다면 문제없다. 하지만 브랜치가 이미 원격에 있다면 로컬 히스토리가 원격과 어긋난다. 푸시하면 이런 걸 보게 된다.

untitled
text
rejected (non-fast-forward)

그 브랜치에 아무도 손대지 않는다고 확신한다면 git push --force를 쓸 수도 있다. 그런데 확신이 없다면?

4. git push --force-with-lease #

리베이스, amend, 인터랙티브 리베이스, 스쿼시 등 커밋을 다시 쓰는 작업을 하고 나면 로컬 히스토리가 원격과 달라진다. git push --force로 해결할 수 있긴 하다. 하지만 --force는 결국 이런 선언이다.

내 로컬이 진실이다. 원격에 뭐가 있든 이걸로 덮어라.

그사이 누군가 그 브랜치에 푸시했다면 남의 작업을 덮어버린다. 화낼 만하다.

훨씬 안전한 선택지가 있다.

untitled
bash
git push --force-with-lease

단순하게 말하면 "내 버전으로 강제 푸시하되, 원격 브랜치가 내가 아는 그 모습 그대로일 때만"이라는 뜻이다. 내 Git이 파악한 시점 이후로 원격이 바뀌었다면 — 누군가 새 커밋을 올렸다면 — Git이 덮어쓰는 대신 푸시를 거부한다. 남의 오후를 망치는 대신 에러 메시지를 받는 셈이다.

가능하면 --force보다 --force-with-lease를 쓰자.

5. git rebase -i #

저자가 "거의 Git의 왕"이라고 부르는 명령이다. 인터랙티브 리베이스는 최근 커밋에 대해 거의 뭐든 할 수 있게 해준다.

한 줄씩 커밋하고, 열 번쯤 갈아엎고, 이런 커밋들을 만들어도 된다는 뜻이다.

untitled
text
Add validation
Fix validation
Actually fix validation
Fix validation again
Remove console.log
Please work now

그리고 나중에 이 난장판을 우아한 히스토리로 바꾸면 된다. 최근 커밋 다섯 개를 손보려면 이렇게 실행한다.

untitled
bash
git rebase -i HEAD~5

편집기가 열리고 이런 화면이 나온다.

untitled
text
pick a111111 Add login form
pick b222222 Add validation
pick c333333 Fix typo
pick d444444 Fix validation
pick e555555 Remove debug log

이제 pick을 다른 명령으로 바꾸면 된다.

pick #

커밋을 그대로 둔다.

reword #

내용은 그대로, 메시지만 바꾼다.

untitled
text
reword a111111 Add beautiful login form

edit #

해당 커밋에서 리베이스를 멈추고 수정할 기회를 준다. 오래된 커밋의 실제 내용을 바꿔야 할 때 쓴다.

squash #

앞 커밋과 합친다.

untitled
text
pick   a111111 Add login form
squash b222222 Add validation
squash c333333 Fix validation

세 커밋이 하나로 합쳐지고, 최종 메시지를 편집할 수 있다. 이런 히스토리가

untitled
text
Add login form
Add validation
Fix validation

이렇게 정리된다.

untitled
text
Add login form with validation

fixup #

squash와 비슷하지만 합쳐지는 커밋의 메시지를 버린다.

untitled
text
pick  a111111 Add login form
fixup b222222 Fix typo
fixup c333333 Remove console.log

Remove console.log라는 메시지의 역사적 가치를 보존할 이유는 아마 없을 것이다. 이럴 때 fixup이 딱이다.

drop #

커밋을 통째로 없앤다.

untitled
text
drop c333333 Terrible idea

커밋 정리를 어디까지 해야 하는지에 대해서는 학파가 갈린다. 잘 정리된 히스토리로 리뷰를 요청하는 게 기본 예의라는 시니어가 있는가 하면, GitHub이나 GitLab이 PR 머지할 때 어차피 스쿼시해주니 불필요하다는 쪽도 있다.

그런데 Squash and merge를 누르고 싶지 않다면? 기능이 논리적으로 커밋 두 개로 나뉘고 둘 다 남기고 싶다면? 그런 경우가 있다. 일주일에 한 번을 쓰든 반년에 한 번을 쓰든, 이 왕은 알아둘 값어치가 있다.

6. git stash #

기능 하나를 기분 좋게 작업 중이다. 요즘 같으면 Kiro나 Claude Code가 열심히 일하는 걸 지켜보는 중이겠고. 그때 프로덕션 버그가 터진다.

지금 작업은 완전히 미완성이다. 디버그 로그가 사방에 널려 있고, 파일 절반이 수정됐고, 빌드도 안 된다. 이 상태를 커밋하고 싶지는 않다.

untitled
bash
git stash

Git이 커밋하지 않은 변경을 임시로 보관하고 작업 디렉터리를 깨끗한 상태로 되돌린다. 이제 브랜치를 옮겨 불부터 끄고 돌아오면 된다.

untitled
bash
git switch main

중요한 디테일 하나. 기본적으로 git stash는 추적 중인 파일만 보관한다. 새로 만든 untracked 파일은 포함하지 않는다. 그것까지 담으려면 -u를 붙인다.

untitled
bash
git stash -u

더 나은 방법은 이름을 붙이는 것이다.

untitled
bash
git stash push -u -m "WIP login feature"

전부 "WIP"라는 이름의 스태시가 여러 개 쌓이면 미래의 내가 지금의 나를 원망하게 된다.

git stash list #

스태시 목록을 본다.

untitled
bash
git stash list
untitled
text
stash@{0}: On feature/login: WIP login feature
stash@{1}: On feature/cart: experiment

git stash show #

특정 스태시의 내용을 확인한다. 전체 diff를 보려면 -p를 붙인다.

untitled
bash
git stash show -p stash@{0}

applypop #

untitled
bash
git stash apply stash@{0}

변경사항이 복원되지만 스태시는 목록에 남는다. 반면 pop은 적용하고 성공하면 목록에서 지운다.

untitled
text
apply = 복원
pop   = 복원 + 스태시 제거

7. git cherry-pick #

저자가 중급 명령어 중 가장 자주 쓴다고 밝힌 것이다. 다른 브랜치에 있는 특정 커밋 하나만 지금 브랜치로 가져오고 싶을 때 쓴다. 브랜치 전체를 머지하기도 싫고 리베이스하기도 싫다. 딱 하나만 필요하다. 저자는 최근 프로젝트에서 새로 만든 환경 설정이 담긴 커밋 하나가 필요했다고 한다. 딱 맞는 사례다.

untitled
bash
git cherry-pick a1b2c3d

Git이 그 커밋이 만든 변경을 현재 브랜치에 적용한다. 여기서도 새 커밋이 생긴다. 내용은 사실상 같지만 해시는 새로 붙는다.

untitled
text
main
A---B---C

feature
     \
      D---E---F

main에 있으면서 E만 가져오고 싶다면 결과는 이렇게 된다.

untitled
text
A---B---C---E'

저자는 좀 덜 고상한 용도로도 쓴다고 고백한다. 브랜치가 완전히 꼬였을 때 — 리베이스가 잘못되고, 히스토리가 수상해지고, 충돌이 번식하기 시작할 때 — 올바른 지점에서 새 브랜치를 만든 다음, 앞선 명령어들 덕에 이미 동그랗게 정리된 커밋들을 하나씩 체리픽해 옮긴다는 것이다. Git 고수들이 고개를 저을 만한 방식이지만, 본인 말로는 "잘 되니까 됐다."

8. git reset --soft, --mixed, --hard #

히스토리를 되돌리는 세 가지 방법이다. 각각 "그럼 내 변경사항은 어떻게 되나"라는 질문에 다르게 답한다.

reset을 이해하는 가장 쉬운 방법은 세 층을 떠올리는 것이다.

untitled
text
커밋 히스토리
스테이징 영역
작업 디렉터리

뒤로 갈수록 파괴력이 세지고, 잘못 고르면 심장이 한 번씩 내려앉는다.

--soft #

untitled
bash
git reset --soft HEAD~1

HEAD를 한 커밋 뒤로 옮기지만, 지워진 커밋의 변경사항은 전부 스테이징된 채로 남는다. 히스토리가 A---B였다면 브랜치는 A를 가리키고, B가 만든 변경은 여전히 커밋 준비 상태다. 너무 일찍 커밋했고 다시 만들고 싶을 때 쓴다.

--mixed #

untitled
bash
git reset HEAD~1

--mixed가 기본값이라 생략해도 된다. 한 커밋 뒤로 가는 건 같지만 변경사항이 작업 디렉터리에 언스테이지 상태로 남는다. 파일에서 사라지지는 않고, 커밋하려면 git add를 다시 해야 한다.

--hard #

untitled
bash
git reset --hard HEAD~1

여기서부터 위험 구간이다. HEAD를 되돌리면서 스테이징 영역과 작업 디렉터리까지 그 커밋 상태로 맞춘다. 변경사항이 파일에서도 사라진다.

untitled
text
--soft  → 변경을 스테이징 상태로 유지
--mixed → 변경을 언스테이지 상태로 유지
--hard  → 작업 트리에서 변경을 버림

마지막 것은 무슨 일이 벌어지는지 알고 쓰자. 그런데 이미 절대 지우면 안 될 걸 지워버렸다면?

9. git reflog #

저자가 아주 가끔 쓰지만 그때마다 목숨을 구해준 명령이라고 말한다. 예를 들어 git reset --hard를 실행했다가 마지막 변경이 아니라 이틀간 작업한 브랜치를 통째로 날렸을 때 같은 경우다.

핵심은 이거다. git log는 지금 보고 있는 히스토리에서 도달 가능한 커밋만 보여준다. 브랜치를 뒤로 되돌리면 일부 커밋은 git log에서 사라진다. 하지만 Git이 그것들을 즉시 삭제했다는 뜻은 아니다. Git은 HEAD 같은 참조가 어디로 움직였는지도 로컬에 기록해둔다.

untitled
bash
git reflog

이런 화면이 나온다.

untitled
text
e35fa12 HEAD@{0}: reset: moving to HEAD~2
821cd77 HEAD@{1}: commit: Add authentication
f992ab1 HEAD@{2}: commit: Add login page

사라진 커밋이 저기 있다. 브랜치를 그 지점으로 되돌릴 수도 있다.

untitled
bash
git reset --hard 821cd77

다만 저자는 이미 패닉 복구 모드에 들어간 상황이라면 더 안전한 쪽을 먼저 택한다고 한다.

untitled
bash
git branch rescue 821cd77

커밋이 다시 브랜치에서 도달 가능해지므로, 다른 걸 건드리지 않고 침착하게 무슨 일이 있었는지 확인할 수 있다.

git log가 눈에 보이는 커밋 히스토리라면, git reflog는 로컬 참조가 — 특히 HEAD가 — 최근에 어디를 가리켰는지를 보여준다.

한계도 분명하다. reflog는 내가 타이핑한 모든 글자를 백업해주는 마법이 아니다. 커밋하지도, 스태시하지도, 다른 방식으로 Git 오브젝트로 저장하지도 않은 변경은 reflog로 되살릴 수 없다. 여섯 시간 동안 커밋 없이 작업하다 날렸다면, 다음부터는 자주 커밋하게 되는 계기 정도로 삼자.

10. git revert #

커밋을 프로덕션에 배포했다. 조금 덜 하드코어한 버전이라면 공유 develop 브랜치에 올렸다. 그리고 뭔가 크게 잘못됐다. 전부 터졌다.

이제 프로덕션 브랜치에서 git reset --hard를 실행할 것인가? 퇴마 의식이라도 할 것인가?

다행히 아니다. 저장소 히스토리에 기록을 남기면서 커밋을 우아하게 되돌리는 방법이 있다.

untitled
bash
git revert <commit-hash>

히스토리가 A---B---C이고 C가 참사를 일으켰다고 하자. git revert C를 실행하면 Git은 C지우지 않는다. 대신 반대 변경을 적용하는 새 커밋을 만든다.

untitled
text
A---B---C---D

결과는 이런 모습이 된다.

untitled
text
C: Add new payment logic
D: Revert "Add new payment logic"

공유 브랜치에서 특히 유용하다. 공개된 히스토리를 다시 쓰지 않기 때문이다. 원래 변경이 있었고, 문제를 일으켰고, 되돌렸다는 사실을 모두가 볼 수 있다. 브랜치를 리셋하고 강제 푸시로 되돌리는 방식과 비교해보면 차이가 분명하다. 그쪽은 히스토리를 다시 쓰기 때문에 같은 브랜치에서 일하는 모두에게 불똥이 튄다.

일반 원칙은 이렇다.

untitled
text
공유 브랜치 + 잘못된 커밋 → revert

이 편이 reset + 강제 푸시보다 대체로 훨씬 안전하다.


저자는 글 말미에 이 수준의 다른 유용한 명령어가 있다면 댓글로 공유해달라고 덧붙였다. 자기 글 아래 달리는 댓글이 글 본문보다 나은 경우가 많고 그게 늘 즐겁다는 이유에서다.


이 글은 위 출처를 바탕으로 한국 독자를 위해 재작성한 기사입니다. 원문의 사실과 수치에 근거하며, 별도의 견해를 포함하지 않습니다.