Git 한 걸음 더 나아가기

yoohee.chung·2022년 12월 9일
1

Git 활용하기

목록 보기
1/1

개요

Git은 2005년 프로젝트가 시작된 이후로 2022년 현재 가장 널리 사용되고 있는 버전 관리 시스템입니다. Git의 주요한 특징은 모든 변경 이력이 로컬에서 관리되는 분산 버전 관리 시스템으로, 실행 속도가 빠르고 데이터 무결성이 보장되며 비선형 워크플로우를 지원한다는 점입니다. 모든 git 디렉토리는 네트워크 접속/중앙 서버와는 독립적으로 동작하는 완전한 이력/버전 추적 기능을 갖춘 하나의 완성된 저장소라는 점이 SVN과는 다른 점이라고 볼 수 있습니다.

본 문서에서는 Git을 사용하기에 있어서, 좀 더 편리하게 버전을 관리할 수 있도록 하는 팁이나, 몇 가지 깊이 알면 좋을 개념들을 소개합니다. 이 문서는 기본적인 commit, push, pull 개념을 알고 있고 이를 충분히 사용할 수 있는 사람을 대상으로 합니다.

(사실 제가 commit-push-pull 만 쓰다가 답답해서 자습한 후기 글..)

Commit 내용 고치기

commit 을 생성하고 나서 이후 commit message를 바꾸거나, 추가적으로 버그를 수정하고 이를 마지막 commit에 함께 반영하고자 하는 경우, 이를 별도의 commit으로 만들지 않고 기존에 있는 최신 커밋을 수정함으로써 반영할 수 있습니다.

git commit --amend

commit 내용을 다시 수정할 수 있는 vi editor 창이 열리고, 수정 내용을 다시 작성하여 저장하면 기존에 있던 commit이 수정(amend) 됩니다.

amend 커밋을 사용할 때 주의해야 할 점은, 기존에 있던 commit을 수정할 경우 commit ID가 바뀐다는 점입니다. 즉, 기존에 있던 commit은 삭제된 것이며, 새로운 커밋이 그 자리를 대신한 것과 같습니다.

따라서 commit --amend 로 수정한 후 이를 다시 push 하려고 할 경우 non-fastforward push 라는 에러가 나게 됩니다. 수정한 버전으로 덮어쓰고자 하는 경우 -f 옵션을 주어 forced push를 하면 됩니다.

git commit -f push

작업 내역 보기 - Git log & reflog

commit의 history를 확인하고 싶을 때는, git log 커맨드를 사용해서 이력 확인을 할 수 있습니다. git log는 commit의 이력을 보여줍니다.

git log


그러나 commit 이력은 rebase/reset 등으로 인해 사라질 수도 있습니다. commit 이력이 사라지면 복구할 방법이 없어보여 막막한 심정을 느낄 수 있지만, git은 스스로의 작업 내역 역시 관리하고 있기에 이를 통해 복구가 가능합니다. Git 자체 변경 이력을 보려면 reflog를 확인해야 합니다.

git reflog


HEAD@{숫자}의 형태로 최근 작업부터 역순으로 보여집니다. reset을 해서 커밋이 다 날아가더라도 여기서 확인해보면 그 이력이 그대로 남아있는 걸 볼 수 있습니다. HEAD@{N}으로 reset하여 이전 작업을 무엇이든 취소할 수 있습니다.

특정 버전으로 되돌리기 - Revert vs Reset

git에서 작업을 하다보면 요구사항이 바뀌거나, 혹은 새로운 커밋에서 버그가 발생했기 때문에 이전 버전으로 되돌아가서 작업을 해야 할 경우가 생깁니다. 이렇게 이전 버전으로 소스코드를 돌리는 방법에는 2가지가 있습니다.

Revert

git revert


Revert는 이전 버전으로 소스 코드를 되돌리는 커밋을 새로 생성함으로써 버전을 되돌리는 방식입니다.

