깃으로 버전 관리하기

우리는 깃에서 문서를 수정할 때마다 간단한 메모로 수정 내용을 적어두고 저장하는데, 이것을 버전이라고 한다.

 

이렇게 깃을 통해 버전을 관리하는 방법을 공부하기 위해 mkdir 명령어로 컴퓨터에 저장소를 만들어보자.

 

지금 상태에서는 git 의 기능을 사용할 수 없다.
여기에 $git init 명령을 입력해서 깃을 사용할 수 있도록 초기화시키자.

그럼 처음에 만들었던 디렉토리와는 다르게 .git 디렉토리가 생긴것을 볼 수 있다.

이게 무슨 의미냐면 이 디렉토리가 깃을 사용하면서 버전이 저장될 저장소(repository)라는 뜻이다.
(윈도우 탐색기에서 .git파일을 찾아보려고 해도 화면에 안보일텐데 사용자가 실수로 .git 디렉토리를 지우지 않도록 일부러 숨겨놓은 것이다.)

 

 

초기 세팅은 됐다. 이제 버전을 만들어보자.

드디어 버전인가.. 싶지만 딱히 어려운 개념은 아니고, 그냥 깃에서 버전이란 문서를 수정하고 저장할 때마다 생기는 것이다.

그런데 만약 이 수정을 1000번 이상 했다고 치자. 그럼 내가 언제 어떤 내용을 수정했는지 기억하기가 쉽지 않다.
그래서 우리는 버전 관리 시스템인 깃을 이용해서 언제 무엇을 변경했는지 파악할 수 있고, 그 버전으로 되돌아갈 수도 있다.

 

 

깃에는 버전을 관리하기 위해 세 가지 단계별로 나눠서 관리한다.

이런 관리 과정은 눈에 보이지 않기 때문에 처음부터 직관적으로 이해하기 어렵다.
이 세 가지 working directory, stage, repository 단계를 차근차근 알아보자.

 

working directory 는 파일의 수정, 저장 등의 작업을 한다. 방금 만들었던 hello-git 디렉토리가 working directory가 된다.

stage는 버전으로 만들 파일이 대기하는 곳이다. 예를 들어 working directory에서 파일을 10개 수정했는데 4개의 파일만 버전으로 만들고 싶다면, 이 4개의 파일만 stage로 넘겨주면 된다.

repository는 스테이지에서 대기하고 있던 파일들을 버전으로 저장하는 곳이다.
(스테이지의 내용은 .git/index 파일에 저장되고, 레포지토리의 내용은 .git/HEAD 파일에 저장된다.)

여기서 working directory 는 hello-git과 같이 눈에 보이지만, 스테이지랑 repository 는 눈에 보이지 않는다. 이것들은 .git 디렉토리 안에 숨은 파일 형태로 존재하는 영역이다.

 

예를 들어보자.
hello.txt 파일 문서를 working directory에서 수정하고 저장했다.
이 수정된 파일을 버전으로 만들고 싶다면 stage에 넣는다. 버전을 더 만들고 싶다면 stage에 더 추가한다.

stage에 수정된 파일들을 다 넣었다면 깃에게 commit 명령을 한다.
commit 명령을 하면 새로운 버전이 생성되면서 stage에 대기하던 파일이 모두 repository에 저장된다.

 

 

정말 이 3단계를 거치는지 working directory에서 vim으로 직접 문서를 수정해보자.

우선 아까 만들었던 hello-git 디렉토리로 이동하고 $git status를 입력해서 깃 상태를 확인해보자.

위의 출력된 정보를 통해 현재 master branch에 있고, 아직 커밋한 것이 없으며 커밋할 파일도 없다는 것을 알게되었다.

 

그럼 hello-git 디렉토리에 저번에 했던 방식대로 vim 편집기를 이용해서 새로운 파일을 만들어보자.
파일을 만들고 다시 status를 입력해보면

이번엔 untracked files이 있다고 출력 메세지가 바뀌었다..!

 

깃에서는 아직 한번도 버전을 관리하지 않은 파일을 untracked files이라고 부른다.

여기까지 우리가 한 것은 작업 디렉토리에 파일을 수정 및 저장한 것 밖에 없다.

 

 

