이미지 로딩 중...

Git 실무 활용 팁 고급 - 슬라이드 1/9
A

AI Generated

2025. 11. 5. · 5 Views

Git 실무 활용 완벽 가이드

실무에서 자주 사용하는 Git 고급 기능과 팁을 다룹니다. 브랜치 전략, 충돌 해결, 히스토리 관리 등 실전에서 바로 활용할 수 있는 Git 활용법을 배워보세요.


목차

  1. Git Rebase
  2. Interactive Rebase
  3. Git Stash
  4. Git Cherry-pick
  5. Git Reflog
  6. Git Bisect
  7. Git Submodule

1. Git Rebase

시작하며

여러분이 팀 프로젝트를 진행하면서 feature 브랜치에서 작업을 완료했는데, 그 사이 main 브랜치에 여러 커밋이 추가된 상황을 경험해본 적 있나요? merge를 하면 불필요한 merge 커밋이 생기면서 히스토리가 복잡해지고, 나중에 코드 리뷰나 디버깅 시 커밋 흐름을 파악하기 어려워집니다.

이런 문제는 실제 개발 현장에서 매우 자주 발생합니다. 여러 개발자가 동시에 작업하면서 브랜치가 많아지면, 커밋 히스토리가 스파게티처럼 얽히게 됩니다.

특히 코드 리뷰 시 "이 기능이 정확히 어떤 순서로 개발되었지?"를 파악하기 어려워지고, git log를 봤을 때 시간 순서가 뒤죽박죽이 되어 버립니다. 바로 이럴 때 필요한 것이 Git Rebase입니다.

rebase를 사용하면 여러분의 작업을 최신 코드 위에 "다시 적용"하여 마치 처음부터 최신 코드를 기반으로 작업한 것처럼 깔끔한 일직선 히스토리를 만들 수 있습니다.

개요

간단히 말해서, Git Rebase는 현재 브랜치의 커밋들을 다른 브랜치의 최신 커밋 위로 이동시키는 기능입니다. "re-base"라는 이름처럼, 작업의 "기준점(base)"을 다시 설정하는 것입니다.

왜 이 개념이 필요한지 실무 관점에서 설명하자면, 깔끔한 커밋 히스토리는 단순히 보기 좋은 것이 아니라 팀의 생산성과 직결됩니다. 코드 리뷰 시 변경사항을 논리적으로 추적할 수 있고, 버그가 발생했을 때 어느 시점에서 문제가 생겼는지 빠르게 찾을 수 있습니다.

예를 들어, 프로덕션에서 긴급 버그가 발생했을 때, 복잡한 merge 커밋들 사이에서 원인을 찾는 것보다 일직선으로 정리된 히스토리에서 찾는 것이 훨씬 빠릅니다. 기존의 merge 방식에서는 두 브랜치의 변경사항을 합치는 새로운 "merge 커밋"이 생성되었다면, rebase를 사용하면 여러분의 커밋들이 대상 브랜치의 최신 커밋 뒤에 순차적으로 재배치됩니다.

이렇게 하면 프로젝트 히스토리가 선형적으로 유지되어 이해하기 쉽습니다. Rebase의 핵심 특징은 세 가지입니다.

첫째, 커밋 히스토리를 일직선으로 만들어 가독성을 높입니다. 둘째, merge 커밋 없이 변경사항을 통합할 수 있습니다.

셋째, 각 커밋이 최신 코드 위에서 다시 적용되므로 충돌을 단계별로 해결할 수 있습니다. 이러한 특징들이 중요한 이유는 팀 전체가 코드의 변경 흐름을 명확히 이해할 수 있게 해주기 때문입니다.

코드 예제

# 현재 feature 브랜치에서 작업 중
git checkout feature/user-authentication

# main 브랜치의 최신 변경사항을 가져옴
git fetch origin main

# feature 브랜치를 main 브랜치 위로 rebase
# 주석: feature 브랜치의 커밋들이 main의 최신 커밋 뒤로 이동됨
git rebase origin/main

# 충돌이 발생하면 파일을 수정한 후
git add .
git rebase --continue

# rebase를 취소하고 싶다면
git rebase --abort

# 강제 푸시 (rebase는 히스토리를 변경하므로 필요)
git push --force-with-lease origin feature/user-authentication

설명

이것이 하는 일: Git Rebase는 여러분의 브랜치에 있는 각 커밋을 임시로 저장했다가, 대상 브랜치의 최신 상태 위에 하나씩 다시 적용합니다. 마치 여러분이 처음부터 최신 코드를 기반으로 작업한 것처럼 히스토리를 재구성하는 것입니다.

첫 번째 단계로, git fetch origin main을 실행하면 원격 저장소의 main 브랜치 최신 상태를 로컬로 가져옵니다. 이때 여러분의 작업 브랜치는 아직 변경되지 않습니다.

단순히 "참조 정보"만 업데이트되는 것이죠. 이렇게 하는 이유는 rebase를 하기 전에 대상 브랜치의 최신 상태를 정확히 알아야 하기 때문입니다.

두 번째 단계로, git rebase origin/main을 실행하면 Git이 자동으로 여러 작업을 수행합니다. 먼저 현재 브랜치(feature/user-authentication)와 대상 브랜치(origin/main)의 공통 조상 커밋을 찾습니다.

그 다음 공통 조상 이후의 feature 브랜치 커밋들을 임시 영역에 저장하고, feature 브랜치의 HEAD를 origin/main의 최신 커밋으로 이동시킵니다. 마지막으로 임시 저장된 커밋들을 하나씩 다시 적용합니다.

이 과정에서 각 커밋이 새로운 base 위에서 재적용되므로, 충돌이 발생할 수 있습니다. 세 번째 단계로, 충돌이 발생하면 Git이 rebase를 일시 중단하고 여러분에게 해결을 요청합니다.

충돌하는 파일을 수정한 후 git add로 스테이징하고 git rebase --continue로 다음 커밋으로 진행합니다. 모든 커밋이 성공적으로 재적용되면, feature 브랜치가 main의 최신 커밋 위에 일직선으로 배치됩니다.

마지막으로 git push --force-with-lease로 원격 저장소에 반영하는데, --force-with-lease는 다른 사람이 이미 푸시한 내용이 있으면 실패하므로 --force보다 안전합니다. 여러분이 이 코드를 사용하면 복잡하게 얽힌 브랜치 히스토리를 깔끔한 일직선으로 정리할 수 있습니다.

코드 리뷰 시 변경사항을 순차적으로 확인할 수 있고, git log가 훨씬 읽기 쉬워지며, 나중에 특정 기능을 되돌리거나 디버깅할 때도 훨씬 수월합니다. 특히 오픈소스 프로젝트나 엄격한 히스토리 관리를 요구하는 팀에서는 rebase가 필수적입니다.

실전 팁

💡 공개된(push된) 브랜치에서는 rebase를 피하세요. 다른 팀원이 이미 기반으로 작업 중일 수 있어 히스토리가 꼬일 수 있습니다. rebase는 자신만 사용하는 feature 브랜치에서만 사용하는 것이 안전합니다.

💡 --force 대신 --force-with-lease를 사용하세요. 이 옵션은 원격 브랜치가 여러분이 알고 있는 상태와 같을 때만 푸시를 허용하므로, 다른 사람의 작업을 실수로 덮어쓰는 것을 방지할 수 있습니다.

