[Git] git checkout 과 detached HEAD

유진·2021년 2월 25일
1
post-custom-banner

git 특정 파일만 pull 하는 방법

페어 프로그래밍을 하다가 특정 파일만 pull 해야하는 상황이 생겨서 구글링을 하던 중 다음과 같은 방식을 발견했다.

$ git fetch <remote> <branch>
$ git checkout <remote/branch> -- <filepath>

출처: [git] remote 에서 특정 파일만 pull 하기

그런데 $ git checkout <remote/branch> -- <filepath> 이 부분이 이해가 안됐다. 'checkout은 브랜치를 전환하는 명령어인데 리모트 브랜치의 파일을 가져오는게 가능한가?'라고 생각했다.

그래서 헬프 데스크에 다음과 같이 질문을 올렸다.

https://imgur.com/tPWncQh

그리고 이런 답변을 받았다.

https://imgur.com/Ed7DPhy

먼저 checkout 부터 공부해보자.

git checkout

https://www.atlassian.com/git/tutorials/using-branches/git-checkout 위주로 정리했습니다.

Git 용어로 "checkout"은 대상 개체가 가지고 있는 버전들에서 원하는 버전으로 전환하는 것이다. git checkout 명령은 세개의 개체 파일, 커밋, 브랜치에 대한 동작이다.

브랜치를 checkout 한다는 것

브랜치 checkout 은 작업 디렉토리(working directory)에 있는 파일들을 브랜치에 저장된 버전으로 업데이트하고, 깃에게 이 브랜치에 있는 모든 새로운 커밋들을 기록해 달라고 말하는 작업이다.

브랜치는 전통적인 SVN(Subversion) 워크플로우에 극적인 변화를 가져왔다. 각각의 기능별로 브랜치를 가지게 됨으로써 기존 기능을 파괴할 걱정 없이 새로운 실험을 시도할 수 있고, 각각의 관련없는 기능들을 동시에 개발할 수 있게 되었다. 게다가, 브랜치는 여러 협업 워크플로우를 지원한다.

git checkout 명령어는 때때로 git clone과 혼동될 수 있다. git clone은 리모트 저장소에서 코드를 가져오고(fetch code from remote), 반면 git checkout은 이미 로컬에 있는 코드의 버전을 전환한다(switch version of code on local).

기존에 가지고 있는 브랜치로 전환하기

당신이 작업하고 있는 레포가 어떤 브랜치를 가지고 있다고 가정하자. 당신은 git checkout을 사용해 브랜치를 전환할 수 있다. git branch 명령어로 어떤 브랜치가 사용 가능하고 현재 어떤 브랜치에서 작업중인지 확인할 수 있다.

# 브랜치 확인
$ git branch 
master 
another_branch 
feature_inprogress_branch 

# 브랜치 전환
$ git checkout feature_inprogress_branch

새로운 브랜치를 만들어 전환하기

git checkoutgit branch와 손발이 척척 맞는 사이이다. git branch <new_branch>로 새로운 브랜치를 만드는 것처럼, git checkout-b 옵션으로 브랜치를 새로 추가할 수 있다.

# 현재 브랜치에서 feature1이라는 브랜치를 만든다
$ git branch feature1

# 현재 브랜치에서 feature1이라는 브랜치를 만들고 해당 브랜치로 전환
$ git checkout -b feature1

리모트 브랜치를 checkout 하기

팀으로 일을 할 때는 리모트 레포지토리를 사용하는 것이 일반적이다. 리모트 레포지토리도 로컬 레포지토리처럼 브랜치를 가지고 있다. 리모트 브랜치를 checkout하기 위해서는 먼저 해당 브랜치의 내용물을 가져와야 한다.

# 리모트 레포의 내용을 가져옴
$ git fetch --all

Git의 최근 버전에서는 리모트 브랜치를 로컬 브랜치처럼 checkout 할 수 있다.

# 리모트 레포의 브랜치를 따라가는 로컬 레포를 만들고, 해당 브랜치로 전환
$ git checkout <remote_branch>

새로운 로컬 브랜치를 만들고 리모트 브랜치의 마지막 커밋으로 초기화 할 수도 있다.

$ git checkout -b git reset --hard <remote_repo>/<remote_branch>

Git의 옛날 버전에서는 remote에 기반한 새 브랜치를 만들어야 한다.

# 리모트 레포의 브랜치를 가져와 로컬에 detached HEAD 상태의 브랜치 생성
$ git checkout <remote_repo>/<remote_branch>
# detached HEAD 브랜치의 내용을 가져와 새로운 로컬 브랜치로 만들어준다
$ git swtich -c <local_branch>

깃의 옛날 버전에서는 detached HEAD 상태의 브랜치를 생성한다고 한다. 그런데, detached HEAD 상태가 뭐지?

Detached HEAD

먼저 HEAD는 Git이 현재 스냅샷을 참조하는 방법이다. 사실 git checkout 명령은 HEAD로 하여금 특정 브랜치나 커밋을 가리키도록 업데이트 하는 것 뿐이다. HEAD가 브랜치를 가리킬 때는 문제가 없다. HEAD는 브랜치를 가리키고, 브랜치는 최신 커밋을 가리키니까.

https://imgur.com/u9Az2gn

그러나 당신이 커밋을 직접 check out 하면 'detached HEAD' 상태가 된다. 커밋을 직접 check out 한다는 것은 브랜치의 특정 커밋을 가리킨다는 것이다.

아래의 그림에서는 HEAD가 브랜치가 아닌 커밋 b를 가리키고 있다.

https://imgur.com/4mD7hg2

우리의 작업 공간이 커밋 b이기 때문에, 기존 브랜치에 commit할 수가 없다. 이미 커밋 c가 있기 때문이다.

그래서 Git은 HEAD가 특정 커밋을 가리키는 이 상태를 detaced HEAD(분리된 머리) 상태라고 명명했다.

detached HEAD 상태에서 작업은 할 수 있다. 커밋도 할 수 있다. 다만 그것을 다른 작업과 merge하거나 리모트에 pull 할 수는 없다. detached HEAD 상태에서 작업한 내용물들은 어느 브랜치에도 속해있지 않기 때문이다. 따라서 detached HEAD 상태에서 다른 브랜치로 checkout 하면 다시 detached HEAD 상태 때 했던 작업으로 돌아갈 수 없다. (detached HEAD 일 때 했던 커밋 ID를 알면 돌아갈 수는 있긴 하지만 어렵다.)

만약 특정 커밋에서 했던 작업을 살리고 싶다면 어떻게 해야 할까? 간단하다. detached HEAD 상태일 때 했던 작업을 브랜치로 만들어주는 것이다.

$ git checkout -b <new branch name>

이렇게 하면 detached HEAD 상태일 때 했던 작업과 커밋을 살릴 수 있다.

이제 다시 문제를 보자

$ git fetch <remote> <branch>
$ git checkout <remote/branch> -- <filepath>

먼저 fetch로 해당 리모트 브랜치의 내용을 가져온다.

$ git checkout <remote/branch> -- <filepath>는 현재 HEAD가 가리키고 있는 로컬 브랜치에서 특정 파일을 해당 리모트 브랜치의 내용물로 바꿔준다. (로컬 브랜치는 바뀌지 않음)

--는 뒤에 오는 이름이 파일이름이라고 명시하는 것이다. 파일이름과 브랜치명이 같을 때 --를 붙이지 않으면 git이 파일 이름을 브랜치로 잘못 해석할 수도 있다.

참고

profile
제가 또 기가막힌 한 줌의 트러플 소금 같은 존재그등요
post-custom-banner

0개의 댓글