이제 수정한 파일을 스테이징 해보자. 스테이징staging은 깃에게 버전 만들 준비를 하라고 알려주는 표현으로, 스테이지에 올린다고도 말한다.

깃에서 스테이징할 때 사용하는 명령어는 git add이다. 이후 다시 상태를 확인해보면?

untracked files라는 문구가 changes to be committed로 바뀌었다.

여기까지가 작업 디렉토리에 있는 수정된 버전의 파일을 스테이지로 복사한 것이다.

 

 

파일이 스테이지에 있다면 이제 버전을 만들 수 있다. 깃에서는 버전을 만드는 것을 간단하게 커밋한다고 말한다.

$git commit -m "message" 명령으로 커밋을 하고, -m 을 통해 커밋과 함께 저장할 메세지를 남길 수 있다.

저어~기 1 file changed, 1 insertion 메세지를 통해 스테이지에 있던 hello.txt파일이 저장소에 추가된 것을 확인할 수 있다.

 

 

버전이 제대로 만들어졌는지 확인하기 위해 git log 명령어를 사용해보면, 커밋을 만든 사람, 버전을 만든 시간, 수정과 관련된 메세지가 나타난다.

여기까지가 스테이지에 있던 hello.txt 파일의 버전을 repository에 저장한 과정이다.

 

 

스테이징과 커밋을 한번에 처리할 수도 있다. 버전으로 올린 것을 전부 저장할지 말지 고민하는 과정을 없앤 것이다.

기존의 commit 명령어에 -am 옵션을 붙여서 git add와 git commit을 한꺼번에 처리하자.
$ git commit -am "message2"

 

이렇게 문서들을 수정해봤는데, 우리는 버전 관리 프로그램을 배우는 사람으로서.. 어떤 부분이 변경된 것인지 확인할 수 있어야 한다.

이전에 $git log를 통해 누가 언제 커밋을 했는지는 알 수 있었지만, 그래서 파일의 어떤 부분이 변경된 것인지는 알 수 없었다. 물론 message 만으로 확인하는 것에는 한계가 있다.

이런 경우 git diff 명령을 사용해서
working repository에 있는 파일과 스테이지에 있는 파일을 비교하거나,
스테이지에 있는 파일과 저장소에 있는 최신 커밋을 비교해서 수정한 파일을 커밋하기 전에 최종적으로 검토할 수 있다.

 

비교를 위해서 hello.txt파일을 임의로 수정했다.

현재 상태를 보면 hello.txt 파일이 수정되었고, 아직 스테이징 상태가 아니라고 한다.

 

이제, 방금 수정한 hello.txt 파일이 저장소에 있는 최신 버전의 hello.txt와 어떻게 다른지 diff 명령을 사용해서 알아보자.

위의 빨간 부분에서 -2는 hello.txt 파일에서 2가 삭제되었다는 뜻이다.

+two 는 hello.txt 파일에 two가 추가되었다는 뜻이다.

 

 


 

 

깃에서는 각 단계마다 파일의 상태를 다르게 표시한다. 그래서 우리는 파일의 상태를 보고 이 파일이 버전 관리의 어떤 단계에 속해있는지 알 수 있다.

우리는 이미 파일의 상태를 본 적이 있다.
아까 git status를 했을 때, tracked와 untracked 상태를 볼 수 있었는데, 이 두 개의 상태가 무엇을 의미하는지 알아보자.

이미 만들었던 파일 hello.txt 말고, 새로운 파일 hello2.txt를 하나 더 만든 후 둘 다 수정하고 working repository에 있을 것을 상상하며 상태를 확인해보자.

앞에서 커밋했었던 hello.txt 파일은 Changes not staged for commit으로 아직 변경된 파일이 스테이지에 올라가지 않았다고 말한다. 그리고 hello앞에 modified라 되어있어서 수정됐다는 것을 알 수 있다.

이렇게 깃은 한 번이라도 커밋을 한 파일의 수정 여부를 추적하는데, 이러한 의미에서 tracked 파일이라고 부른다.

반면, 방금 새로 만들어서 아직 commit을 하지 않은 hello2 파일은 untracked라고 표시되는 것을 볼 수 있다.
한 번도 깃에서 버전 관리를 한 적이 없기 때문에 수정 내역을 추적하지 않기 때문이다.

 