💡 rebase 중 충돌이 너무 많이 발생하면 git rebase --abort로 취소하고 merge를 고려하세요. 때로는 merge가 더 적절한 선택일 수 있으며, 무리하게 rebase를 진행하다 히스토리를 망칠 수 있습니다.

💡 rebase 전에 백업 브랜치를 만들어두세요. git branch backup-feature로 현재 상태를 저장해두면, rebase가 잘못되어도 언제든지 되돌릴 수 있습니다.

💡 git config pull.rebase true로 설정하면 git pull 시 자동으로 rebase가 적용됩니다. 이렇게 하면 매번 수동으로 rebase할 필요 없이 항상 깔끔한 히스토리를 유지할 수 있습니다.


2. Interactive Rebase

시작하며

여러분이 feature 개발을 완료하고 PR을 올렸는데, 리뷰어가 "커밋이 너무 많고 메시지가 'WIP', 'fix typo', 'oops' 같은 것들이라 히스토리를 이해하기 어렵네요"라고 피드백을 준 경험이 있나요? 개발 과정에서는 작은 단위로 자주 커밋하는 것이 좋지만, 최종적으로는 논리적으로 의미 있는 단위로 정리해야 합니다.

이런 문제는 특히 장기 프로젝트나 복잡한 기능 개발에서 자주 발생합니다. 개발하면서 "일단 커밋하고 나중에 정리하자"고 생각하지만, 막상 정리하려니 어떻게 해야 할지 막막합니다.

여러 커밋을 하나로 합치거나, 순서를 바꾸거나, 메시지를 수정해야 하는데 방법을 모르는 경우가 많죠. 바로 이럴 때 필요한 것이 Interactive Rebase입니다.

이 기능을 사용하면 커밋 히스토리를 마치 텍스트 에디터에서 문장을 편집하듯이 자유롭게 수정, 병합, 삭제, 재정렬할 수 있습니다. 마스터하면 PR 전에 히스토리를 완벽하게 다듬어 전문적인 개발자의 모습을 보여줄 수 있습니다.

개요

간단히 말해서, Interactive Rebase는 여러 커밋을 대화형 인터페이스에서 편집할 수 있게 해주는 Git의 강력한 기능입니다. 일반 rebase가 커밋의 위치를 이동하는 것이라면, interactive rebase는 커밋의 내용, 순서, 메시지까지 모두 수정할 수 있습니다.

왜 이 기능이 필요한지 실무 관점에서 설명하자면, 깔끔한 커밋 히스토리는 팀의 코드 품질과 협업 효율성을 크게 높입니다. 각 커밋이 하나의 논리적 변경사항을 담고 있으면, 코드 리뷰가 쉬워지고, git blame으로 버그의 원인을 추적하기 쉬우며, 특정 기능만 되돌리거나 체리픽하기도 편합니다.

예를 들어, "사용자 인증 기능 추가"라는 하나의 논리적인 커밋이 있다면, 나중에 이 기능에 버그가 있을 때 정확히 어떤 변경이 있었는지 한눈에 파악할 수 있습니다. 기존에는 잘못된 커밋을 수정하려면 새로운 커밋을 만들어 덮어쓰거나, 복잡한 명령어들을 여러 번 실행해야 했다면, interactive rebase를 사용하면 텍스트 에디터에서 todo 리스트를 수정하듯이 직관적으로 커밋들을 관리할 수 있습니다.

Interactive Rebase의 핵심 특징은 세 가지입니다. 첫째, 여러 커밋을 하나로 합칠 수 있습니다(squash).

둘째, 커밋 순서를 자유롭게 변경할 수 있습니다. 셋째, 커밋 메시지를 수정하거나 커밋 내용 자체를 수정할 수 있습니다(edit).

이러한 특징들이 중요한 이유는 개발 과정의 혼란스러운 히스토리를 최종 제출 전에 깔끔한 스토리로 다듬을 수 있기 때문입니다.

코드 예제

# 최근 5개 커밋을 대화형으로 편집
git rebase -i HEAD~5

# 또는 특정 커밋 이후의 모든 커밋 편집
git rebase -i abc1234

# 에디터가 열리면 다음과 같은 내용이 표시됨:
# pick abc1234 Add user authentication
# pick def5678 Fix typo
# pick ghi9012 Add password validation
# pick jkl3456 WIP
# pick mno7890 Update tests

# 커밋을 합치려면 'pick''squash'로 변경:
# pick abc1234 Add user authentication
# squash def5678 Fix typo
# squash ghi9012 Add password validation
# pick jkl3456 Add login feature
# pick mno7890 Update tests

# 커밋 메시지를 수정하려면 'pick''reword'로:
# reword abc1234 Add user authentication

# 커밋을 삭제하려면 해당 줄을 삭제하거나 'drop'으로 변경

설명

이것이 하는 일: Interactive Rebase는 지정한 범위의 커밋들을 텍스트 에디터에서 편집 가능한 리스트로 보여주고, 여러분이 원하는 대로 수정한 후 그 지시사항에 따라 히스토리를 재구성합니다. 마치 비디오 편집처럼 커밋이라는 "씬"들을 자르고, 붙이고, 순서를 바꿀 수 있습니다.

첫 번째 단계로, git rebase -i HEAD~5를 실행하면 Git이 설정된 텍스트 에디터(보통 vim이나 nano)를 열어줍니다. 이때 표시되는 것은 최근 5개 커밋의 리스트인데, 각 줄은 "명령어 커밋해시 커밋메시지" 형식입니다.

HEAD~5는 "현재로부터 5개 전 커밋"을 의미하며, 이 커밋부터 현재까지의 모든 커밋이 편집 대상이 됩니다. 에디터 하단에는 사용 가능한 명령어들(pick, reword, edit, squash, fixup, drop 등)에 대한 설명이 주석으로 표시됩니다.

두 번째 단계로, 여러분이 원하는 작업에 맞게 명령어를 변경합니다. 예를 들어 'fix typo'라는 의미 없는 커밋을 이전 커밋에 합치려면 해당 줄의 'pick'을 'squash'로 바꾸면 됩니다.

squash를 사용하면 해당 커밋이 바로 위 커밋과 합쳐지며, 나중에 합쳐진 커밋의 메시지를 편집할 기회가 주어집니다. 'reword'를 사용하면 커밋 내용은 그대로 두고 메시지만 변경할 수 있고, 'edit'을 사용하면 해당 시점에서 rebase가 멈춰 파일을 수정하고 git commit --amend로 커밋을 변경할 수 있습니다.

줄의 순서를 바꾸면 커밋 순서도 그대로 바뀝니다. 세 번째 단계로, 에디터를 저장하고 닫으면 Git이 여러분의 지시사항을 하나씩 실행합니다.

squash나 reword를 사용했다면 중간에 다시 에디터가 열려 커밋 메시지를 편집할 수 있습니다. 이때 여러 커밋 메시지가 합쳐진 내용이 표시되므로, 불필요한 부분을 삭제하고 의미 있는 하나의 메시지로 정리하면 됩니다.

