이번에 맥북을 사고나니 너무 오래 붙들고 있어서 목 통증이 오는 거 같다.
그래서 이번에 거치대를 구매했다.

키보드나 마우스는 원래 쓰던 키보드가 블루투스를 지원해서 그대로 쓰는 데,
문제는 마우스다.

무선이긴 한데, USB가 A타입이다 ...
그래서 거치대에 아싸리 포트까지 사서 모니터 쓰던 모니터 연결까지 할 예정이다.

개발 혹은 편집 쪽에서 맥이 정말 1황이라는 말이 많아서
제대로 환경을 갖추기 시작한건데,
어째 배보다 배꼽이 더 큰 거 같다...

그래도 벌써 뭔가 적응이 됐는지
윈도우를 쓸 때도 한영키 바꾸려고 캡스락부터 누르고 본다.

이 또한 개발자로 한 걸음이 아니겠는가 !
그럼 오늘 포스팅도 Leshgo.


1. Git이 무서워지는 순간


Git이 무서워지는 순간이 무엇이 있을까.

  • 커밋을 잘못했을 때
  • push한 뒤 오타를 발견했을 때
  • 브랜치를 잘못 합쳤을 때
  • reset을 했는데 작업이 사라졌을 때

정도로 꼽을 수 있을 것 같다.
이 순간들의 공통점이 있는데,
바로 히스토리를 건드리는 순간이다.

Git은 파일 관리 도구처럼 보이지만
실제로는 커밋 히스토리 관리 도구다.

그래서 이번에는 "히스토리를 건드리는 행위들"에 대해 다룬다.


2. 작업을 멈추지 않고 잠시 치워두는 방법 - Stash


Git의 파일 형태

stash의 형태를 알아보기 전에 먼저 다뤄야 할 개념이 하나 있다.
바로 Git에서의 파일 상태인데 Git의 파일 형태는 크게 3가지로 나뉜다.

tracked 파일

  • 한 번이라도 git add 된 적 있는 파일
  • Git이 관리 중인 파일

modified 파일

  • 이미 tracked인데 수정된 상태

untracked 파일

  • Git이 아직 한 번도 추적되지 않은 새 파일
  • git status에 빨간색으로 뜨는 파일들

이 파일 상태 개념을 이해해야 stash 동작을 정확히 이해할 수 있다.

Stash의 필요성

실무 상황을 하나 생각해보자.

로그인 기능을 작업 중인데, 아직 완성은 안됐다.
근데 dev 브랜치로 급하게 이동해야 한다.

이때 선택지는 두 가지다:

  • 억지로 커밋하고 이동
  • 작업을 임시로 치워두고 이동

여기서 "의미 없는 커밋 남기기 싫을 때" 사용 하는 게 stash다.


Stash의 본질

stash는 커밋이 아니다.
히스토리에 기록되지 않는 임시 저장이다.

commit과 stash 이 둘을 비교해보자.

commit

  • 히스토리에 남는다.
  • 의미 있는 작업 단위
  • push 대상

stash

  • 히스토리에 남지 않는다.
  • 미완성 상태 임시 보관
  • push 대상 아님

그래서 Stash는
"미완성 작업을 잠깐 숨겨두는 기능"이다.


기본 사용 흐름

git stash					# 현재 작업 임시 저장
git stash list				# stash 목록 확인
git stash pop				# 가장 최근 stash 적용 + 삭제
git stash apply stash@{1}	# 특정 stash 적용 (삭제 안 함)

이 정도면 충분하다.
너무 많은 옵션은 오히려 해가 된다.


옵션 -u의 중요성

기본 git stash는 수정된 파일은 저장하고
새로 만든 파일(untracked)은 남겨둔다.

그래서 실무에서는
새 파일까지 포함해서 완전히 비워두기 위해
-u 옵션을 자주 쓴다.

git stash -u

Stash가 쓰이는 상황

  • 브랜치를 급하게 이동해야 할 때
  • 실험 코드를 잠깐 치울 때
  • pull 전에 작업 정리 할 때

Stash는 히스토리를 깨끗하게 유지하기 위한 도구이지,
기록을 대신하는 도구는 아니다.
의미 있는 작업 단위라면 commit으로 남기는 것이 원칙이다.


3. 이미 만든 커밋을 고치는 순간 - Amend


amend의 필요성