그럼 git add를 통해 스테이지에 올린 후의 상태를 보자.

마지막 버전 이후에 수정된 hello는 modified로, 한 번도 버전을 관리하지 않았던 hello2는 new file로 표시된다. 기존의 tracked파일이나 untracked 파일 모두 스테이지에 올라온 것을 확인할 수 있다.

git add는 각각의 파일별로 따로 해줬지만, git commit 을 할 때는 한번에 다 포함된다. (너무 당연한가?)

 

그런데 git log 명령어를 사용해도 각 커밋에 어떤 파일들이 관련된 것인지 알 수 없다..

만약, 우리가 보려는 커밋에 관련된 파일까지 같이 보고 싶다면 git log 명령어에 --stat 옵션을 사용해서 관련 파일까지 전부 확인할 수 있다.
$git log --stat

더보기

로그 메세지가 너무 많을 경우 화면을 나눠서 볼 수도 있다.
엔터를 누르면 다음 로그 화면을, Q를 누르면 로그 화면에서 빠져나와 다시 깃 명령을 입력할 수 있다.

 

 


 

한 번이라도 버전을 만든 파일은 tracked 상태가 된다고 했었다. 이 상태의 파일은 현재 working repository에 있는지, 스테이지에 있는지 등 더 구체적인 상태를 알려준다.

만약 파일을 수정만 하고 상태를 보면, Changes not stage for commit이라는 메세지가 나타타서 파일이 현재 수정만 된 modified 상태임을 알려준다.

 

git add로 스테이지에 올리면 커밋할 사항이 있다고 알려준다. Changes to be committed

현재 상태는 staged 상태이다.

 

스테이지에 있는 파일을 커밋해보면

hello2.txt의 상태는 modified에서 다시 unmodified로 돌아간 것을 확인할 수 있다.

 

이것으로 전체적인 파일의 상태에 따라 어느 단계에 속해있는지 알아봤다.

더보기

commit 도중 커밋 메세지를 잘못 입력하는 경우가 있을 수 있다.
이 경우, $git commit --amend 명령을 이용해서 방금 커밋한 메세지를 수정할 수 있다.

 

 


 

 

지금까지 수정한 파일을 스테이지에 올리고 커밋하는 방법을 알아봤다.
이제 반대로 스테이지에 올렸던 파일을 내리거나 커밋을 취소하는 등 각 단계로 돌아가는 방법에 대해 알아보자.

 

working repository에서 수정한 파일 되돌리기

파일을 수정한 뒤 소스가 정상적으로 동작하지 않을 때, 수정한 내용을 취소하고 다시 최신 버전 상태로 되돌려야 하는 경우가 있다. 이럴 때 restore(checkout) 명령어를 사용하면 작업 트리에서 수정한 내용을 쉽게 취소할 수 있다. 하지만 되돌린 내용을 다시 복구할 수 없다는 점에 주의하자. (책에선 checkout 명령어를 예시로 보여줬었는데 지금 공부할 때의 git 버전이 바뀌어서 상위호환인 restore를 쓴다고 한다!! 밑에 status에서도 restore를 사용하라는 안내문이 출력된다..)

비록 방금 과정을 말하긴 했지만, 무심코 모든 단계에서 Ctrl + Z 마냥 남용할 수도 있겠다는 생각을 했다.
restore(checkout)은 파일을 수정만 하고 아직 stage 단계에 올리지 않았을 경우에 사용하는 방법이다.

안그래도 status에서 working directory의 변경 사항을 취소하려면 restore(checkout)을 사용하라고 알려주고 있다.

 

스테이징 되돌리기

이번에는 수정된 파일을 스테이징 했을 때, 스테이징을 취소하는 방법이다.
우선, 스테이징을 하기 위해 파일을 수정하고 git add로 파일을 스테이지로 올려보자.

이번에도 역시 상태 메세지에 unstage를 하려면 git restore --staged file을 사용하라고 적혀있다.

상태를 보면 unstaged된 것을 확인할 수 있다.

 

 

최신 커밋 되돌리기

