생존을 위한 git tips

Heejoo Shin
Algorima
Published in
9 min readMar 16, 2020

Intro: Welcome to Git, Don’t be Shy

CVS, SVN 등 세상에는 다양한 버전 관리시스템(Version Control System)이 있다. 그중에서 요즘 오픈소스로 가장 널리 사용되며 개발자라면 필연적으로 접해봤을 것이 깃(git)이다. 그런데도 깃은 사용법이 결코 쉽지 않다.

One does not simply learn git

고로 이 글은 깃을 써야 하지만 어려워하는 사람, 잘 쓰고 싶은데 뭔지 모르는 사람들을 위한 글이라고 볼 수 있다. 깃 울렁증이 있는 당신에게 생존을 위해 필요한 깃 필수 지식을 전달하며, 건투를 빈다. (참고: 이 글은 로컬/원격 레포, 브랜치 등 아주 기본개념은 숙지했다는 가정하에서 진행됩니다)

Survival Commands

git status

가장 기본적이나 많은 사람이 간과하는 명령어라고 생각한다. 뭔가 잘못됐을 때, 혹은 잘못되지 않아도 수시로 확인을 위해서 git status를 자주 치는 것이 좋다. 보통은 십중팔구 git status 하고 나와 있는 명령어대로 따라 하면 상황이 해결된다.

git stash

커밋을 하지 않은 상태로 다른 브랜치 내용을 보기 위해서는 필수적으로 사용해야 하는 명령어이다. 실행하면 커밋처럼 변경사항에 대한 해시값 및 레퍼런스가 생긴다. 동시에 여러 레퍼런스를 저장할 수도 있는데, 뭐가 뭔지 헷갈릴 때는 다음 명령어를 사용하자:

git stash list

