blog

git checkout 대신 git switch와 git restore를 써야 하는 이유

Front-end / Git#Git#switch#restore#checkout#detached HEAD

브랜치를 옮길 때 습관처럼 git checkout을 쓰다가 git switch를 보고 "뭐가 다르지?" 싶었던 적이 있을 것이다. 브랜치를 바꾸는 동작 자체는 같다. 다만 git switch는 git checkout이 하던 여러 일 중 브랜치 전환만 떼어 낸 명령이고, 그렇게 나눈 데에는 이유가 있다.

checkout은 두 가지 일을 한다

  1. 브랜치 전환: 작업 트리와 HEAD를 다른 브랜치로 옮긴다.
  2. 파일 되돌리기: 작업 트리의 파일을 인덱스나 특정 커밋의 내용으로 덮어쓴다.
git checkout develop       # 1. develop으로 전환
git checkout -- src/a.ts   # 2. src/a.ts의 수정 사항을 버림

첫 번째는 언제든 되돌릴 수 있다. 두 번째는 커밋하지 않은 변경을 경고 없이 지우므로 되돌릴 방법이 없다. 위험도가 전혀 다른 두 작업이 같은 이름을 쓰는 것이 문제의 출발점이다.

어떤 실수가 생기나

git checkout <이름>은 그 이름이 브랜치면 브랜치를 바꾸고, 브랜치가 아니면서 파일 경로와 일치하면 그 경로의 변경을 버린다.

# 'src'라는 브랜치로 가려 했지만 그런 브랜치는 없다
git checkout src
# Updated 1 path from the index
# → src 디렉터리 아래 커밋하지 않은 변경이 전부 사라짐

브랜치 이름을 잘못 썼을 뿐인데 Git은 파일 되돌리기로 해석한다. 출력은 "경로를 갱신했다"는 한 줄뿐이고 확인 절차도 없다.

Git 2.23에서 나뉜 명령

Git 2.23(2019년 8월)은 checkout의 역할을 두 명령으로 나눴다.

하는 일기존 명령새 명령
브랜치 전환git checkout developgit switch develop
브랜치 생성 후 전환git checkout -b feature/xgit switch -c feature/x
직전 브랜치로git checkout -git switch -
작업 트리 파일 되돌리기git checkout -- src/a.tsgit restore src/a.ts
스테이징 취소git reset HEAD src/a.tsgit restore --staged src/a.ts
특정 커밋의 파일 가져오기git checkout HEAD~1 -- src/a.tsgit restore --source=HEAD~1 src/a.ts
  • git switch는 브랜치만 다룬다. 인자가 브랜치가 아니면 파일로 해석하지 않고 에러를 낸다.
  • git restore는 파일만 다룬다.
git switch src
# fatal: invalid reference: src

명령 이름이 의도를 그대로 드러내므로 리뷰나 문서에서 읽는 사람도 무슨 일이 일어날지 바로 안다.

또 하나의 차이: detached HEAD

커밋이나 태그로 이동하면 HEAD가 어떤 브랜치에도 붙지 않은 detached HEAD 상태가 된다. 이 상태에서 만든 커밋은 브랜치를 따로 만들지 않으면 나중에 찾기 어렵다.

git checkout v1.0.0          # 경고만 출력하고 바로 detached HEAD로 이동
git switch v1.0.0            # fatal: a branch is expected, got tag 'v1.0.0'
git switch --detach v1.0.0   # 의도를 명시해야 이동

git switch는 --detach로 의도를 밝혀야만 이동하므로, 실수로 detached HEAD에서 작업하는 일을 막아 준다.

그럼 checkout은 쓰면 안 되나

그렇지는 않다. git checkout은 하위 호환을 위해 남아 있고 계속 동작한다. 오래된 문서, 스크립트, CI 설정에는 여전히 많이 쓰이므로 읽을 줄은 알아야 한다. 다만 새로 명령을 입력하거나 문서를 쓸 때는 다음을 권장한다.

  • 브랜치를 옮길 때는 git switch
  • 파일을 되돌릴 때는 git restore

정리

  • git switch와 git checkout의 브랜치 전환 동작은 같다.
  • git checkout은 브랜치 전환과 파일 되돌리기를 한 명령으로 처리해서, 브랜치 이름을 잘못 쓰면 파일 변경이 경고 없이 사라질 수 있다.
  • Git 2.23부터 브랜치 전환은 git switch, 파일 되돌리기는 git restore로 나뉘었다.
  • git switch는 브랜치가 아닌 대상으로 이동하지 않고, detached HEAD로 가려면 --detach를 명시해야 한다.