이번에는 수정된 파일을 스테이징하고 커밋까지 했을 때, 가장 마지막에 한 커밋을 취소하는 방법에 대해 알아보자.
일단 다시 파일을 수정하고 -am 옵션으로 바로 커밋까지 했다.

이제 최신 커밋을 되돌리기 위해 git reset 명령 다음에 HEAD^를 붙이자.

HEAD^는 현재 HEAD가 가리키는 브랜치의 최신 커밋을 가리킨다. git log 명령에서 가장 최신 커밋에 HEAD -> master 표시를 기억할 것이다. 이렇게 되돌리면 커밋도 취소되고 스테이지에서도 내려진다. 취소한 파일은 working directory에만 남겨지는 것이다.
(만약 최근 3개의 커밋을 한번에 취소하고 싶다면 $git reset HEAD~3 을 사용한다.)

git reset 명령의 옵션
명령 설명
--soft HEAD^ 최근 커밋을 하기 전 상태로 working directory를 되돌린다.
--mixed HEAD^ 최근 커밋과 스테이징을 하기 전 상태로 working directory를 되돌린다.
옵션 없이 git reset을 사용할 경우 이 옵션을 기본으로 작동한다.
--hard HEAD^ 최근 커밋과 스테이징, 파일 수정을 하기 전 상태로 working directory를 되돌린다.
이 옵션으로 되돌린 내용은 복구할 수 없다.

 

항상 최신 커밋만을 되돌리기보다, 특정 버전으로 되돌아가서 그 이후의 버전을 삭제하고 싶은 경우도 있을 수 있다.

특정 커밋으로 되돌릴 때는 git reset 명령 다음에 커밋 해시(커밋 ID라고도 함)를 사용한다.

 

 

 

git reset 명령을 연습해보기 위해 파일을 수정하고 저장하는 것을 반복했다.

현재 마지막으로 커밋한 최신 항목은 R4이다. 여기서 R2 커밋을 최신 커밋으로 만들어보자.

그런데 reset에서 커밋 해시를 사용해서 되돌릴 때 주의할 점이 있다.
예를들어 reset A를 입력한다면 이 명령은 A 커밋을 삭제하는 것이 아니라, A 커밋 이후에 만들었던 커밋을 삭제하고 A 커밋으로 이동하겠다는 의미이다. (A를 리셋시키겠다는 것이 아니라 A로 리셋시키겠다는 말인듯)

아무튼 R2 커밋으로 이동하기 위해 R2 커밋의 id(해시)를 복사해서

이것을 리셋 명령어에 복사해주자.

이걸로 최신 커밋이 R2가 된 것을 확인할 수 있다.

로그를 확인해보면 역시 R3와 R4 커밋은 삭제됐다. 이 말은 R3, R4 커밋에서 수정한 파일 내용도 같이 삭제됐다는 의미이다.

 

커밋으로 되돌릴 때 수정했던 것을 삭제해도 된다면 git reset 명령을 사용하면 되지만, 나중에 사용할 것을 대비해서라도 취소한 커밋을 남겨두어야 할 때가 있다.
이 때, git reset이 아니라 git revert 명령을 사용한다.

 

예시를 위해 임의로 텍스트파일을 수정하고 커밋 했다. 
우리는 새로 커밋한 R5 버전을 취소하고, R5 직전 커밋 R2로 되돌아가보자.

revert의 경우에는 취소할 커밋의 해시를 지정하자.

revert 명령을 실행해보니 기본 편집기가 자동으로 나타나면서 커밋 메세지를 입력하는 창이 나온다.
커밋 메세지의 맨 위에는 어떤 버전을 revert 했는지 나와있다. 만약 revert하면서 추가로 남겨둘 내용이 있다면 입력하고 저장하자.

로그를 살펴보면 R5를 revert한 새로운 커밋이 생겼다. 그리고 reset과 달리 기존의 R5도 사라지지 않았다.

이를 통해 revert는 R5 버전을 지우는 대신, R5에서 변경했던 이력을 취소한 새로운 커밋을 만들었음을 확인할 수 있다.

이에 따라서 R5에서 수정한 파일 이력 또한 삭제되고 R2로 돌아가있다.

R5라는 커밋 이력만 남아있고 전부 삭제한것이다... 커밋 이력 외에 reset과 크게 다를것 없다.