백엔드 개발자 로드맵 따라가기 5. 형상 관리 - Git

박성수·2020년 11월 24일
2

1. 개요

Git은 대표적인 형상 관리 시스템이다.
형상 관리 시스템이란, 소스 코드의 관리 (버전 관리, 변경 사항 추적) 등의 기능을 수행하는 시스템을 말한다.

웹 호스팅 서비스로는 GitHub와 GitLab이 있다.

Git에 대해서 설명하려면 끝도 없기 때문에 이번 포스팅에서는 실제 업무를 진행하면서 많이 사용하는 기능들을 알아보려고 한다.

2. 기본 개념

기본 개념은 여기서 하나하나 다 보시는게 최고입니다.

https://git-scm.com/book/ko/v2/

Git의 여러가지 기능들을 정확히 이해하려면 Staging 이란 개념을 확실히 알아야 한다.

Staging 영역이란, 쉽게 말해서 Git이 관리하는 파일의 영역으로 'git add <파일>' 명령을 통해 Tracked 상태가 된 파일들의 단계를 의미한다.

Staging Area는 git commit을 실행했을 때 git이 처리할 것들이 있는 곳이다.

또한 트리를 이해해야 한다. 트리에는 Working Directory, index, HEAD(포인터)가 존재한다.

Index는 트리는 아니지만, 이해를 돕기위해서 트리라고 표현한다. Index는 위 그림에서 UnStaged 상태의 파일들이다.

3. git reset

git reset은 위 세 트리의 포인터를 원하는 커밋 시점으로 되돌릴 때 사용한다.

3-1. --soft

HEAD 트리의 포인터를 변경할 때 사용한다. 이 때, 마지막 커밋의 포인터를 변경하면 commit 전의 상태로 돌아가는 것과 같다.

3-2. --mixed

git reset 기능의 default 값으로, Index 트리(HEAD + Index)의 포인터를 변경할 때 사용한다.
이 때, 마지막 커밋의 포인터를 변경하면 add 전의 상태로 돌아가는 것과 같다.

3-3. --hard

Working Directory 트리(HEAD + Index + Working Directory)까지 변경한다.
이 때, 마지막 커밋의 포인터를 변경하면 로컬 파일 수정 전의 상태로 돌아가는 것과 같다.

git에서 데이터를 실제로 삭제하는 몇 안되는 위험한 명령어가 reset --hard 명령어다. 이 명령은 되돌리기가 불가능하다. 단, 커밋한 데이터는 복원이 가능하다.

hard 옵션은 working directory까지 되돌리기 때문에 수정한 파일들까지 다 지워진다. 따라서 git status를 봐도 수정 내역이 없다.

3-4. git checkout 과의 차이점

reset 명령은 HEAD가 가리키는 브랜치를 움직이지만 checkout은 HEAD만 다른 브랜치로 옮긴다.

물론 checkout 명령은 브랜치 전환에 가장 많이 사용된다. working directory가 안전하기 때문이다.

필자는 checkout 명령을 브랜치 전환외에도 fix에 많이 사용하는데, 배포한 특정 버전에 이슈가 생겼다면 해당 배포 버전으로 checkout을 하고 fix 브랜치를 생성한 뒤, 이슈를 해결한다. 그러고 배포 브랜치(마스터)와 fix 브랜치를 병합하고 빠르게 배포한 뒤, fix 브랜치를 develop 브랜치와도 병합하여 트리를 맞춰준다.

3-5. git revert 와의 차이점

reset과 달리 커밋 이력을 지우지 않는다. revert도 하나의 커밋으로 이력이 남게 된다.

git revert <commit명>

이미 원격지로 Push한 커밋 이력을 되돌릴 때 사용한다. 로컬에서 revert 하는 순간 이후의 커밋 이력도 같이 가져가기 때문에 로직 충돌이 발생하고 해결해야 한다. 때문에 원격지에서의 충돌은 적게 난다...
어차피 Push한 커밋 이력을 reset으로 돌려서는 Push가 안되기 때문에(Force 옵션을 쓰면되지만..) revert를 사용해야 한다. 하지만 되도록 쓰고 싶지 않다.

4. git 되돌리기

4-1. 커밋 내용 수정하기

커밋할 파일을 빠트렸거나 아직 덜 수정된 채 커밋 되었다면 추가 및 수정하여 새로 커밋을 해줘야한다.
이 때, 앞에서 실수로 해버린 커밋의 내용을 덮어쓰고 로그를 수정할 옵션이 amend이다.

git commit --amend

4-2. 파일을 Unstage로 변경하기

파일을 Unstage 상태(UnTracked 상태를 말하며, UnStaged와 Staged는 Staging 영역)로 변경한다는 것은 add 전의 상태로 만든다는 것이다.

해당 작업은 아래 restore 명령으로 수행할 수 있다.

git restore --staged <file명>

여러개의 파일을 수정하고 따로 커밋하려고 했지만 한번에 add를 다 시켜버려서 하나의 커밋 staging에 포함되었을 때 특정 파일을 Unstage로 만들경우에 사용한다.

4-3. Modified 상태 파일 되돌리기

필요에 따라 파일을 수정하였지만 어떠한 이유로 수정할 필요가 없어졌거나 커밋된 버전 시점으로 원복해야 할 상황이 생길 수 있다.

아래에서 초록색 글씨의 log/test.log 파일과 빨간색 글씨의 log/test.log는 같은 파일을 가리키지만 그 내용은 다르다. 빨간색 글씨의 log/test.log는 수정본이다.