모든 작업이 완료되면 feature 브랜치의 히스토리가 여러분이 지시한 대로 깔끔하게 정리됩니다. 여러분이 이 기능을 사용하면 PR을 올리기 전에 개발 과정의 시행착오를 모두 정리하고, 논리적으로 의미 있는 커밋들만 남길 수 있습니다.

리뷰어는 각 커밋이 무엇을 하는지 명확히 알 수 있어 리뷰가 빨라지고, 나중에 이 기능을 수정하거나 되돌려야 할 때도 훨씬 쉽습니다. 특히 오픈소스 기여나 엄격한 커밋 규칙을 가진 팀에서는 interactive rebase가 필수 스킬입니다.

실전 팁

💡 squash 대신 fixup을 사용하면 커밋 메시지 편집 없이 자동으로 이전 커밋에 합쳐집니다. 'fix typo' 같은 의미 없는 커밋을 빠르게 정리할 때 유용합니다.

💡 interactive rebase 중에 막히면 git rebase --abort로 언제든지 취소할 수 있습니다. 실험적으로 여러 가지 시도를 해보고, 마음에 들지 않으면 취소하고 다시 시작하세요.

💡 커밋을 너무 많이 합치면 나중에 문제가 생겼을 때 원인을 찾기 어렵습니다. 하나의 커밋은 하나의 논리적 변경사항만 담도록 적절히 분리하세요. 예를 들어 "기능 추가"와 "테스트 추가"는 별도 커밋으로 유지하는 것이 좋습니다.

💡 git log --oneline --graph로 현재 히스토리를 확인한 후 interactive rebase를 시작하세요. 어떤 커밋들이 있는지 미리 파악하면 더 효율적으로 정리할 수 있습니다.

💡 autosquash 기능을 활용하세요. 커밋 메시지를 fixup! 이전커밋메시지 형식으로 작성한 후 git rebase -i --autosquash를 실행하면 자동으로 해당 커밋을 찾아 합쳐줍니다.


3. Git Stash

시작하며

여러분이 feature 브랜치에서 열심히 코딩하고 있는데, 갑자기 팀장님이 "긴급 버그가 발생했어요! 지금 당장 hotfix 브랜치로 가서 수정해주세요"라고 말씀하시는 상황을 경험해본 적 있나요?

문제는 현재 작업 중인 코드가 아직 커밋하기에는 불완전하다는 것입니다. 반쪽짜리 코드를 커밋하자니 히스토리가 지저분해지고, 그렇다고 변경사항을 버리자니 몇 시간 동안 작업한 내용이 날아가버립니다.

이런 문제는 실무에서 거의 매일 발생합니다. 긴급 요청, 우선순위 변경, 다른 브랜치에서 코드 확인 요청 등 여러 이유로 현재 작업을 임시로 중단하고 다른 작업으로 전환해야 하는 상황이 생깁니다.

특히 여러 작업을 동시에 진행하는 개발자라면 이런 컨텍스트 스위칭이 빈번하게 일어나죠. 바로 이럴 때 필요한 것이 Git Stash입니다.

stash를 사용하면 현재 작업 중인 변경사항을 스택에 임시 저장하고 워킹 디렉토리를 깨끗하게 만들어, 다른 브랜치로 자유롭게 이동할 수 있습니다. 작업을 마치고 돌아오면 저장한 내용을 다시 불러와 중단했던 지점부터 이어서 작업할 수 있습니다.

개요

간단히 말해서, Git Stash는 현재 작업 중인 변경사항(staged와 unstaged 모두)을 임시 저장소에 보관하고, 워킹 디렉토리를 마지막 커밋 상태로 되돌리는 기능입니다. 마치 책갈피처럼 현재 위치를 표시해두고 다른 곳으로 갔다가 돌아오는 것입니다.

왜 이 기능이 필요한지 실무 관점에서 설명하자면, 현대 소프트웨어 개발은 매우 동적입니다. 한 가지 작업만 집중해서 끝까지 하는 경우는 드물고, 여러 작업 사이를 빠르게 오가야 합니다.

긴급 버그 수정, 코드 리뷰 대응, 다른 브랜치의 코드 확인 등 다양한 이유로 컨텍스트를 전환해야 하는데, 매번 불완전한 코드를 커밋하면 히스토리가 엉망이 됩니다. 예를 들어, 새로운 결제 시스템을 개발하던 중 기존 로그인 버그를 급하게 수정해야 한다면, stash로 결제 시스템 작업을 보관하고 로그인 버그를 처리한 후 다시 결제 시스템으로 돌아올 수 있습니다.

기존에는 임시 브랜치를 만들어 커밋하거나, 변경사항을 다른 곳에 복사해두는 등의 번거로운 방법을 사용했다면, stash를 사용하면 한 줄의 명령어로 즉시 작업을 보관하고 복원할 수 있습니다. Git Stash의 핵심 특징은 세 가지입니다.

첫째, 스택 구조로 여러 stash를 저장할 수 있어 여러 작업을 동시에 보관할 수 있습니다. 둘째, 브랜치와 무관하게 작동하므로 다른 브랜치에서도 stash를 적용할 수 있습니다.

셋째, 선택적으로 적용하거나 삭제할 수 있어 유연하게 관리할 수 있습니다. 이러한 특징들이 중요한 이유는 멀티태스킹이 필수인 현대 개발 환경에서 작업 흐름을 끊지 않고 효율적으로 여러 작업을 관리할 수 있게 해주기 때문입니다.

코드 예제

# 현재 변경사항을 stash에 저장
git stash

# 설명과 함께 저장 (나중에 찾기 쉬움)
git stash save "WIP: payment gateway integration"

# untracked 파일도 함께 저장
git stash -u

# 저장된 stash 목록 확인
git stash list

# 가장 최근 stash를 적용 (stash는 유지됨)
git stash apply

# 가장 최근 stash를 적용하고 삭제
git stash pop

# 특정 stash를 적용
git stash apply stash@{2}

# 특정 stash 삭제
git stash drop stash@{0}

# 모든 stash 삭제
git stash clear

설명

이것이 하는 일: Git Stash는 현재 수정한 파일들(staged와 unstaged)을 스택이라는 임시 저장소에 보관하고, 워킹 디렉토리를 HEAD 커밋 상태로 초기화합니다. 스택 구조이므로 여러 번 stash할 수 있고, 나중에 필요할 때 순서대로 또는 선택적으로 꺼내 쓸 수 있습니다.

첫 번째 단계로, git stash 또는 git stash save "설명"을 실행하면 Git이 현재 변경사항을 분석합니다. staged 영역의 파일들(git add한 파일)과 modified 상태의 파일들(아직 add하지 않은 파일)을 모두 스택의 맨 위에 저장합니다.

이때 기본적으로 untracked 파일(새로 만든 파일)은 저장되지 않으므로, 이들도 포함하려면 -u 옵션을 사용해야 합니다. 설명을 추가하면 나중에 git stash list로 확인할 때 어떤 작업이었는지 쉽게 알 수 있습니다.

두 번째 단계로, stash가 완료되면 워킹 디렉토리가 깨끗해져서 다른 브랜치로 전환할 수 있습니다. 예를 들어 git checkout hotfix-branch로 이동해서 긴급 버그를 수정하고 커밋한 후, 다시 원래 브랜치로 돌아올 수 있습니다.