C라는 commit이 있지만, 우리는 R에 있는 버전으로 다시 소스코드를 되돌리고 싶습니다. Revert는 소스코드를 되돌리면서, "Revert가 일어나서 소스코드가 R에 있던 버전으로 되돌아갔다"를 의미하는 커밋을 하나 새로 생성합니다.

Revert는 이력을 건드리지 않고, 새로운 commit을 만들어서 버전을 복구하는 방식이므로 상대적으로 안전한 방법이라고 할 수 있습니다. (물론 위에서 언급했다시피 reset으로 돌린 것을 복구하지 못하는 것은 아닙니다.)

Reset

Revert가 복구 이력을 새로 생성하는 반면 Reset은 되돌리고자 하는 commit 이후의 commit은 날려버리고, 다시 HEAD가 이전 commit을 가리키도록 하는 방식입니다.

뒤에 있던 commit은 commit log 를 보아도 더 이상 남아있지 않습니다. reflog 를 찾아 다시 reset 이전으로 복구하는 것은 가능합니다만, 기본적으로 commit history 에 손을 대는 방식이라 상대적으로 덜 안전하다고 여겨집니다.

reset은 세 가지 다른 옵션으로 할 수 있는데 각각 다음과 같습니다.

종류설명
--softcommit 이력만 되돌아가고, stage된 파일의 index와 코드는 그대로 남아있는 상태
--mixed (default)commit 이력이 되돌아가고 index가 초기화되지만 코드는 그대로 남아있는 상태
--hard코드, index, commit 이력 모두 reset하는 버전으로 돌아간 상태

다른 브랜치의 작업 내용 반영하기 - merge vs. rebase vs. cherry-pick

자신의 브랜치에서 작업을 하다가 master나 develop의 내용, 혹은 다른 브랜치의 내용을 내 브랜치에 반영하고자 하는 경우, merge, rebase, cherry-pick 명령어를 사용할 수 있습니다. 각각은 어떻게 다른 것일까요?

merge

다른 브랜치의 내용을 내 브랜치에 반영하는 가장 보편적인 방법은 merge입니다. merge는 크게 fast-forward merge와, 3-way merge가 있습니다.

fast-forward merge

develop/master에서 생성한 feature/hotfix 에서 작업을 하고 난 후, 이 작업을 부모 브랜치에 반영하려 하는 경우에 fast-forward 방식의 merge가 적용됩니다.
자식 브랜치의 commit 이 merge되고 나면 부모 브랜치의 HEAD 가 빨리감기 되듯 합쳐진 branch의 HEAD로 이동을 합니다.

### 3-way merge 반면에, 부모 브랜치가 아닌 다른 브랜치의 작업을 반영하거나, --no-ff 옵션을 주거나, 혹은 자식 브랜치에서 부모 브랜치에 누적된 변경을 반영하려고 하는 경우에는 fast-forward와는 다른 방식으로 merge가 이루어집니다.

이 때 사용하는 방식이 3-way merge입니다. 3-way merge에서는 별도의 merge commit 이 생성되고, 이 commit에서 merge가 이루어집니다.

왼쪽은 --no-ff 옵션으로 3-way merge를 했을 때, 오른쪽은 ff 방식으로 merge를 했을 때의 커밋 히스토리입니다.

Rebase

rebase는 두 브랜치의 내용을 합치는 또 하나의 방법입니다. 장점도 있지만, 사용하지 말아야 할 때도 있습니다. 아래는 각각 merge와 rebase를 사용해서 두 브랜치를 합치는 것입니다.

위가 merge, 아래가 rebase입니다. merge는 새로운 commit C5를 만들어 두 브랜치의 내용을 합쳤습니다.

반면에 Rebase는 두 브랜치의 공통 조상을 찾는 일부터 합니다. 이 예제에서 두 브랜치의 공통 조상은 C2 commit입니다. 그 사이에 master에는 C3가 추가되었고, experiment 에는 C4 가 추가되었습니다.

