본문 바로가기
튜토리얼

시작하는 개발자를 위한 생존 git tips

by evmoon 2021. 4. 25.

Intro: Welcome to Git, Don't be Shy

One does not simply learn git

개발을 좀 해봤다 한다면(!) 언젠가는 git(깃)에 대해 들어봤을 거라 생각합니다. 깃은 오픈소스 코드버전관리시스템으로, 현재 가장 널리 쓰이고 있다 해도 과언이 아닐 것입니다. 하지만 깃의 사용법은 결코 쉽지 않습니다. 그래서 이 글은 깃을 써야하지만 어려워하는 사람, 잘 쓰고는 싶은데 뭐가 뭔지 모르겠는 사람들을 위한 글이라 할 수 있습니다. 깃 울렁증이 있는 많은 분들에게 이 글이 도움이 되길 바라며, 깃을 어느 정도 원활히 다루는데 필요한 최소한의 지식만을 전달하려 노력했습니다. (참고: 이 글은 로컬/원격 레포, 브랜치 등 아주 기본개념은 숙지했다고 가정합니다.)

커밋 명령들

가장 기본적인 커밋을 하는 방법부터 알아봅시다.
일단 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를 통해 푸시되지 않은 커밋의 이름 및 내용을 모두 바꿀 수 있습니다.

Survival Commands

기본적인 커밋 방법을 숙지했으니, 이젠 알아두면 유용할 여러 명령어를 알아봅시다.

git status

가장 기본적이나 초보들이 많이 간과하는 명령어라고 생각합니다. 뭔가 잘못됐을 때, 혹은 잘못되지 않았어도 수시로 확인을 위해서 git status를 치는 것이 좋습니다. 문제가 발생 시 이 명령어를 치면 나름 친절한 설명과 함께 해결 방법이 제시되기 때문에, 대부분의 경우 그대로 따라서 하면 해결됩니다.

git stash

커밋되지 않은 변경 사항을 잠시 킵해둘수 있는 기능입니다. 주로 뭔가를 잠깐 실험해보거나, 작업물이 있지만 아직 커밋하기는 싫고 다른 브랜치로 이동해야하는 상황에서 요긴하게 쓰입니다. 실행을 하면 커밋처럼 변경사항에 대한 해시값 및 레퍼런스가 생기는데, 동시에 여러 레퍼런스를 저장할 수도 있습니다. 레퍼런스가 여러 개 생기면 어떤 스태시가 어떤 거였는지 헷갈리기 때문에, 그 경우엔 다음의 명령어를 실행하여 각 스태시의 레퍼런스 번호, 해시값 및 HEAD위치(어느 커밋 위의 작성됐는지)를 확인할 수 있습니다.

git stash list # 현재 레포의 스태시들을 모두 나열

다만 이렇게 되면 각 스태시의 내용은 보여주지 않기 때문에, 각 스태시의 내용을 확인하고 싶을 때는 다음의 명령어를 실행합니다.

git stash show -p <스태시 번호>

스태시의 내용을 다시 적용하는 방법에는 크게 두 가지가 있습니다:

git stash apply <레퍼런스 번호 또는 해시값>
git stash pop <레퍼런스 번호 또는 해시값>

git stash apply의 경우 해당 스태시를 남겨둔채로 내용을 적용하지만, git stash pop의 경우 해당 스태시를 없애버리기 때문에, 유의해서 사용해야 합니다.
특정 스태시를 현재 작업에 적용시키지 않고 없애버리고 싶다면

git stash drop <레퍼런스 번호 또는 해시값>

지금까지 스태시한 모든 내용을 없애고 싶다면

git stash clear

를 실행하면 됩니다.

git fetch