git stash list를 실행하면 저장된 모든 stash가 stash@{0}, stash@{1} 같은 형식으로 표시됩니다. 숫자가 작을수록 최근에 저장한 것입니다.

세 번째 단계로, 원래 작업으로 돌아왔을 때 git stash pop을 실행하면 가장 최근 stash(stash@{0})의 내용이 현재 워킹 디렉토리에 적용되고, 해당 stash는 스택에서 제거됩니다. 만약 여러 stash 중 특정한 것을 적용하려면 git stash apply stash@{2} 같은 형식으로 지정할 수 있습니다.

apply는 stash를 적용하되 스택에서 제거하지 않으므로, 같은 내용을 여러 브랜치에 적용해야 할 때 유용합니다. 충돌이 발생하면 일반적인 merge 충돌처럼 수동으로 해결하면 됩니다.

여러분이 이 기능을 사용하면 작업 컨텍스트를 빠르게 전환하면서도 진행 중인 작업을 잃지 않을 수 있습니다. 긴급 요청에 즉시 대응할 수 있고, 여러 실험적인 변경을 동시에 시도해볼 수 있으며, 불완전한 코드로 히스토리를 오염시키지 않을 수 있습니다.

특히 빠르게 변하는 스타트업 환경이나 여러 프로젝트를 동시에 진행하는 개발자에게는 필수 기능입니다.

실전 팁

💡 git stash -u로 untracked 파일도 함께 저장하세요. 새로 만든 파일을 깜빡하고 stash하면 다른 브랜치로 갔을 때 그 파일들이 그대로 남아있어 혼란스러울 수 있습니다.

💡 stash에 의미 있는 설명을 추가하세요. git stash save "WIP: refactoring authentication logic"처럼 작성하면 몇 주 후에 stash 목록을 봤을 때도 무엇인지 바로 알 수 있습니다.

💡 git stash pop 대신 git stash apply를 먼저 사용해보세요. apply는 stash를 유지하므로, 적용 후 문제가 있으면 다시 되돌릴 수 있습니다. 확인 후 git stash drop으로 삭제하면 더 안전합니다.

💡 git stash branch new-branch-name을 사용하면 stash 내용으로 새 브랜치를 만들 수 있습니다. 임시로 저장했던 작업이 생각보다 중요해졌을 때 유용합니다.

💡 stash를 너무 많이 쌓아두지 마세요. 정기적으로 git stash list를 확인하고 불필요한 것은 git stash clear로 정리하세요. 오래된 stash는 나중에 적용할 때 충돌이 많이 발생할 수 있습니다.


4. Git Cherry-pick

시작하며

여러분이 develop 브랜치에서 새로운 기능을 개발했는데, 갑자기 "이 기능 중에서 로그 개선 부분만 먼저 프로덕션에 반영해주세요"라는 요청을 받은 경험이 있나요? 전체 브랜치를 merge하기에는 아직 테스트가 덜 된 다른 커밋들이 포함되어 있고, 해당 부분만 수동으로 복사하자니 실수할 가능성이 높습니다.

이런 문제는 실무에서 매우 흔합니다. 특히 여러 기능을 동시에 개발하는 브랜치에서 특정 버그 수정이나 특정 기능만 선택적으로 다른 브랜치에 적용해야 할 때가 있습니다.

또는 실수로 잘못된 브랜치에 커밋했거나, 다른 팀원의 브랜치에서 유용한 커밋을 가져오고 싶을 때도 있죠. 전체 merge는 과하고, 수동 복사는 번거롭고 오류가 발생하기 쉽습니다.

바로 이럴 때 필요한 것이 Git Cherry-pick입니다. cherry-pick을 사용하면 원하는 커밋만 "체리처럼 따서" 현재 브랜치에 적용할 수 있습니다.

커밋 하나하나가 독립적인 변경사항이므로, 필요한 것만 선택해서 가져올 수 있는 것이죠.

개요

간단히 말해서, Git Cherry-pick은 다른 브랜치의 특정 커밋을 선택해서 현재 브랜치에 복사하여 적용하는 기능입니다. 원본 커밋은 그대로 두고, 같은 변경사항을 가진 새로운 커밋을 현재 브랜치에 만드는 것입니다.

왜 이 기능이 필요한지 실무 관점에서 설명하자면, 소프트웨어 개발은 선형적이지 않습니다. 여러 브랜치에서 동시에 개발이 진행되고, 우선순위가 갑자기 바뀌고, 특정 수정사항만 긴급하게 배포해야 하는 상황이 자주 발생합니다.

예를 들어, feature 브랜치에서 10개의 커밋을 작업했는데 그중 보안 취약점을 수정한 커밋 하나만 즉시 main 브랜치에 적용해야 한다면, 전체 브랜치를 merge할 수 없고 해당 커밋만 선택적으로 가져와야 합니다. 또는 다른 개발자가 만든 유틸리티 함수가 유용해서 그 커밋만 내 브랜치에도 적용하고 싶을 때도 있습니다.

기존에는 해당 변경사항을 수동으로 복사해서 새로운 커밋을 만들어야 했다면, cherry-pick을 사용하면 커밋 해시만 알면 한 줄의 명령어로 정확히 같은 변경사항을 적용할 수 있습니다. Git Cherry-pick의 핵심 특징은 세 가지입니다.

첫째, 특정 커밋만 선택적으로 적용할 수 있어 유연합니다. 둘째, 원본 커밋은 그대로 유지되고 새로운 커밋이 생성되므로 안전합니다.

셋째, 여러 커밋을 한 번에 cherry-pick할 수도 있어 효율적입니다. 이러한 특징들이 중요한 이유는 복잡한 브랜치 전략을 사용하는 팀에서 필요한 변경사항만 정확히 가져올 수 있게 해주기 때문입니다.

코드 예제

# 현재 main 브랜치에 있다고 가정
git checkout main

# 다른 브랜치의 특정 커밋 하나를 가져오기
# 주석: abc1234 커밋의 변경사항이 현재 브랜치에 새 커밋으로 적용됨
git cherry-pick abc1234

# 여러 커밋을 한 번에 가져오기
git cherry-pick abc1234 def5678 ghi9012

# 범위로 커밋 가져오기 (abc1234 이후부터 def5678까지)
git cherry-pick abc1234..def5678

# 충돌이 발생하면 해결 후 계속 진행
git add .
git cherry-pick --continue

# cherry-pick 취소
git cherry-pick --abort

# 커밋하지 않고 변경사항만 적용 (수정 후 커밋 가능)
git cherry-pick -n abc1234

설명

이것이 하는 일: Git Cherry-pick은 지정한 커밋의 변경사항(diff)을 현재 브랜치의 HEAD에 적용하여 새로운 커밋을 만듭니다. 원본 커밋은 그대로 남아있고, 같은 내용의 새로운 커밋이 현재 브랜치에 생성되는 것입니다.

마치 체리를 따듯이 원하는 커밋만 골라서 가져온다는 의미입니다. 첫 번째 단계로, cherry-pick하려는 커밋을 찾아야 합니다.

git log 또는 git log --oneline --graph --all로 전체 히스토리를 보면서 필요한 커밋의 해시를 확인합니다. 예를 들어 feature 브랜치에서 "Fix critical security vulnerability" 커밋이 abc1234라는 해시를 가지고 있다면, 이 해시를 복사해둡니다.