rebase 명령이 떨어지면 git은 이 C2 부터, 현재 checkout한 branch가 가리키는 commit까지의 diff를 차례로 만들어서 임시로 저장합니다.(C4를 임시 저장)

rebase할 브랜치(experiment)가 합쳐질 브랜치(master) 가 가리키는 commit을 가리키게 하고, C4의 내용을 C3 뒤에 갖다 붙입니다. 즉 master에 새로이 반영되었던 내용들을 experiment 브랜치의 뿌리로 다시 설정(re-base) 하는 것이 핵심입니다.

충돌하는 내용이 없다면 C4와 내용은 같지만 commit ID가 C3 뒤에 생성되며 rebase가 완료됩니다. (C4')

Rebase 의 장점과 단점

유의할 점은, merge나 rebase나 최종적인 결과물의 내용은 동일하다는 점입니다. 이 점은 revert vs reset 과의 관계와도 비슷합니다.

둘은 commit history만 다르다는 점에 유의해야 합니다. merge는 두 브랜치의 최종 결과물을 합치고, rebase는 브랜치의 변경사항을 순서대로 다른 브랜치에 적용하면서 합친다는 차이점이 있습니다.

Rebase는 좀 더 깨끗한 history를 만드는 데 유리합니다. Merge가 모든 병합의 history를 남긴다는 장점이 있지만 나중에 작업의 선후 파악이 어려워질 수 있다는 단점이 있는데 rebase는 정반대의 특징을 가집니다. rebase한 브랜치의 로그를 살펴보면 히스토리가 선형적이라는 것을 확인할 수 있습니다. 병렬적으로 진행된 작업이라고 하더라도 rebase 이후에는 모든 작업이 차례대로 수행된 것처럼 보입니다.

한편 merge에 비해 rebase에는 위험성이 있는 편입니다. 위의 예시에서 C4가 C4'라는 다른 commit ID를 가진 채로 병합이 되었다는 점에 주의해야 합니다. Rebase는 절대 공개 저장소에 Push한 commit을 대상으로 이루어져서는 안 됩니다. 내 브랜치가 나 혼자 작업하고 부모에 merge될 것이라면 문제가 없습니다. 그러나 동료가 나의 브랜치를 pull 해서 작업할 가능성이 있다면 rebase는 매우 신중하게 사용해야 합니다.

만일 내가 새 commit을 만들어 server에 push 하고, 나중에 develop/master 를 rebase하여 다시 push 하는 경우 commit ID가 바뀌기 때문에, 동료가 자신의 작업을 또 push 하려 하는 경우 내가 작업한 버전을 다시 pull 한 다음 일일이 conflict를 고치고 다시 push해야 하는 번거로움을 겪을 수 있습니다.

Cherry-Pick

  git cherry-pick <commit_id>

Cherry-pick은 그 이름만큼이나 편법에 가까워보이는 기능입니다. 다른 브랜치의 특정 commit을 내 브랜치에 반영하고자 할 때 cherry-pick을 사용할 수 있습니다.

그러나 cherry-pick으로 가져온 변경분은 원래의 commit과 내용을 같아도 commit ID가 달라지기 때문에 사용에 신중해야 합니다.

특정 커밋에 태그 달기

다른 VCS와 마찬가지로 git도 태그 기능을 지원하는데, 보통 마스터 브랜치의 형상을 daily로 기록하거나, 릴리즈 버전을 딸 때 사용합니다.

  git tag v1.4-lw #light-weight tag
  git tag -a v1.4 -m "my version 1.4" #tag with annotations

아무 옵션도 주지 않으면 light-weight 태그가 생성되는데, 이 태그는 이름만 갖고 있습니다. -a -m 옵션을 사용해서 추가적인 정보인 annotation을 붙인 태그를 생성할 수도 있습니다.

profile
잘 먹고 잘 살고 싶습니다.

0개의 댓글