지금까지 스태시에 넣어놓은 변경사항들이 HEAD 위치(어떤 커밋 위에서 작성됐는지, 레퍼런스 번호 및 해시값과 함께 나타날 것이다.

스태시한 변경사항을 적용하는 방법은 크게 두 가지이다:

git stash apply <number 또는 해시값>
git stash pop <number 또는 해시값>

stash apply는 레퍼런스를 유지한 채로 적용하는 반면, pop은 말 그대로 stash에서 없애버리면서 적용하기 때문에 주의가 필요하다.

특정 스태시만 없애버리고 싶다면:

git stash drop <number 또는 해시값>

지금까지 stash 해 놓은 모든 내용을 없애고 싶다면,

git stash clear

git fetch

안전하게 원격 브랜치의 변경사항을 가져오고 싶을 때 쓰면 된다. Pull을 하지 않고 이후의 브랜치 변동사항을 가져올 때 유용하게 쓰인다. 예를 들어 개발자 A, B가 같은 브랜치에서 작업하고 있는 상황에서 A가 브랜치를 rebase/merge/커밋 삭제 등의 작업을 한 상태에서 개발자 B가 무턱대고 pull을 실행하면, 지금까지 작업한 결과물이 날아가거나 원치 않은 변경사항이랑 섞이는 불상사(!)가 생길 수 있다😱

git reset

git reset --hard <hash값>

뭔가 잘못했을 때 자주 쓰는 명령어이다.

위를 실행하면 로컬 작업상황을 해당 해시값의 상태로 되돌릴 수 있다. 단, 원래 상태가 날아가므로 유의해서 사용해야 한다.

git rebase || merge

예를 들어 master브랜치에서 checkout한 feature 브랜치에서 작업하고 있었다고 가정하자. 당신이 feature 브랜치에서 커밋을 하며 작업을 하는 동안, master 브랜치에 새로운 커밋들이 적용됐다. 당신은 master 브랜치의 새로운 커밋들이 필요한 경우, 어떻게 하겠는가? 이때 필요한 것이 rebase나 merge이다. Rebase나 merge 모두 브랜치가 갈라진 상황에서 현재의 상태로 브랜치의 상태를 맞춘다는 점에서는 같다고 볼 수 있으나 적용하는 방법이 상이하다.

git rebase origin/<브랜치 A> 을 실행시키면, 현재 브랜치의 커밋들을 하나씩 원격 브랜치 A의 끝에서부터 적용한다.

git fetch
git rebase origin/master
git add <충돌파일명> # 충돌파일 있을 시 해결한 후 실행
git rebase --continue # 충돌내용을 모두 해결한 후 실행
git push --force-with-lease # 충돌 내용 없을 시 바로 여기부터 실행하면 됨

여기서 git fetch를 쓰는 이유는, git rebase origin/master 가 원격 브랜치의 레퍼런스를 자동으로 가져오진 않기 때문이다. 즉 당신이 마지막으로 git fetch나 master브랜치에서 git pull 한 상태를 origin/master로 인식하고 가져와서 rebase를 한다는 말이다(참고로 git pull 을 하면 fetch처럼 원격 레포의 업데이트 레퍼런스들을 모두 가져오면서 로컬 브랜치 내용을 원격 레포에 맞추어 업데이트한다). 그동안 master가 업데이트됐으면, 나중에 다시 rebase를 해야 하므로 꼭 확인해주자.

git push --force-with-lease를 쓰는 이유는 원격 브랜치와 로컬 브랜치의 HEAD, 간단히 말해서 master 브랜치에서 뻗어 나오는 지점이 달라졌으므로 --force-with-lease를 붙여서 강제 업데이트시켜야 한다. 여기서 rebase의 단점이 드러나는데, 강제 업데이트를 시키기 때문에 브랜치 히스토리를 덮어쓰게 되는 것이다.

물론 단점만 있는 것은 아니다. 대신 rebase를 하면 깔끔한 브랜치 히스토리를 유지할 수 있다. 특정 브랜치 작업을 혼자 하는 경우 가장 깔끔한 업데이트 방법이나, 여러 명이 함께 작업하는 브랜치인 경우 rebase후 브랜치가 갈라지므로 권장되지 않는다.

좀 더 간편하게 쓸 수 있는 것은 merge이다. git merge <브랜치 a>를 하면, 브랜치 a의 변경사항들이 머지 커밋으로 합쳐져 현재 브랜치에 추가된다.

git fetch
git merge origin/master
git push

git merge origin/master를 하면 현재 있는 브랜치로 원격 master 브랜치의 변경사항들이 merge 커밋으로 들어올 것이다. Merge는 여러모로 ‘안전한’ 방법이라 불린다. 일단 기존 히스토리를 덮어쓰지 않으며, 사용법이 쉽고, 언제 다른 브랜치의 내용이 현재 브랜치에 적용됐는지 알아보기가 쉽다(그 말인즉슨 문제가 생겼을 때 빨리 이전 상태로 후퇴할 수 있다). 다만 다른 브랜치 내용을 지속해서 merge할 경우, 브랜치 히스토리가 지저분해진다는 단점이 있다. 또한 merge 를 했을 때도 rebase와 똑같이 충돌이 날 수 있으며, 하나씩 해결해줘야 한다.

커밋 순서

일단 git status로 현재 변경된 파일/추적되지 않은 파일 등을 확인해보자.

git status # 현재 변경된 파일 및 추적되지 않은 파일들을 파악한다.

그 다음 git add로 커밋에 포함될 파일들을 추가하자.

git add . # 변경되거나 추적되지 않은 모든 파일들을 모두 staging 상태에 올린다.
git add <디렉토리/파일명> # 선택적으로 특정 디렉토리/파일을 staging에 올리고 싶을때 사용한다.

새로 만든 파일의 경우 처음에 추적되지 않으므로, 반드시 git add로 추가시켜야 한다.

이쯤에서 git status를 실행 시켜 상황을 파악하자. 이제 staging에 올라갈, 즉 커밋에 포함될 파일들이 표시돼있다. Staging에 올라간 내용을 바꾸고 싶을 때, 크게 아래와 같은 방법들을 사용한다.

git reset HEAD <파일경로> # staging에 올라갔으나 커밋하고 싶지 않은 파일을 staging에서 해제
git checkout -- <파일> # 파일의 변경사항을 없애고 싶을 때

git checkout의 경우 실험적으로 파일 내용을 바꾼 후, 에디터로 열어서 다시 변경사항을 없애기 귀찮을 때 요긴하게 쓰인다.

git commit -m "커밋명" # 변경사항 커밋
git push # 원격 저장소에 커밋 업로드.

git commit -m “커밋이름” 할 때 주의할 점은, 이 커밋은 로컬에만 적용됐고 원격 저장소에는 아직 적용되지 않았다는 점이다. 그러므로 git push 까지 해줘야 원격 레포도 업데이트된다.

Push 하기 전 바꿀 점을 발견했다면, 변경을 모두 적용하고 git add 한 다음, git commit --amend를 통해 푸시되지 않은 커밋의 이름 및 내용을 모두 바꿀 수 있다.

Merge Conflict 해결

깃을 사용하면서 가장 처음으로 겪는 큰 어려움이 바로 merge conflict가 발생했을 때이다. 특히 feature 브랜치에서 오랫동안 master의 변경사항을 리베이스하지 않은 경우, 수많은 충돌의 늪에 빠질 수 있다. 그럴 때 git push --force의 유혹에 빠지기 쉽다.

git push — force: problem NOT solved!

보시다시피 별로 좋은 방법은 아니다. git push --force는 현재의 로컬로 원격 리포를 덮어써버리므로, 그 과정에서 다른 사람의 작업물이 날아갈 수 있고 conflict를 해결한 것이 아니기 때문에 언제 어디서 문제가 발생할지 알 수 없다(즉, 근본적인 해결책이 될 수 없다).

가장 바람직한 것은 정기적으로 rebase또는 merge를 해주며 (한 브랜치에서 오래 작업하며 merge를 여러 번 해야 하는 경우, rebase가 더 좋다) 충돌을 해결해주는 것이 좋다.

Useful Tips

set origin upstream

git branch --set-upstream-to origin/<branch 이름>

git checkout -b <브랜치명>으로 로컬에서 브랜치 생성 후, git push origin <브랜치명>으로 해당 브랜치를 원격 리포에도 생성한다. 그 후 위의 명령어를 해주면 git push origin <브랜치명> 할 필요 없이 git push만으로 해당 브랜치에 로컬 커밋들을 적용할 수 있다.

로컬에서 완벽하게 작업하고 한 번에 원격에 올려서 되는 게 아닐 때 은근히 브랜치명을 치는 것이 귀찮아지므로(특히 브랜치명이 길어지면 타자 연습을 하는 기분이 든다), 이걸 미리 해주는게 편리하다.

git client 설치

git log 등의 명령어를 사용하면 얼마든지 커맨드 라인 상에서도 브랜치 구조를 파악할 수 있지만, 예쁘게 출력하기 위해선 수많은 argument를 붙여야 하고 치는 것도 귀찮다 (그리고 무엇보다 안 예뻐서 문과갬성에 맞지 않다..). 그러므로 이런 불편을 없애기 위해 git client를 설치하는 것이 좋다. 클라이언트를 설치하면 gui로 브랜치 구조를 쉽게 파악할 수 있다. git client는 fork, sourcetree등이 있으며 입맛에 맞는 걸 아무거나 설치해도 된다.

Sourcetree | Free Git GUI for Mac and Windows

Fork — a fast and friendly git client for Mac and Windows

마무리하며💻

깃은 공부를 해도 해도 모르는 것이 자꾸 튀어나오기 십상이다. 그렇다고 해서 절망할 필요는 없다. 어차피 백문이 불여일견이라고, 직접 겪어보기 전까진 여기에 작성한 명령어의 필요성을 대부분 인지하지 못할 것이다. 직접 부딪쳐보며 검색하고 실수를 하는 과정 모두가 소중한 공부의 밑거름이다. 고로 처음부터 두려워하지 말고, 차근차근 배운다는 생각으로 임하는 것이 바람직하다. 모두가 깃 고수가 되는 그날까지, 포기하지 않았으면 한다.🙂

--

--

Heejoo Shin
Algorima
0 Followers
Writer for

Software Engineer @Algorima