실무에서 자주 나오는 상황:

  • 커밋 메시지 오타
  • 파일 하나 빼먹음
  • 작은 수정인데 새 커밋 남기기 애매함

이 외에도 방금 한 커밋만 살짝 고치고 싶어질 때가 있다.
이때 등장하는 게 이제부터 다룰 --amend다.


amend의 본질

많이 오해하는 부분부터 정리하자면,

amend는 커밋을 수정하는 게 아니라,
새로운 커밋을 다시 만드는 것이다.

이것이 amend의 가장 중요한 특징이다.

stash와의 차이

stash

  • 히스토리 안 건드림
  • 안전
  • push와 무관

amend

  • 히스토리 다시 만듦
  • 조심해야 함
  • force와 연결될 수 있음

커밋 ID가 바뀌는 이유

Git 커밋은 단순 메시지가 아니다.

커밋 ID에는:

  • 파일 상태
  • 부모 커밋
  • 메시지
  • 작성 시간

이 정보가 모두 포함된다.

이 중 하나라도 바뀌면
완전히 다른 ID가 만들어진다.

그래서:

git commit --amend -m "수정된 메시지"

하면
"같은 커밋을 고친 것처럼 보이지만"
실제로는 새로운 커밋인 걸 알 수 있다.


amend 사용 방식 정리

메시지만 수정

git commit --amend -m "수정된 메시지"

파일을 추가로 포함

git add forgotten.js
git commit --amend

메시지는 그대로, 내용만 추가

git add fix.js
git commit --amend --no-edit

여기까지면 충분하다.
stash에서도 후에 서술했지만,
옵션은 더 깊게 들어가면 오히려 독이 된다.


조심해야 할 문제

이미 push한 상태라면?

원격에는 "옛날 커밋 ID"가 있다.
그런데 amend 하면
로컬에는 "새 커밋 ID"가 생긴다.

그럼 히스토리가 달라지면서 push가 거부된다.
원격 저장소는 기존 커밋 ID를 기준으로 기록을 유지하고 있기 때문이다.

이때 필요한 게:

git push --force

즉, amend는 force와 세트가 될 수 있다는 말이다.


amend를 쓰기 전 원칙

  • 개인 브랜치에서만 사용
  • push 전에 사용하는 것이 가장 안전
  • 공유 브랜치(main, dev)에서는 지양

4. 과거로 돌아가는 방법 - Reset과 Reflog


reset이 필요한 순간

실무에서 이런 상황이 생긴다.

  • 방금 한 커밋이 완전 잘못됐다.
  • 작업이 꼬여서 "그 커밋 시점"으로 그냥 돌아가고 싶다.
  • 로컬 변경사항을 전부 버리고 최신 커밋 상태로 맞추고 싶다.

이때 등장하는 명령어가 reset이고,
그 중에서도 가장 강력한 방식이 --hard다.


reset --hard의 의미

git reset --hard는 단순히 "되돌리기"가 아니다.
HEAD를 특정 커밋으로 이동시키고,
작업 디렉토리까지 그 커밋 상태로 강제로 맞춘다.

즉, 현재 작업 중이던 변경사항까지 포함해서
지정한 시점으로 돌아간다.


amend와 reset의 차이

amendreset은 둘 다 히스토리를 건드린다.
그래서 자주 헷갈리는데, 방향이 다르다.

amend

  • 마지막 커밋을 다시 만든다
  • 커밋 내용을 수정하는 데 사용
  • 커밋 ID가 바뀐다
  • 기본적으로 "최근 커밋 보정"에 가깝다

reset

  • HEAD를 과거 커밋으로 이동시킨다
  • 특정 시점으로 돌아가는 데 사용
  • --hard를 쓰면 작업 디렉토리까지 강제로 맞춘다
  • 히스토리 자체를 뒤로 되감는다.

reset --hard 사용 예시

직전 커밋으로 되돌리기

git reset --hard HEAD~1

HEAD~1은 "현재(HEAD)에서 한 단계 이전 커밋"을 의미한다.

특정 커밋으로 되돌리기

git reset --hard abc1234

커밋 ID(해시)를 지정하면 그 시점으로 돌아간다.

로컬 변경사항 모두 삭제하고 현재 커밋 상태로 맞추기

git reset --hard HEAD

이건 "커밋을 이동"시키는 게 아니라
작업 디렉토리를 현재 커밋 상태로 덮어씌우는 용도로 자주 쓴다.