이 때, 수정본의 내용을 커밋 상태의 파일(초록색)로 되돌리려면 restore 명령을 사용하면 된다.

git restore <file명>

5. git cherry-pick

cherry-pick은 크게 다음 2가지 상황에서 사용된다.

  1. 여러 브랜치의 커밋들 중 필요한 커밋을 선택하여 내 브랜치로 적용시킬 때 사용한다.

  2. 누군가 revert를 해서 특정 시점의 커밋 파일이 없어진 경우


Developer2 브랜치에서 Developer1 브랜치의 7eca3bd 커밋 내용을 적용했을 때의 상황이다.
Developer2에서는 Cherry-pick 하는 순간 새로운 커밋(d859ca1)을 만든다.

이 과정에서 충돌이 생길 수도 있다.

그럴때는 다음 옵션으로 해결하자.

5-1. --continue (충돌 해결)

git add <conflict 해결한 file명>
git cherry-pick --continue

5-2. --abort (cherry-pick 취소)

git cherry-pick --abort

6. git rebase

git의 rebase 기능은 여러가지 역할을 하는데, 크게 보면 git history를 관리하면서 여러 브랜치들과 저장소들 간의 base를 맞추는 작업을 한다. 여기서 base란, git 커밋 목록들을 의미한다.

rebase는 잘 사용한다면 다양한 기능으로 사용될 수 있고, 깔끔한 히스토리 관리에 아주 탁월한 명령어이다. 하지만 이미 원격지에 push한 커밋에 대해서는 rebase를 하지마라

6-1. 커밋 여러개 수정하기

수정하려는 commit들의 pick을 edit으로 저장하면

순서대로 해당 커밋 시점으로 이동한다.
이 때, git commit --amend 명령으로 커밋을 수정한다.

rebase가 끝나고 log를 확인해보면 수정되어 있는 것을 확인할 수 있다.

각 rebase 작업 이후에는 반드시 --continue 를 실행해줘야 한다.

6-2. 커밋 삭제 및 순서 바꾸기

git rebase -i HEAD~5 (HEAD로 부터 작업대상 커밋 수 지정)

삭제하려는 commit 목록(e13b128)의 pick을 drop으로 변경 후 저장하면 해당 이력이 삭제된다.

e13b128 커밋을 삭제하고 02ee797과 d7e645f의 커밋 순서를 바꾼 뒤, 저장하면 아래와 같이 원하는 이력을 갖는다.

6-3. 커밋 합치기


위의 Controller명 변경과 관련된 커밋 목록들 3개를 다 합치려고 한다.

git rebase -i HEAD~5 (HEAD로 부터 작업대상 커밋 수 지정)


합치려는 커밋 목록들을 pick -> squash로 변경해준다.
그리고 최종 커밋 메세지 수정 후 저장하면 아래와 같이 깔끔한 커밋 history를 가질 수 있다.

6-4. 커밋 분리하기


여기서 f8024c9 커밋을 serviceA() 와 serviceB() 커밋으로 분리하려고 한다.

rebase 기능을 통해서 분리하려는 커밋을 pick -> edit으로 변경 후 저장한다.

그리고 아래 명령으로 git commit을 해제(Unstage 상태, add 전)한다.

git reset HEAD^ (window라면, HEAD^^)

그리고 분리하고 싶은 기능을 각각 분리해서 커밋을 진행한다.

작업이 끝나면 반드시 --continue를 해준다.

그리고 커밋 로그를 확인하면 커밋이 분리된 것을 확인할 수 있다.

6-5. 브랜치 합치기

초록색은 main developer(developer1) 브랜치, 보라색은 sub developer(developer2) 브랜치라고 하자.

이 두 브랜치를 합치는 방법은 merge와 rebase 두 가지가 있다.

6-5-1. 먼저 merge를 이용한 병합

git merge developer2

6-5-2. 다음 rebase를 이용한 병합

git rebase developer1 #sub branch로 rebase 병합
git checkout developer1
git merge developer2 #main branch 상태 맞추기

위 merge로의 병합보다 더 깔끔하게 정리된 히스토리를 확인할 수 있을 것이다.

7. git 원격지 파일 삭제

실수로 원격지에 올라간 파일들을 삭제하는 일이 가끔 생긴다.
그럴땐 아래 명령을 수행하고 대상이 폴더라면 -r 옵션을 붙이면 된다.

git rm --cached -r ./.idea

8. update-index (특정 파일 임시로 커밋에서 제외)

git에서 .gitignore 를 사용해서 특정 파일이나 폴더를 스테이징 영역에서 제외시킬 수 있지만, gitignore에서 처럼 영구적으로 제외할 것이 아니라 특정 커밋에서만 임시적으로 제외하고 싶다면 다음과 같은 방법이 있다.

8-1. --assume-unchanged

아래 명령으로 수행된 특정 파일은 git이 해당 파일의 변경을 인식하지 않는다.

git update-index --assume-unchanged <파일명>

8-1. --no-assume-unchanged

아래 명령으로 해당 파일을 다시 git이 인식하도록 지정할 수 있다.

git update-index --no-assume-unchanged <파일명>

8-2. --really-refresh

git 커밋 목록에서 임시로 제외됐던 파일들을 다시 워킹트리에 대해 갱신한다.

git update-index --really-refresh

8-3. 커밋에서 임시로 제외된 항목 보기

git ls-files -v | grep ^h

profile
Java 백엔드 개발자입니다. 제가 생각하는 개발자로서 가져야하는 업무적인 기본 소양과 현업에서 가지는 고민들을 같이 공유하고 소통하려고 합니다.

0개의 댓글