'안전하게' 원격 브랜치의 변경사항만을 가져오고 싶을 때 사용하면 됩니다. 특히 Pull을 하지 않고 이후의 브랜치 변동사항을 가져올 때 유용합니다. 예를 들어 개발자 A, B가 같은 브랜치에서 작업하고 있는 상황을 가정해봅시다. A가 브랜치를 rebase/merge/커밋 삭제 등의 작업을 한 상태에서 개발자 B가 무턱대고 pull을 실행하면, 지금까지 개발자 B가 작업한 결과물이 날아가거나 원치 않은 변경사항이랑 섞이는 불상사(!)가 생길 수 있습니다. 그러한 경우를 방지하기 위해, 일단 git fetch를 실행해 원격 브랜치의 상황을 확인한 다음 적절하게 대응할 수 있습니다.

git reset

git reset --hard <hash값>

커밋이 꼬이거나 기타 난감한 상황에서 사용되는 명령어입니다.
위의 명령어를 실행하면 로컬 작업상황을 해당 해시값의 상태로 되돌릴 수 있습니다. 단, 원래 상태가 (흔적도 없이) 날아가므로 특히 유의해서 사용해야 합니다. 미숙한 git 조작 등으로 인해 꼬였을 때 사용할 수 있는 최후의 방법(...)으로 알아두고, 가능하면 사용하지 않는 것이 좋습니다.

git cherry-pick
레포의 특정 커밋을 똑 떼와서 현재 브랜치에 적용하고 싶을 때 사용됩니다. 동시에 여러 커밋도 적용할 수 있는데, 사용 시 기존 커밋과 내용은 같지만 해시값이 다른 커밋이 생기게 됩니다.

git cherry-pick <hash값>

git reflog

앞서 git fetch에서 들었던 개발자 A와 B의 예시를 다시 생각해봅시다. 개발자 A가 브랜치를 변경한 상태에서 개발자 B가 해당 브랜치를 pull 해버렸고, 그 과정에서 개발자 B의 커밋은 사라져버렸습니다. 이 경우에 개발자 B의 작업물은 영영 사라진 것일까요? 아직 늦지 않았다면, 개발자 B의 작업물을 다시 복구(!)시킬 수 있습니다. git은 브랜치의 끝지점 및 레퍼런스들이 업데이트 될 때 이전 상태를 일정 기간 동안 보관합니다. 그러므로 git reflog을 통해 해당 커밋의 해시값을 확인하고, git reset --hard <hash값>을 활용해 해당 상태로 되돌리거나, git cherry-pick <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한 상태를 기준으로 rebase를 한다는 뜻입니다(참고로 git pull을 하면 fetch처럼 원격 레포의 업데이트 레퍼런스들을 모두 가져오면서 로컬 브랜치 내용을 원격 레포에 맞추어 업데이트합니다). 그동안 master가 업데이트됐으면, 나중에 다시 rebase를 해야 하므로 꼭 fetch를 실행시켜 확인해주는 것이 좋습니다.
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와 똑같이 충돌이 날 수 있으며, 하나씩 해결해줘야 합니다.

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만으로 해당 브랜치에 로컬 커밋들을 푸시할 수 있습니다.
로컬에서 여러 번 push할 경우 은근히 브랜치명을 치는 것이 귀찮아지므로(특히 브랜치명이 길어지면 타자 연습을 하는 기분이 들 수 있습니다), 이걸 미리 해주는게 편리합니다.

git client 설치

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

 

 

Sourcetree | Free Git GUI for Mac and Windows

A Git GUI that offers a visual representation of your repositories. Sourcetree is a free Git client for Windows and Mac.

www.sourcetreeapp.com

 

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

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

fork.dev

마무리하며💻

이 글은 필자가 개발자 인턴으로 일하면서 깃을 다루며 직접 사고도 치고(...) 다른 사람의 실수를 수습해주고, merge conflict도 해결하면서 쌓았던 경험들을 집약한 글이라 할 수 있습니다. 아직 부족함이 많고 깃 공부에는 끝이 없지만, 이 정도만 숙지하더라도 깃을 활용한 기본적인 협업은 어느 정도 가능할거라 생각합니다.
참고로 이 글은 필자가 인턴 재직 당시 썼던 글을 재구성한 것입니다.

'튜토리얼' 카테고리의 다른 글

초보를 위한 tmux 사용법  (0) 2021.07.04