reset --hard의 위험성

--hard는 강력하다.
변경사항이 즉시 사라진 것처럼 보인다.
그래서 실수하면 "끝난 것 같다"는 착각이 들 수 있다.

하지만 Git에는 마지막 안전장치가 있다.


실수했을 때의 복구 - reflog

reflog내가 HEAD를 어디로 이동시켰는지 기록으로 남긴다.

그래서 reset을 실수했을 때는:

git reflog

로 이전 상태를 찾고,

git reset --hard HEAD@{2}

처럼 특정 시점으로 다시 돌아올 수 있다.


5. 히스토리를 다시 쓰는 행위 - Rebase


rebase가 필요한 순간

실무에서 이런 상황이 생긴다.

  • feature 브랜치를 오래 작업했다.
  • 그 사이에 dev 브랜치가 많이 앞서갔다.
  • 내 브랜치를 최신 dev 위에 정리해서 올리고 싶다.

이때 쓰는 게 rebase다.


rebase의 본질

rebase는 병합이 아니라,
기존 커밋들을 새로운 기준 위에 다시 쌓는 것이다.

다시 말해 기존 커밋을 복사해서
다른 기준점(base) 위에 다시 만든다.
그래서 커밋 ID가 전부 바뀐다.


rebase vs merge 비교

rebase를 이해하려면 merge와의 차이를 정리해야 한다.

둘 다 브랜치를 합치는 방법이지만,
히스토리 모양이 완전히 다르다.

사실 merge를 소개할 때도 rebase가 단골 개념인 거 같다.

merge

git merge dev
  • 두 브랜치를 하나로 합친다
  • 병합 커밋이 생성된다
  • 히스토리가 가지처럼 남는다
  • 이미 공유된 브랜치에서 안전하다.

→ 기록을 그대로 유지하는 방식

rebase

git rebase dev
  • 내 커밋을 dev 최신 위로 다시 쌓는다
  • 병합 커밋이 생기지 않는다
  • 히스토리가 일자로 정리된다
  • 커밋 ID가 전부 바뀐다

→ 히스토리를 재작성하는 방식

merge는 "합친다"이고,
rebase는 "다시 쌓는다"다.

실무에서 쓰이는 방식

  • 공유 브랜치(main,dev) → merge
  • 개인 feature 브랜치 정리 → rebase

이렇게 구분하는 경우가 많다.


기본 사용 예시

최신 dev 위로 올리기

git checkout feature/login
git rebase dev

내 feature 브랜치의 커밋들을
dev 최신 커밋 위에 다시 쌓으라는 의미를 담는다.


충돌이 나면?

rebase 중 충돌이 나면

git add .
git rebase --continue

중단하고 싶으면:

git rebase --abort

Interactive Rebase - 실무에서 이렇게 쓴다.


언제 쓰는가

보통 이런 상황이다.

  • 커밋을 너무 잘게 쪼개서 올렸다
  • "fix", "수정", "다시 수정" 같은 커밋이 많다
  • PR 올리기 전에 커밋을 정리하고 싶다

이럴 때 사용하는 게:

git rebase -i HEAD~3

→ 최근 3개의 커밋을 편집 모드로 연다.


실무에서 가장 많이 쓰는 기능 3가지

  1. squash - 커밋 합치기

작은 수정 커밋들을 하나로 합친다.

pick a1b2c3 로그인 기능 구현
squash d4e5f6 콘솔 로그 제거
squash g7h8i9 오타 수정

→ 깔끔한 하나의 커밋

실무에서 가장 많이 쓰는 기능이다.


  1. reword - 커밋 메시지 수정
reword a1b2c3 로그인 기능 구현

→ 메시지를 다시 입력하게 된다.

PR 올리기 전에 메시지 정리할 때 자주 사용된다.


  1. drop - 커밋 삭제
drop d4e5f6 테스트 코드 임시 추가

→ 해당 커밋을 히스토리에서 제거한다.

단, 이미 push한 브랜치에서는 매우 조심해야 한다.


rebase의 위험성

rebase는 커밋을 "다시 만드는 것"이다.

그래서 이미 push한 브랜치에서 사용하면
커밋 ID가 전부 바뀌고
force push가 필요해진다.

즉, 팀 브랜치에서는 매우 조심해야 한다.