그 다음 cherry-pick을 적용할 대상 브랜치(보통 main이나 hotfix)로 전환합니다. 두 번째 단계로, git cherry-pick abc1234를 실행하면 Git이 abc1234 커밋과 그 부모 커밋 사이의 차이(diff)를 계산합니다.

그 차이를 현재 브랜치의 HEAD에 적용하려고 시도합니다. 충돌이 없다면 자동으로 새로운 커밋이 생성되며, 원본 커밋과 같은 메시지를 가지지만 다른 해시를 가집니다.

충돌이 발생하면 Git이 cherry-pick을 일시 중단하고 충돌하는 파일들을 표시합니다. 일반적인 merge 충돌처럼 파일을 수정한 후 git add로 스테이징하고 git cherry-pick --continue로 진행하면 됩니다.

세 번째 단계로, 여러 커밋을 한 번에 cherry-pick하려면 해시를 공백으로 구분해서 나열하거나, 범위를 지정할 수 있습니다. git cherry-pick abc1234..def5678은 abc1234(미포함)부터 def5678(포함)까지의 모든 커밋을 순서대로 적용합니다.

이때 각 커밋마다 충돌이 발생할 수 있으므로, 각각 해결하면서 진행해야 합니다. -n 옵션을 사용하면 커밋을 즉시 만들지 않고 변경사항만 워킹 디렉토리에 적용하므로, 여러 cherry-pick을 합쳐서 하나의 커밋으로 만들거나 추가 수정을 한 후 커밋할 수 있습니다.

여러분이 이 기능을 사용하면 복잡한 브랜치 상황에서도 필요한 변경사항만 정확히 가져올 수 있습니다. 긴급 버그 수정을 여러 브랜치에 빠르게 적용하거나, 실험적인 브랜치에서 성공한 부분만 메인 코드에 통합하거나, 잘못된 브랜치에 커밋한 것을 올바른 브랜치로 옮기는 등 다양한 상황에서 유용합니다.

다만 너무 자주 사용하면 같은 변경사항이 여러 브랜치에 중복되어 나중에 merge 시 혼란스러울 수 있으므로, 정말 필요한 경우에만 신중하게 사용하세요.

실전 팁

💡 cherry-pick한 후에는 원본 브랜치도 업데이트 계획을 세우세요. 같은 수정이 두 브랜치에 다른 커밋으로 존재하면 나중에 merge할 때 중복 충돌이 발생할 수 있습니다.

💡 -x 옵션을 사용하면 커밋 메시지에 원본 커밋 정보가 자동으로 추가됩니다. git cherry-pick -x abc1234처럼 사용하면 "(cherry picked from commit abc1234)" 같은 메시지가 붙어 추적이 쉬워집니다.

💡 여러 커밋을 cherry-pick할 때는 순서가 중요합니다. 나중 커밋이 이전 커밋에 의존한다면 순서대로 가져와야 충돌을 최소화할 수 있습니다.

💡 cherry-pick 대신 merge를 고려하세요. 여러 커밋을 연속으로 가져와야 한다면 cherry-pick보다 브랜치를 merge하는 것이 더 명확하고 히스토리 관리에도 좋습니다. cherry-pick은 정말 특정 커밋만 필요할 때 사용하세요.

💡 충돌이 자주 발생한다면 원본 커밋이 현재 브랜치와 너무 달라진 것입니다. 이럴 때는 수동으로 변경사항을 적용하는 것이 더 안전할 수 있습니다.


5. Git Reflog

시작하며

여러분이 실수로 중요한 커밋을 삭제하거나, rebase를 잘못해서 브랜치가 엉망이 되거나, reset을 너무 세게 해서 모든 작업이 날아간 것처럼 보이는 경험을 해본 적 있나요? "아, 이제 끝났다.

몇 시간 동안의 작업이 날아갔어..."라고 절망하는 그 순간, 사실 Git은 모든 것을 기억하고 있습니다. 이런 문제는 Git을 배우는 과정에서 거의 모든 개발자가 겪습니다.

강력한 명령어를 실수로 잘못 사용하거나, 옵션을 착각해서 예상치 못한 결과가 나오거나, 브랜치를 잘못 삭제하는 등의 실수는 피할 수 없습니다. 특히 git reset --hardgit rebase같은 히스토리를 변경하는 명령어는 사용하기 조심스럽지만, 실수는 언제든 일어날 수 있습니다.

바로 이럴 때 필요한 것이 Git Reflog입니다. reflog는 Git의 "타임머신"이자 "안전망"으로, HEAD가 가리켰던 모든 위치를 시간순으로 기록합니다.

여러분이 실수로 뭔가를 삭제했다고 생각해도, reflog를 보면 그 시점으로 돌아갈 수 있는 커밋 해시를 찾을 수 있습니다.

개요

간단히 말해서, Git Reflog(Reference Log)는 로컬 저장소에서 HEAD와 브랜치 참조가 이동한 모든 이력을 시간순으로 기록하는 Git의 안전장치입니다. commit, checkout, reset, rebase 등 거의 모든 작업이 기록되므로, 실수로 무언가를 잃어버렸을 때 복구할 수 있습니다.

왜 이 기능이 필요한지 실무 관점에서 설명하자면, 개발하다 보면 실수는 불가피합니다. 특히 히스토리를 변경하는 고급 Git 명령어를 사용할 때 예상치 못한 결과가 나올 수 있습니다.

하지만 걱정할 필요 없습니다. Git은 기본적으로 데이터를 거의 삭제하지 않고, reflog에 모든 변경 이력을 보관하기 때문입니다.

예를 들어, interactive rebase 중에 실수로 중요한 커밋을 drop했다면, reflog에서 rebase 전 상태를 찾아 그 시점으로 돌아갈 수 있습니다. 또는 브랜치를 삭제했다가 "아, 그 브랜치 아직 필요했는데!"라고 깨달았을 때도 reflog로 복구할 수 있습니다.

기존에는 실수를 하면 백업이나 다른 팀원의 저장소에서 복구해야 했다면, reflog를 알고 있으면 대부분의 실수를 스스로 즉시 복구할 수 있습니다. Git Reflog의 핵심 특징은 세 가지입니다.

첫째, 로컬 저장소의 모든 HEAD 이동을 기록하므로 완전한 작업 이력을 추적할 수 있습니다. 둘째, 커밋이 "삭제"되어 git log에 보이지 않아도 reflog에는 남아있어 복구할 수 있습니다.

셋째, 기본적으로 90일 동안 보관되므로 충분한 복구 시간이 있습니다. 이러한 특징들이 중요한 이유는 Git을 자신있게 사용할 수 있게 해주는 안전망이 되기 때문입니다.

코드 예제

# reflog 확인 (최근 HEAD 이동 이력)
git reflog

# 더 자세한 정보와 함께 확인
git reflog show --all

# 특정 브랜치의 reflog 확인
git reflog show main

# reflog에서 원하는 시점 찾기
# 출력 예시: abc1234 HEAD@{2}: commit: Add user authentication
# abc1234가 복구하려는 커밋 해시

# 해당 커밋으로 새 브랜치 만들기 (안전한 방법)
git checkout -b recovery-branch HEAD@{2}