rebase의 원칙

  • push 전 개인 브랜치에서만 적극 사용
  • 공유 브랜치에서는 지양
  • rebase 후에는 force push가 필요할 수 있음

6. 원격 히스토리를 덮어쓰는 순간 - Force Push

force push가 필요한 순간

우리는 이미 이런 상황을 만들었다:

  • amend → 커밋 ID 변경
  • rebase → 여러 커밋 ID 변경
  • reset → 히스토리 이동

이런 작업을 하고 나면
로컬 히스토리가 원격과 달라진다.

그 상태에서 그냥 push 하면 거부된다.
왜냐하면 원격에는
"이전 커밋 ID 기준으로 기록이 남아 있기 때문이다."

이때 사용하는 게:

git push --force

force push의 본질

force push는 원격 히스토리를
내 로컬 기준으로 덮어쓰는 행위다.

즉, 원격에 있던 기록을 무시하고
지금 내 상태를 기준으로 다시 쓴다.

이게 위험한 이유로 자세한 건 바로 다음에 다루겠다.

force push의 위험성

공유 브랜치라면
다른 사람이 pull 받아간 커밋이
갑자기 사라질 수 있고,
충돌이 대량으로 발생할 수 있다.

그래서 force는 단순한 옵션이 아니라
협업 리스크다.

force push의 원칙

  • 개인 feature 브랜치 → 사용 가능
  • main / dev 같은 공유 브랜치 → 절대 지양
  • 팀 컨벤션 우선

7. 결국 Git이 관리하는 것

파일을 관리하는 도구

처음 Git을 배울 때는
"파일 변경을 기록하는 도구" 정도로 생각한다.

하지만 지금까지 다룬 기능들을 떠올려보자.

  • stash → 작업을 잠시 치워둔다
  • amend → 마지막 커밋을 다시 만든다
  • reset → HEAD를 과거로 이동시킨다
  • rebase → 커밋을 새로운 기준 위에 다시 쌓는다
  • force push → 원격 히스토리를 덮어쓴다

이 기능들의 공통점은 파일을 직접 수정하는 게 아니다.
커밋의 연결 구조를 다루는 것이다.

실제로 관리하는 것

Git은 파일 자체를 저장하는 시스템이 아니다.

Git이 관리하는 것은:

  • 특정 시점의 파일 상태(스냅샷)
  • 그 스냅샷들 사이의 연결 관계
  • 그리고 그 연결이 만들어내는 히스토리

즉, Git은 파일 관리 도구가 아니라
"히스토리 관리 시스템"에 가깝다.

그래서 겪는 위험성

reset, rebase, force push가 위험한 이유는

파일을 지워서가 아니라
히스토리 구조를 바꾸기 때문이다.

협업에서 중요한 건 파일 내용뿐 아니라
"누가, 언제, 어떤 흐름으로 작업했는가"이기 때문이다.

결론

여기까지 오면 보이는 게 있다.

Git을 잘 다룬다는 건
명령어를 많이 아는 것이 아니라
히스토리를 어떻게 유지할지 판단하는 것이다.

이 관점이 잘 잡히면
mergerebase를 선택하는 기준도 달라진다.


8. 핵심 정리

  • Stash → 히스토리를 건드리지 않는 임시 보관
  • Amend → 마지막 커밋을 다시 만드는 기능
  • Reset → HEAD를 과거 시점으로 이동
  • Reflog → HEAD 이동 기록을 통해 복구
  • Rebase → 커밋을 새로운 기준 위에 다시 쌓기
  • Force Push → 원격 히스토리를 로컬 기준으로 덮어쓰기

기능은 많지만, 어찌됐던 결론은 하나로 볼 수 있다.
Git은 파일을 수정하는 도구가 아니라
커밋 히스토리를 설계하고 관리하는 도구다.

결국 전 파트에서 언급했던 것과 이어지는데,
명령어를 많이 아는 것이 아니라,
언제 히스토리를 건드려도 되는지
혹은 언제 건드리면 안 되는지
공유된 기록을 어떻게 다뤄야 하는지
이러한 판단 기준을 가지는 것이 중요하게 작용할 것 같다.

롤백은 언제든 발생할 수 있다.
하지만 그 동작이 협업 구조에 어떤 영향을 미치는지는 알고 사용해야 한다.
Git은 되돌릴 수 있지만, 히스토리는 가볍게 다룰 수 있는 것이 아니다.


0개의 댓글