# 또는 현재 브랜치를 해당 시점으로 되돌리기
git reset --hard HEAD@{2}

# 삭제된 브랜치 복구하기
# 1. reflog에서 해당 브랜치의 마지막 커밋 찾기
git reflog | grep "deleted-branch-name"
# 2. 해당 커밋으로 브랜치 재생성
git checkout -b deleted-branch-name abc1234

설명

이것이 하는 일: Git Reflog는 로컬 저장소에서 일어나는 거의 모든 작업을 시간순으로 기록하는 로그 시스템입니다. commit을 만들거나, 브랜치를 이동하거나, rebase를 하거나, reset을 하는 등 HEAD가 가리키는 커밋이 바뀔 때마다 그 시점을 기록합니다.

이 기록은 로컬에만 저장되며 원격 저장소에는 공유되지 않습니다. 첫 번째 단계로, git reflog를 실행하면 최근 HEAD 이동 이력이 시간 역순으로 표시됩니다.

각 항목은 "해시 HEAD@{n}: 작업설명" 형식으로 나타나는데, HEAD@{0}이 가장 최근이고 숫자가 클수록 과거입니다. 예를 들어 abc1234 HEAD@{2}: commit: Add user authentication이라는 항목은 "2단계 전에 user authentication을 추가하는 커밋을 만들었고, 그때 HEAD가 abc1234를 가리켰다"는 의미입니다.

이 로그를 살펴보면 언제 무엇을 했는지 완전한 타임라인을 볼 수 있습니다. 두 번째 단계로, 복구하려는 시점을 찾았다면 그 시점의 커밋 해시나 HEAD@{n} 표현을 사용해 돌아갈 수 있습니다.

가장 안전한 방법은 git checkout -b recovery-branch HEAD@{2}처럼 새 브랜치를 만드는 것입니다. 이렇게 하면 현재 브랜치는 그대로 두고 복구된 상태를 새 브랜치에서 확인할 수 있습니다.

확인 후 문제없으면 그 브랜치를 사용하거나 원래 브랜치에 merge할 수 있습니다. 더 과감하게 현재 브랜치를 직접 되돌리려면 git reset --hard HEAD@{2}를 사용하지만, 이 경우 현재 작업이 사라질 수 있으니 주의해야 합니다.

세 번째 단계로, 브랜치를 삭제했을 때 복구하는 방법도 비슷합니다. git reflog | grep "branch-name"으로 삭제된 브랜치와 관련된 항목을 찾습니다.

브랜치 삭제 직전의 커밋 해시를 찾아서 git checkout -b branch-name 커밋해시로 브랜치를 재생성하면 됩니다. reflog는 기본적으로 90일간 보관되므로, 최근에 삭제한 것이라면 거의 확실히 복구할 수 있습니다.

여러분이 이 기능을 알고 있으면 Git을 훨씬 자신있게 사용할 수 있습니다. "이 명령어를 쓰면 뭔가 잘못될 것 같은데..."라는 두려움 없이 고급 기능들을 시도해볼 수 있고, 실제로 실수를 해도 대부분 복구할 수 있습니다.

reflog는 Git의 가장 강력한 안전장치이며, 많은 개발자들이 "Git이 내 커리어를 구했다"고 말하는 이유 중 하나입니다. 다만 reflog는 로컬에만 있으므로, 저장소를 완전히 삭제하면 복구할 수 없다는 점은 기억하세요.

실전 팁

💡 복구하기 전에 항상 새 브랜치를 만들어 확인하세요. git reset --hard로 바로 되돌리면 현재 작업을 잃을 수 있으므로, git checkout -b backup-check HEAD@{n}으로 먼저 확인하는 것이 안전합니다.

💡 git reflog expire --expire=30.days --all로 오래된 reflog를 정리할 수 있지만, 굳이 정리할 필요는 없습니다. 디스크 공간을 거의 차지하지 않고, 오래된 기록이 나중에 유용할 수도 있습니다.

💡 팀원의 실수를 도와줄 때도 reflog를 알려주세요. "모든 게 날아갔어요!"라고 당황하는 동료에게 reflog로 복구하는 법을 가르쳐주면 영웅이 될 수 있습니다.

💡 git refloggit log의 차이를 이해하세요. git log는 커밋 히스토리를 보여주지만, reflog는 여러분의 작업 이력을 보여줍니다. reset으로 "삭제된" 커밋은 log에는 없지만 reflog에는 남아있습니다.

💡 중요한 작업 전에는 git reflog > backup-reflog.txt로 reflog를 파일로 저장해두세요. 정말 최악의 상황(저장소가 완전히 망가진 경우)에도 커밋 해시를 알면 복구할 방법이 있을 수 있습니다.


6. Git Bisect

시작하며

여러분이 프로덕션에서 버그를 발견했는데, 며칠 전까지는 분명히 정상 작동했던 기능인 경험이 있나요? 그 사이에 100개의 커밋이 추가되었고, 어느 커밋에서 버그가 시작되었는지 찾기 위해 커밋을 하나하나 체크아웃해서 테스트하는 것은 너무 비효율적입니다.

"도대체 어디서부터 잘못된 거지?"라는 질문에 답하기 위해 몇 시간을 허비할 수도 있습니다. 이런 문제는 특히 대규모 프로젝트나 여러 개발자가 동시에 작업하는 환경에서 자주 발생합니다.

버그의 증상은 명확한데 원인을 모르고, 최근 변경사항이 너무 많아 어디서부터 찾아야 할지 막막합니다. 수동으로 커밋을 하나씩 테스트하면 시간이 너무 오래 걸리고, 잘못된 커밋을 놓칠 수도 있습니다.

바로 이럴 때 필요한 것이 Git Bisect입니다. bisect는 "이진 탐색(binary search)" 알고리즘을 사용해 자동으로 문제가 발생한 커밋을 찾아줍니다.

100개의 커밋이 있어도 7번 정도만 테스트하면 정확한 커밋을 찾을 수 있습니다. 마치 "높거나 낮거나" 게임처럼 Git이 중간 커밋을 체크아웃해주고, 여러분은 "좋음(good)" 또는 "나쁨(bad)"만 알려주면 됩니다.

개요

간단히 말해서, Git Bisect는 이진 탐색 알고리즘을 사용해 버그가 처음 발생한 커밋을 자동으로 찾아주는 디버깅 도구입니다. 정상 작동하는 커밋과 버그가 있는 커밋 사이를 절반씩 나누며 탐색하므로, 매우 빠르게 원인을 찾을 수 있습니다.

왜 이 기능이 필요한지 실무 관점에서 설명하자면, 소프트웨어 프로젝트가 커질수록 회귀 버그(regression bug)를 추적하기 어려워집니다. 특히 "이전에는 작동했는데 지금은 안 되는" 문제는 수십 또는 수백 개의 커밋 중 어디서 발생했는지 찾기 매우 힘듭니다.

예를 들어, 사용자가 "2주 전에는 결제가 잘 됐는데 지금은 안 돼요"라고 신고했고, 그 사이에 150개의 커밋이 있다면, 수동으로 찾으려면 몇 시간이 걸립니다. 하지만 bisect를 사용하면 log₂(150) ≈ 8번의 테스트만으로 정확한 커밋을 찾을 수 있습니다.

기존에는 커밋을 하나하나 체크아웃해서 테스트하거나, git log를 보면서 "이 커밋이 수상해 보이는데"라고 추측해야 했다면, bisect를 사용하면 체계적이고 효율적으로 확실한 답을 찾을 수 있습니다. Git Bisect의 핵심 특징은 세 가지입니다.

첫째, 이진 탐색 알고리즘으로 탐색 횟수를 최소화하여 시간을 크게 절약합니다. 둘째, 테스트 스크립트를 제공하면 완전 자동으로 실행할 수 있어 사람이 개입할 필요가 없습니다.

셋째, 정확한 커밋을 찾아주므로 추측이 아닌 확실한 원인 파악이 가능합니다. 이러한 특징들이 중요한 이유는 복잡한 버그를 과학적으로 추적하여 빠르게 수정할 수 있게 해주기 때문입니다.

코드 예제

# bisect 시작
git bisect start

# 현재 커밋이 버그가 있음을 표시 (bad)
git bisect bad

# 정상 작동하는 과거 커밋을 표시 (good)
# 주석: 이 커밋에서는 버그가 없었음
git bisect good v2.0.0

# Git이 자동으로 중간 커밋으로 체크아웃함
# 주석: 이제 테스트를 실행하고 결과에 따라 good/bad 표시

# 테스트 후 버그가 있으면
git bisect bad

# 테스트 후 정상이면
git bisect good

# 위 과정을 반복하면 Git이 최종적으로 첫 번째 bad 커밋을 찾아줌

# bisect 종료 (원래 브랜치로 돌아감)
git bisect reset

# 자동화: 테스트 스크립트로 자동 실행
git bisect start HEAD v2.0.0
git bisect run npm test

설명

이것이 하는 일: Git Bisect는 컴퓨터 과학의 이진 탐색 알고리즘을 커밋 히스토리에 적용합니다. 정상(good)과 비정상(bad) 두 지점 사이를 자동으로 이등분하면서, 여러분이 각 시점을 테스트하고 피드백을 주면 범위를 좁혀가며 정확한 문제 커밋을 찾아냅니다.

첫 번째 단계로, git bisect start로 bisect 모드를 시작하고, 현재 상태를 git bisect bad로 표시합니다. "지금은 버그가 있다"는 의미입니다.

그 다음 정상 작동하는 과거 시점(보통 태그나 커밋 해시)을 git bisect good v2.0.0 같은 형식으로 표시합니다. 이렇게 하면 Git은 good과 bad 사이에 몇 개의 커밋이 있는지 계산하고, "Bisecting: 75 revisions left to test after this" 같은 메시지를 보여주며 자동으로 중간 지점의 커밋으로 체크아웃합니다.

두 번째 단계로, Git이 체크아웃한 커밋에서 애플리케이션을 실행하거나 테스트를 돌려서 버그가 있는지 확인합니다. 예를 들어 npm start로 앱을 실행해보거나, npm test로 실패하는 테스트가 있는지 확인하거나, 수동으로 특정 기능을 테스트합니다.

버그가 여전히 존재하면 git bisect bad를 실행하여 "이 시점에도 버그가 있다"고 알려줍니다. 그러면 Git은 good과 현재 커밋 사이의 중간으로 이동합니다.

반대로 버그가 없으면 git bisect good을 실행하여 "이 시점은 정상이다"고 알려주고, Git은 현재 커밋과 bad 사이의 중간으로 이동합니다. 세 번째 단계로, 이 과정을 반복하면 탐색 범위가 절반씩 줄어들어 결국 하나의 커밋으로 좁혀집니다.

Git이 "abc1234 is the first bad commit"이라는 메시지를 보여주면 바로 그 커밋이 버그를 도입한 시점입니다. git show abc1234로 해당 커밋의 변경사항을 확인하면 무엇이 문제인지 알 수 있습니다.

작업이 끝나면 git bisect reset으로 bisect 모드를 종료하고 원래 브랜치로 돌아갑니다. 더 나아가, 테스트가 자동화되어 있다면 git bisect run npm test 같은 명령어로 완전 자동으로 실행할 수 있습니다.

Git이 각 커밋에서 테스트를 실행하고 종료 코드(0이면 good, 1이면 bad)를 보고 자동으로 good/bad를 판단합니다. 여러분이 이 기능을 사용하면 복잡한 회귀 버그를 빠르고 정확하게 추적할 수 있습니다.

추측이 아닌 체계적인 탐색으로 확실한 원인을 찾을 수 있고, 수동 작업 시간을 크게 줄일 수 있으며, 특히 테스트 자동화와 결합하면 거의 노력 없이 버그의 근원을 찾아낼 수 있습니다. 대규모 프로젝트에서 "언제부터 이 기능이 망가졌지?"라는 질문에 답하는 가장 효율적인 방법입니다.

실전 팁

💡 bisect 시작 전에 테스트 방법을 명확히 정의하세요. "버그가 있다"는 기준이 애매하면 good/bad 판단이 일관되지 않아 잘못된 결과가 나올 수 있습니다. 자동화된 테스트가 있으면 가장 좋습니다.

💡 git bisect skip으로 테스트할 수 없는 커밋은 건너뛸 수 있습니다. 빌드가 실패하거나 무관한 이유로 실행할 수 없는 커밋이 있을 때 유용합니다.

💡 bisect 중간에 코드를 수정하지 마세요. 현재 상태를 변경하면 탐색이 부정확해집니다. 오직 테스트만 실행하고 good/bad만 판단해야 합니다.

💡 git bisect log로 현재까지의 bisect 진행 상황을 확인할 수 있고, git bisect replay logfile로 이전 bisect를 재현할 수 있습니다. 복잡한 탐색을 반복해야 할 때 유용합니다.

💡 테스트 자동화가 되어 있다면 git bisect run을 적극 활용하세요. 몇 분만 기다리면 자동으로 결과가 나오므로, 다른 작업을 하면서도 병행할 수 있습니다.


7. Git Submodule

시작하며

여러분이 여러 프로젝트에서 공통으로 사용하는 라이브러리나 공유 컴포넌트를 관리하고 있는데, 각 프로젝트마다 코드를 복사해서 붙여넣는 경험이 있나요? 문제는 라이브러리를 업데이트할 때마다 모든 프로젝트에 수동으로 반영해야 하고, 버전이 달라지면서 일관성을 유지하기 어렵다는 것입니다.

또는 외부 라이브러리를 프로젝트에 포함시켜야 하는데 npm이나 pip 같은 패키지 매니저로 관리하기 어려운 경우도 있습니다. 이런 문제는 특히 마이크로서비스 아키텍처나 여러 관련 프로젝트를 동시에 관리하는 팀에서 자주 발생합니다.

공통 코드를 각 프로젝트에 복사하면 유지보수가 악몽이 되고, 한 곳에서 수정한 내용을 다른 곳에 반영하는 것을 잊어버리는 경우도 많습니다. "이 버그 수정을 다른 프로젝트에도 적용했던가?"라는 질문에 확신을 가질 수 없게 됩니다.

바로 이럴 때 필요한 것이 Git Submodule입니다. submodule을 사용하면 다른 Git 저장소를 현재 저장소 안에 하위 디렉토리로 포함시킬 수 있습니다.

마치 폴더처럼 보이지만 실제로는 독립적인 Git 저장소이므로, 각각 별도로 버전 관리되면서도 메인 프로젝트에 통합되어 있습니다.

개요

간단히 말해서, Git Submodule은 하나의 Git 저장소 안에 다른 Git 저장소를 중첩시켜 관리하는 기능입니다. 메인 프로젝트는 submodule의 특정 커밋을 가리키므로, 각 프로젝트가 독립적으로 버전 관리되면서도 함께 작동할 수 있습니다.

왜 이 기능이 필요한지 실무 관점에서 설명하자면, 대규모 소프트웨어 개발에서는 코드 재사용과 모듈화가 필수적입니다. 공통 라이브러리, 공유 컴포넌트, 플러그인, 테마 등을 여러 프로젝트에서 사용할 때 각각 독립적으로 개발하고 버전 관리하면서도, 메인 프로젝트에서는 필요한 버전을 정확히 지정할 수 있어야 합니다.

예를 들어, 여러분의 회사가 10개의 마이크로서비스를 운영하는데 모두 공통 인증 라이브러리를 사용한다면, 그 라이브러리를 별도 저장소로 관리하고 각 서비스에서 submodule로 포함시키면 됩니다. 라이브러리를 업데이트하면 각 서비스는 필요에 따라 업데이트를 적용할 수 있습니다.

기존에는 코드를 복사하거나 패키지 매니저에 의존해야 했다면, submodule을 사용하면 소스 코드 레벨에서 통합하면서도 독립적인 버전 관리를 유지할 수 있습니다. Git Submodule의 핵심 특징은 세 가지입니다.

첫째, 각 저장소가 독립적으로 관리되므로 submodule은 자체 커밋 히스토리를 가집니다. 둘째, 메인 프로젝트는 submodule의 특정 커밋을 참조하므로 버전을 정확히 제어할 수 있습니다.

셋째, 여러 프로젝트가 같은 submodule을 공유할 수 있어 코드 중복을 제거합니다. 이러한 특징들이 중요한 이유는 복잡한 의존성을 명확하게 관리하고, 각 모듈의 독립성을 유지하면서도 전체 시스템을 통합할 수 있기 때문입니다.

코드 예제

# 기존 프로젝트에 submodule 추가
git submodule add https://github.com/company/shared-library.git libs/shared

# 주석: .gitmodules 파일이 생성되고 submodule 정보가 기록됨

# submodule을 포함한 저장소를 클론할 때
git clone https://github.com/company/main-project.git
cd main-project

# submodule을 초기화하고 내용을 가져옴
git submodule init
git submodule update

# 또는 클론 시 한 번에 처리
git clone --recurse-submodules https://github.com/company/main-project.git

# submodule을 최신 버전으로 업데이트
cd libs/shared
git pull origin main
cd ../..
git add libs/shared
git commit -m "Update shared library to latest version"

# 모든 submodule을 한 번에 업데이트
git submodule update --remote

설명

이것이 하는 일: Git Submodule은 메인 저장소와 하위 저장소를 연결하는 참조 시스템입니다. 메인 프로젝트의 특정 디렉토리가 다른 Git 저장소를 가리키도록 설정하고, 어느 커밋을 사용할지 기록합니다.

각 저장소는 독립적으로 작동하지만 메인 프로젝트에서는 하나의 통합된 구조로 보입니다. 첫 번째 단계로, git submodule add <저장소URL> <경로>를 실행하면 지정한 경로에 외부 저장소가 클론되고, .gitmodules라는 파일이 생성됩니다.

이 파일은 submodule의 URL과 경로를 기록하는 설정 파일입니다. 동시에 Git은 submodule 디렉토리를 특별하게 취급하여, 일반 파일이 아닌 "커밋 참조"로 관리합니다.

예를 들어 libs/shared 디렉토리를 추가하면, 메인 프로젝트는 shared 저장소의 특정 커밋 해시만 기록하고, 그 디렉토리 안의 파일 내용은 직접 관리하지 않습니다. 두 번째 단계로, submodule이 포함된 프로젝트를 클론할 때는 특별한 처리가 필요합니다.

일반 git clone만 하면 submodule 디렉토리가 비어있습니다. git submodule init으로 .gitmodules 정보를 읽어 로컬 설정을 초기화하고, git submodule update로 실제 내용을 가져와야 합니다.

또는 클론할 때 --recurse-submodules 옵션을 사용하면 이 과정이 자동으로 처리됩니다. 이렇게 2단계로 나뉜 이유는 대규모 프로젝트에서 모든 submodule을 항상 필요로 하지 않을 수 있기 때문에, 선택적으로 가져올 수 있게 하기 위함입니다.

세 번째 단계로, submodule을 업데이트하는 방법은 두 가지입니다. 첫째, submodule 디렉토리로 들어가서 일반 Git 명령어로 작업할 수 있습니다.

cd libs/shared && git pull origin main처럼 최신 변경사항을 가져온 후, 메인 프로젝트로 돌아와서 git add libs/sharedgit commit으로 새로운 커밋 참조를 기록합니다. 둘째, git submodule update --remote를 사용하면 모든 submodule이 각각의 기본 브랜치 최신 버전으로 자동 업데이트됩니다.

이렇게 하면 메인 프로젝트는 항상 submodule의 어떤 버전을 사용하는지 정확히 기록하므로, 팀원들이 모두 같은 버전으로 작업할 수 있습니다. 여러분이 이 기능을 사용하면 코드 재사용을 체계적으로 관리할 수 있습니다.

공통 라이브러리를 한 곳에서 관리하면서 여러 프로젝트에 배포할 수 있고, 각 프로젝트는 필요한 버전을 독립적으로 선택할 수 있으며, 라이브러리 업데이트가 있을 때 점진적으로 각 프로젝트에 적용할 수 있습니다. 다만 submodule은 초기 설정과 학습 곡선이 있으므로, 간단한 경우에는 npm이나 git subtree 같은 대안도 고려해보세요.

실전 팁

💡 .gitmodules 파일을 꼭 커밋하세요. 이 파일이 있어야 다른 팀원들이 submodule을 올바르게 가져올 수 있습니다.

💡 submodule 업데이트 후 메인 프로젝트에서 커밋하는 것을 잊지 마세요. submodule 디렉토리에서 git pull만 하고 메인 프로젝트에서 커밋하지 않으면, 다른 사람들은 여전히 이전 버전을 사용하게 됩니다.

💡 CI/CD 파이프라인에서 git clone --recurse-submodules를 사용하거나, 클론 후 git submodule update --init --recursive를 실행하세요. 그렇지 않으면 빌드가 실패할 수 있습니다.

💡 submodule이 너무 많아지면 관리가 복잡해집니다. 정말 독립적으로 개발되고 여러 곳에서 재사용되는 코드만 submodule로 만들고, 단순히 디렉토리 정리 목적이라면 monorepo 구조를 고려하세요.

💡 submodule 대신 Git Subtree도 고려해보세요. subtree는 submodule보다 사용이 간단하고, 외부 저장소의 코드를 메인 프로젝트에 통합하는 방식입니다. 각각 장단점이 있으므로 프로젝트 특성에 맞게 선택하세요.


#Git#Branch#Rebase#Cherry-pick#Stash#TypeScript

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.