고급 Git 커밋 작업

GitBeginner
지금 연습하기

소개

Git 의 세계에 오신 것을 환영합니다! 버전 관리의 기초를 다졌다면, 이제 실력을 한 단계 더 끌어올릴 차례입니다. 이번 실습에서는 Git 의 고급 커밋 작업들을 살펴볼 것입니다. 이러한 기술들을 익히면 프로젝트 히스토리를 더욱 정교하게 제어할 수 있으며, 실수를 바로잡고 작업을 재구성하며 팀원들과 더 효율적으로 협업할 수 있게 됩니다.

이 실습을 여러분의 타임머신을 업그레이드하는 과정이라고 생각해보세요. 단순히 과거로 여행하는 것을 넘어, 이제는 타임라인 자체를 수정하는 방법을 배우게 될 것입니다! 내용이 다소 어렵게 느껴지더라도 걱정하지 마세요. 각 단계를 차근차근 안내하며, 단순히 '어떻게' 하는지뿐만 아니라 실제 상황에서 '왜' 이 작업이 유용한지도 함께 설명해 드립니다.

실습을 마치면 커밋 수정 (amend), 변경 사항 되돌리기 (revert), 특정 커밋만 골라오기 (cherry-pick), 대화형 리베이스 (interactive rebase), 커밋 합치기 (squash) 를 자유자재로 다룰 수 있게 됩니다. 이는 전문 개발자들이 깔끔하고 조직적인 프로젝트 히스토리를 유지하기 위해 매일 사용하는 강력한 도구들입니다. 그럼 Git 실력을 한 차원 높이러 떠나볼까요?

이 과정은 단계별 지침을 통해 학습과 연습을 돕는 가이드형 실습입니다. 각 단계를 주의 깊게 따라가며 직접 경험을 쌓아보세요. 통계에 따르면 이 실습은 초급 수준이며, 85%의 완료율과 학습자들로부터 96%의 긍정적인 평가를 받았습니다.

작업 공간 설정하기

고급 작업을 시작하기 전에 새로운 작업 공간을 준비합시다. 새로운 디렉토리를 만들고 Git 저장소를 초기화하겠습니다. 이렇게 하면 기존 프로젝트에 영향을 주지 않고 마음껏 실험해 볼 수 있는 깨끗한 공간이 마련됩니다.

터미널을 열고 다음 명령어를 한 줄씩 입력한 뒤 Enter를 누르세요.

cd ~/project
mkdir git-advanced-lab
cd git-advanced-lab
git init

각 명령어가 어떤 역할을 하는지 단계별로 살펴보겠습니다.

  1. cd ~/project: cd는 "디렉토리 변경 (change directory)"의 약자입니다. ~/project는 보통 홈 디렉토리 (~) 내의 "project" 폴더를 가리키는 경로입니다. 이 명령어를 통해 터미널의 위치를 해당 디렉토리로 이동합니다. 만약 해당 폴더가 없다면 오류가 발생할 수 있으니, 그럴 때는 mkdir ~/project로 먼저 폴더를 만든 후 다시 시도하세요.
  2. mkdir git-advanced-lab: mkdir은 "디렉토리 생성 (make directory)"의 약자입니다. 현재 위치 (이전 명령어를 통해 이동한 ~/project) 에 "git-advanced-lab"이라는 새 폴더를 만듭니다. 이 디렉토리가 이번 실습의 Git 저장소 루트가 됩니다.
  3. cd git-advanced-lab: 현재 위치를 방금 만든 "git-advanced-lab" 디렉토리 내부로 이동합니다. 이제 실행하는 모든 명령어는 이 디렉토리 안에서 작동합니다.
  4. git init: 저장소를 초기화하는 핵심 명령어입니다. 현재 디렉토리 (git-advanced-lab) 안에 Git 에 필요한 구조를 설정하여 Git 저장소로 변환합니다. 실행 후에는 .git이라는 숨겨진 폴더가 생성되는데, 이곳이 버전 히스토리와 설정이 저장되는 Git 저장소의 심장부입니다.

이제 Git 저장소가 준비되었으니, 작업할 간단한 파일을 만들고 첫 번째 커밋을 진행해 봅시다.

echo "Hello, Advanced Git" > hello.txt
git add hello.txt
git commit -m "Initial commit"

명령어의 상세 내용은 다음과 같습니다.

  1. echo "Hello, Advanced Git" > hello.txt: echo는 텍스트를 출력하는 명령어이고, >는 리다이렉션 연산자입니다. echo의 출력 내용을 "hello.txt"라는 파일로 보냅니다. 파일이 없으면 새로 만들고, 있으면 내용을 덮어씁니다. 결과적으로 "Hello, Advanced Git"이라는 내용이 담긴 "hello.txt" 파일이 생성됩니다.
  2. git add hello.txt: Git 이 파일의 변경 사항을 추적하게 하려면 명시적으로 알려줘야 합니다. 이 명령어는 "hello.txt" 파일을 스테이징 영역 (staging area) 에 올립니다. 스테이징은 다음 커밋에 포함될 준비를 하는 단계로, 커밋을 위한 '준비 구역'이라고 생각하면 됩니다.
  3. git commit -m "Initial commit": git commit은 스테이징 영역에 있는 모든 변경 사항을 저장소 히스토리에 새로운 커밋으로 저장합니다. -m 옵션은 커밋 메시지를 추가하는 것으로, 뒤에 오는 "Initial commit"은 변경 사항에 대한 짧은 설명입니다. 명확하고 간결한 커밋 메시지를 작성하는 습관을 들이는 것이 좋습니다.

좋습니다! 이제 하나의 커밋이 있는 저장소가 만들어졌습니다. 모든 것이 올바르게 설정되었는지 상태를 확인해 봅시다.

git status

명령어를 실행하면 터미널에 다음과 같은 메시지가 나타날 것입니다.

On branch master
nothing to commit, working tree clean

이 메시지는 작업 트리 (working tree) 가 깨끗하다는 것을 의미합니다. "작업 트리"는 프로젝트 파일이 있는 디렉토리를 말하며, "깨끗하다 (clean)"는 것은 아직 커밋되지 않은 변경 사항이 없다는 뜻입니다. 이제 깨끗한 상태에서 고급 작업을 시작할 준비가 끝났습니다!

마지막 커밋 수정하기

방금 커밋을 마쳤는데 파일에서 빠뜨린 변경 사항을 발견했거나, 커밋 메시지에 오타를 낸 상황을 상상해 보세요. 이런 사소한 수정을 위해 새로운 커밋을 또 만드는 대신, Git 의 --amend 옵션을 사용하여 가장 최근의 커밋을 수정할 수 있습니다. 이는 마치 시간을 살짝 되돌려 마지막 행동을 바로잡는 것과 같습니다.

직접 해봅시다. 먼저 hello.txt 파일에 한 줄을 더 추가하여 수정해 보겠습니다.

echo "This is an important file." >> hello.txt

이 명령어는 "hello.txt" 파일 끝에 새로운 내용을 덧붙입니다. >> 연산자의 의미를 알아봅시다.

  • >는 파일의 기존 내용을 모두 지우고 새 내용으로 덮어씁니다.
  • >>는 기존 내용을 유지하면서 파일 끝에 새 내용을 추가합니다.

이제 "hello.txt" 파일은 다음과 같이 두 줄이 됩니다.

Hello, Advanced Git
This is an important file.

이제 이 "중요한 파일"이라는 메모를 첫 번째 커밋에 포함했어야 했다고 가정해 봅시다. 이전 커밋을 수정하여 이 변경 사항을 포함하고, 메시지도 그에 맞게 업데이트할 수 있습니다.

git add hello.txt
git commit --amend -m "Initial commit with important note"

명령어의 구성은 다음과 같습니다.

  1. git add hello.txt: 파일을 수정했으므로 다시 스테이징해야 합니다. Git 에게 "hello.txt"의 현재 상태를 커밋에 포함하고 싶다고 알려주는 것입니다.
  2. git commit --amend -m "Initial commit with important note": 여기서 마법이 일어납니다.
    • --amend: 새로운 커밋을 만드는 대신 마지막 커밋을 수정하겠다는 플래그입니다.
    • -m "...": 새로운 커밋 메시지를 제공합니다. 이 옵션을 생략하면 기본 텍스트 편집기가 열려 기존 메시지를 직접 수정할 수 있습니다.

어떤 변화가 일어났나요?

Git 히스토리에 새로운 커밋이 추가되는 대신, 기존의 "Initial commit"이 개선된 새 버전으로 교체되었습니다. 이전 커밋은 사라지고, 우리가 스테이징한 변경 사항과 업데이트된 메시지가 반영된 새로운 커밋이 그 자리를 대신합니다.

커밋 로그를 확인하여 이를 검증해 봅시다.

git log --oneline

출력 결과에 커밋이 여전히 하나만 있어야 하며, 메시지는 "Initial commit with important note"로 바뀌어 있어야 합니다.

<commit_hash> Initial commit with important note

로그 화면이 자동으로 닫히지 않는다면 q를 눌러 종료하세요.

커밋 수정 시 주의사항:

  • 푸시하지 않은 커밋만 수정하세요: GitHub 나 GitLab 같은 공유 원격 저장소에 이미 푸시한 커밋은 절대로 수정하지 마세요. 이미 푸시된 커밋을 수정하면 다른 팀원들이 사용 중인 히스토리를 재작성하게 되어 협업 환경에서 큰 혼란과 문제를 야기할 수 있습니다.
  • 로컬 히스토리 정리용: 수정 기능은 주로 작업을 공유하기 전, 자신의 로컬 커밋 히스토리를 깔끔하게 다듬는 용도로 사용됩니다. 예를 들어 기능 브랜치에서 작업하는 동안 커밋을 정교하게 다듬는 아주 좋은 방법입니다.

커밋 되돌리기

때로는 커밋을 완료한 후에야 해당 작업에 버그가 있음을 깨닫거나, 단순히 변경 사항을 취소하고 싶을 때가 있습니다. git reset을 사용하여 시간을 되돌리고 커밋을 삭제하는 방법을 생각할 수도 있지만, git reset은 이미 푸시된 작업이거나 다른 사람과 공유 중인 브랜치에서는 위험할 수 있습니다. 변경 사항을 취소하는 더 안전하고 협업 친화적인 방법은 git revert를 사용하는 것입니다.

git revert는 특정 커밋이 도입한 변경 사항을 반대로 수행하는 새로운 커밋을 생성합니다. 히스토리에서 원래의 커밋을 지우지 않고, 원치 않는 커밋의 효과를 상쇄하는 커밋을 뒤에 덧붙이는 방식입니다. 이는 히스토리를 보존하므로 공유 저장소에서 훨씬 안전합니다.

되돌릴 대상이 될 새로운 커밋을 하나 만들어 봅시다.

echo "This line will be reverted" >> hello.txt
git add hello.txt
git commit -m "Add line to be reverted"

이전과 비슷한 과정입니다.

  1. echo "..." >> hello.txt: 파일에 새로운 줄을 추가합니다.
  2. git add hello.txt: 수정된 파일을 스테이징합니다.
  3. git commit -m "...": "Add line to be reverted"라는 메시지와 함께 새 커밋을 만듭니다.

이제 "This line will be reverted"라는 줄을 추가한 것이 실수였다고 판단하고 이를 제거하고 싶다고 가정해 봅시다. 마지막 커밋을 다음과 같이 되돌릴 수 있습니다.

git revert HEAD

명령어를 분석해 보겠습니다.

  • git revert: 변경 사항을 되돌리는 명령어입니다.
  • HEAD: Git 에서 HEAD는 현재 브랜치의 최신 커밋을 가리키는 포인터입니다. 따라서 git revert HEAD는 "가장 최근의 커밋을 되돌려라"는 의미가 됩니다. 특정 커밋 해시를 지정하여 더 오래된 커밋을 되돌릴 수도 있습니다.

이 명령어를 실행하면 Git 은 다음 과정을 수행합니다.

  1. 변경 사항 분석: HEAD 커밋이 가져온 변화 (여기서는 한 줄 추가) 를 확인합니다.
  2. 반대 변경 사항 생성: 그 변화를 취소하는 방법 (여기서는 해당 줄 삭제) 을 계산합니다.
  3. 새 커밋 생성: 이 반대 변경 사항을 적용하는 새로운 커밋을 자동으로 생성합니다.
  4. 텍스트 편집기 실행 (선택 사항): 보통 Git 은 되돌리기 커밋 메시지를 작성할 수 있도록 기본 텍스트 편집기 (Vim, Nano 등) 를 엽니다. 기본 메시지는 보통 "Revert 'Add line to be reverted'"와 같은 형태입니다. 그대로 사용하거나 설명을 덧붙일 수 있습니다.

git revert 실행 중 편집기 다루기:

편집기가 열렸을 때 익숙하지 않더라도 당황하지 마세요. 과정은 간단합니다.

  • 기본 메시지를 수락하고 완료하려면:
    • Vim 의 경우: Esc 키를 눌러 명령 모드로 들어간 뒤, :(콜론) 을 입력하고 wq(저장 후 종료) 를 입력한 다음 Enter를 누릅니다.
    • Nano 의 경우: Ctrl + X(종료) 를 누르고, Y(변경 저장) 를 누른 뒤 Enter(파일명 확인) 를 누릅니다.
    • 기타 편집기: "저장 (Save)" 후 "닫기 (Close)" 또는 "종료 (Exit)" 옵션을 찾으세요.

편집기를 저장하고 닫으면 Git 이 되돌리기 커밋을 완료합니다.

이제 커밋 히스토리를 확인해 봅시다.

git log --oneline

로그에 최신순으로 세 개의 커밋이 보여야 합니다.

  1. "Revert"로 시작하는 커밋 (방금 만든 되돌리기 커밋)
  2. "Add line to be reverted" (되돌려진 원래의 커밋)
  3. "Initial commit with important note" (수정했던 첫 번째 커밋)
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original> Add line to be reverted
<commit_hash_initial> Initial commit with important note

이제 hello.txt 파일의 내용을 확인해 보면 "This line will be reverted"라는 줄이 사라져 있을 것입니다. "Add line to be reverted" 커밋의 변경 사항이 성공적으로 취소되었습니다.

공유 저장소에서 git reset보다 git revert를 권장하는 이유는 무엇인가요?

  • 히스토리 보존: git revert는 히스토리를 재작성하지 않습니다. 대신 취소 작업을 위한 새 커밋을 추가하므로 협업 시 안전합니다. 모든 팀원이 원래의 실수와 그에 대한 수정 과정을 히스토리에서 명확히 볼 수 있습니다.
  • 강제 푸시 방지: git reset을 사용한 뒤 원격 저장소에 반영하려면 강제 푸시 (git push --force) 가 필요한 경우가 많습니다. 이는 다른 사람의 작업을 덮어쓸 수 있는 위험한 행동입니다. git revert는 일반적인 푸시가 가능하므로 이런 문제가 없습니다.
  • 명확한 감사 추적 (Audit Trail): 변경 사항이 언제, 왜 취소되었는지에 대한 기록이 남습니다. 이는 프로젝트의 발전 과정을 이해하고 문제를 디버깅하는 데 매우 유중한 정보가 됩니다.

특정 커밋만 골라오기 (Cherry-picking)

Git 의 체리 피킹 (Cherry-picking) 은 나무에서 체리를 하나 따는 것과 같습니다. 다른 브랜치에 있는 특정 커밋 하나만 선택해서 현재 브랜치에 적용하는 기능입니다. 다른 브랜치의 특정 기능이나 버그 수정 사항만 가져오고 싶을 때 매우 유용합니다.

별도의 브랜치에서 기능을 개발하다가, 그중 특정 기능만 메인 브랜치로 가져오는 상황을 시뮬레이션해 봅시다.

먼저 새 브랜치를 만들고 커밋을 하나 생성합니다.

git checkout -b feature-branch
echo "This is a new feature" >> feature.txt
git add feature.txt
git commit -m "Add new feature"

명령어 설명:

  1. git checkout -b feature-branch:
    • git checkout: 브랜치를 전환하거나 생성할 때 사용합니다.
    • -b feature-branch: "feature-branch"라는 새 브랜치를 만들고 즉시 그 브랜치로 이동하라는 의미입니다. 이제 이 브랜치가 활성 브랜치가 됩니다.
  2. echo "..." >> feature.txt: "feature-branch"에서 "feature.txt"라는 새 파일을 만듭니다.
  3. git add feature.txt: 새 파일을 스테이징합니다.
  4. git commit -m "Add new feature": "feature-branch"에 "Add new feature"라는 메시지로 커밋을 생성합니다.

이제 이 기능 개발을 잠시 멈추고, 이 커밋만 메인 브랜치 (master) 에 통합하고 싶다고 가정해 봅시다. 먼저 master 브랜치로 돌아가야 합니다.

git checkout master

이 명령어는 활성 브랜치를 다시 "master"로 전환합니다.

이제 "feature-branch"에 있는 "Add new feature" 커밋을 master 브랜치에 적용해 보겠습니다. 이때 git cherry-pick을 사용합니다.

git cherry-pick feature-branch

왜 커밋 해시 대신 feature-branch라고 썼을까요? 이 간단한 예시에서 feature-branch는 해당 브랜치의 최신 커밋을 가리키기 때문입니다. 브랜치에 커밋이 하나뿐이므로 결과적으로 그 커밋을 가져오게 됩니다. 만약 여러 커밋 중 특정 하나를 고르고 싶다면 해당 커밋의 해시를 사용해야 합니다. 예를 들어 git log feature-branch --oneline으로 해시를 확인한 뒤 git cherry-pick <commit_hash>와 같이 입력합니다.

git cherry-pick을 실행하면 Git 은 다음을 수행합니다.

  1. 커밋 찾기: 지정된 브랜치의 최신 커밋 (또는 지정한 해시의 커밋) 을 찾습니다.
  2. 변경 사항 적용: 해당 커밋의 변경 사항을 현재 브랜치 (master) 에 적용하려고 시도합니다.
  3. 새 커밋 생성: 성공하면 (충돌이 없다면), Git 은 master 브랜치에 동일한 변경 사항과 메시지를 가진 새로운 커밋을 생성합니다.

master 브랜치의 커밋 로그를 확인해 봅시다.

git log --oneline

master 브랜치에 "Add new feature"라는 메시지의 새 커밋이 보일 것입니다. 중요한 점은 변경 사항은 같지만, "feature-branch"에 있는 커밋과는 다른 해시를 가진 별개의 새 커밋이라는 점입니다.

또한 ls 명령어로 파일을 확인해 보면 master 브랜치에도 "feature.txt" 파일이 생성된 것을 볼 수 있습니다.

체리 피킹 시 주의사항:

  • 새로운 커밋 생성: 체리 피킹은 항상 새로운 커밋을 만듭니다. 원래 커밋을 이동시키거나 복사하는 것이 아니라는 점을 기억하세요.
  • 충돌 가능성: 두 브랜치의 코드 베이스가 많이 다르다면 충돌이 발생할 수 있습니다. 이 경우 Git 은 작업을 멈추고 병합 충돌 (merge conflict) 때와 마찬가지로 사용자에게 해결을 요청합니다.
  • 신중한 사용: 체리 피킹은 강력하지만 남용하면 히스토리를 파악하기 어렵게 만들 수 있습니다. 특히 나중에 해당 기능 브랜치 전체를 병합할 때 복잡해질 수 있습니다. 전체 병합이 적절하지 않은 상황에서 특정 수정 사항이나 기능만 골라 적용할 때 가장 효과적입니다.

대화형 리베이스 (Interactive Rebasing)

대화형 리베이스는 Git 에서 가장 강력하면서도 복잡한 기능 중 하나입니다. 이를 통해 커밋 히스토리를 매우 유연하게 재작성할 수 있습니다. 커밋 순서를 바꾸거나, 여러 커밋을 하나로 합치고 (squash), 메시지를 수정하거나, 아예 삭제할 수도 있습니다. 마치 커밋 히스토리를 위한 정교한 편집기를 사용하는 것과 같습니다.

경고: 대화형 리베이스는 히스토리를 재작성하므로, 특히 공유 브랜치에서 사용할 때는 위험할 수 있습니다. 이미 공유 저장소에 푸시된 커밋은 리베이스하지 마세요. 리베이스는 작업을 공유하기 전, 자신의 로컬 커밋들을 깔끔하게 정리할 때 사용하는 것이 가장 안전하고 적절합니다.

리베이스의 효과를 확인하기 위해 연속된 커밋들을 몇 개 만들어 봅시다.

echo "First change" >> hello.txt
git commit -am "First change"
echo "Second change" >> hello.txt
git commit -am "Second change"
echo "Third change" >> hello.txt
git commit -am "Third change"

이 명령어들은 "hello.txt"에 한 줄씩 추가하며 세 개의 커밋을 빠르게 생성합니다. git commit -am에서 -am은 수정되거나 삭제된 파일을 자동으로 스테이징 (-a) 하고 메시지를 추가 (-m) 하는 단축 명령어입니다. 이미 추적 중인 파일의 변경 사항을 바로 커밋할 때 편리합니다.

이제 이 세 개의 커밋을 정리해 봅시다. "First change"와 "Second change"는 사실 하나의 논리적인 작업이므로 하나로 합치고 싶고, "Third change"의 메시지는 좀 더 구체적으로 바꾸고 싶다고 가정해 봅시다. 대화형 리베이스가 이 작업에 딱 제격입니다.

다음 명령어로 대화형 리베이스 세션을 시작합니다.

git rebase -i HEAD~3

명령어의 의미는 다음과 같습니다.

  • git rebase -i: 리베이스를 실행하되 -i(interactive) 플래그를 통해 대화형 모드로 진행합니다.
  • HEAD~3: 리베이스할 커밋의 범위를 지정합니다. HEAD로부터 뒤로 세 번째 커밋까지, 즉 최근 3 개의 커밋을 선택합니다. 특정 커밋 해시를 입력하여 그 지점부터 리베이스를 시작할 수도 있습니다.

이 명령어를 실행하면 기본 텍스트 편집기가 열리며 최근 3 개의 커밋 목록이 나타납니다. 이 화면에서 Git 에게 히스토리를 어떻게 수정할지 지시하게 됩니다. 다음과 유사한 내용이 보일 것입니다 (해시는 다를 수 있습니다).

1 pick 63c95db First change
2 pick 68e7909 Second change
3 pick 5371424 Third change
4
5 ## Rebase 3bf348d..5371424 onto 3bf348d (3 commands)
6 #
7 ## Commands:
8 ## p, pick <commit> = use commit
9 ## r, reword <commit> = use commit, but edit the commit message
10 ## e, edit <commit> = use commit, but stop for amending
11 ## s, squash <commit> = use commit, but meld into previous commit
12 ## f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
13 ##                    commit's log message, unless -C is used, in which case
14 ##                    keep only this commit's message; -c is same as -C but
15 ##                    opens the editor
16 ## x, exec <command> = run command (the rest of the line) using shell
17 ## b, break = stop here (continue rebase later with 'git rebase --continue')
18 ## d, drop <commit> = remove commit
19 ## l, label <label> = label current HEAD with a name
20 ## t, reset <label> = reset HEAD to a label
21 ## m, merge [-C <commit> | -c <commit>] <label> [## <oneline>]
22 ## .       create a merge commit using the original merge commit's
23 ## .       message (or the oneline, if no original merge commit was
24 ## .       specified); use -c <commit> to reword the commit message
25 #
26 ## These lines can be re-ordered; they are executed from top to bottom.
27 #
28 ## If you remove a line here THAT COMMIT WILL BE LOST.
29 #
30 ## However, if you remove everything, the rebase will be aborted.

대화형 리베이스 편집기 이해하기:

  • 커밋 목록: 상단에는 리베이스 대상 커밋들이 나열됩니다. 기본적으로 오래된 순서대로 (위에서 아래로) 정렬되어 있습니다.
  • 명령어 (Commands): 커밋 목록 아래에는 각 커밋에 사용할 수 있는 명령어 설명이 있습니다. 자주 쓰이는 것들은 다음과 같습니다.
    • pick (또는 p): 커밋을 그대로 사용합니다. 기본값입니다.
    • reword (또는 r): 커밋을 사용하되, 커밋 메시지만 수정합니다.
    • edit (또는 e): 커밋을 사용하되, 리베이스 도중 멈춰서 파일 내용을 수정하거나 추가 작업을 할 수 있게 합니다.
    • squash (또는 s): 이 커밋을 이전 (위) 커밋과 합칩니다. 두 커밋 메시지도 하나로 합쳐집니다.
    • fixup (또는 f): squash와 비슷하지만, 이 커밋의 메시지는 버리고 이전 커밋의 메시지만 유지합니다.
    • drop (또는 d): 이 커밋을 히스토리에서 완전히 삭제합니다.

우리의 리베이스 계획:

우리는 다음을 수행할 것입니다.

  1. "Second change"를 "First change"에 합치기 (squash)
  2. "Third change"의 메시지를 더 좋게 수정하기 (reword)

이를 위해 편집기 내용을 다음과 같이 수정하세요.

pick abc1234 First change
squash def5678 Second change
reword ghi9101 Third change

주의: 편집기 내의 커밋 해시 값은 건드리지 마세요.

Vim 편집기 사용법 (Vim 이 열린 경우):

  1. 입력 모드 전환: i 키를 누릅니다. 이제 텍스트를 수정할 수 있습니다.
  2. 내용 수정: 화살표 키로 이동하여 "pick"을 "squash"와 "reword"로 바꿉니다.
  3. 입력 모드 종료: Esc 키를 누릅니다.
  4. 저장 및 종료: :wq를 입력하고 Enter를 누릅니다.

편집기를 저장하고 닫으면 Git 이 지시사항에 따라 리베이스를 시작합니다.

첫 번째 단계: 커밋 합치기 (Squash)

Git 은 먼저 squash 명령을 처리합니다. "Second change"의 변경 사항을 "First change"에 통합한 뒤, 합쳐진 커밋의 메시지를 작성할 수 있도록 편집기를 다시 엽니다. 다음과 같은 내용이 보일 것입니다.

## This is a combination of 2 commits.
## This is the 1st commit message:

First change

## This is the 2nd commit message:

Second change

## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
...

기존의 두 메시지가 모두 보입니다. 이를 지우고 하나의 일관된 메시지로 수정하세요. 예를 들어 다음과 같이 바꿀 수 있습니다.

Combined first and second changes: Initial setup of hello.txt

수정 후 저장하고 닫습니다.

두 번째 단계: 메시지 수정 (Reword)

다음으로 "Third change"에 대한 reword 명령을 처리합니다. 편집기가 또 한 번 열리며 원래 메시지가 나타납니다.

Third change
...

이를 더 설명적인 메시지로 수정합니다.

Improved third change: Added a more descriptive line to hello.txt

저장하고 닫습니다.

리베이스 완료:

모든 지시사항이 처리되면 Git 은 리베이스를 마무리합니다. 보통 "Successfully rebased and updated refs/heads/master."라는 메시지가 출력됩니다.

리베이스된 히스토리 확인:

이제 커밋 로그를 다시 확인해 봅시다.

git log --oneline

다음과 유사한 히스토리가 보일 것입니다.

<commit_hash_third_revised> Improved third change: Added a more descriptive line to hello.txt
<commit_hash_combined> Combined first and second changes: Initial setup of hello.txt
<commit_hash_cherry_pick> Add new feature
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original_reverted> Add line to be reverted
<commit_hash_initial_amended> Initial commit with important note

확인할 점:

  • 커밋 개수가 줄어들었습니다 (두 개를 하나로 합쳤기 때문).
  • "First change"와 "Second change"가 합쳐져 새로운 메시지를 갖게 되었습니다.
  • "Third change"의 메시지가 수정되었습니다.
  • 히스토리를 재작성했으므로 리베이스된 커밋들의 해시 값이 모두 바뀌었습니다.

대화형 리베이스 핵심 요약:

  • 로컬 정리 도구: 대화형 리베이스는 주로 로컬 커밋 히스토리를 깔끔하게 다듬는 용도입니다.
  • 공유 브랜치 주의: 이미 푸시된 브랜치는 리베이스하지 마세요. 팀원들에게 큰 혼란을 줄 수 있습니다.
  • git rebase --abort: 리베이스 도중 실수를 했거나, 충돌 해결이 어렵거나, 상황이 꼬였다면 언제든지 이 명령어로 리베이스를 취소하고 시작 전 상태로 안전하게 돌아갈 수 있습니다.

루트부터 리베이스하기 (Rebasing from the Root)

5 단계에서는 git rebase -i HEAD~3을 사용하여 최근 3 개의 커밋을 다뤘습니다. 하지만 대화형 리베이스는 더 강력한 기능을 제공합니다. git rebase -i --root를 사용하면 저장소의 모든 커밋, 즉 최초의 커밋 (root commit) 부터 현재까지의 전체 히스토리를 대상으로 리베이스를 수행할 수 있습니다. 프로젝트의 전체 역사를 재구성할 수 있는 궁극의 제어권을 갖게 되는 것입니다.

극도의 주의 사항: 루트 리베이스는 매우 강력한 작업이며, 그만큼 위험성도 큽니다. 공유 저장소에서의 루트 리베이스는 거의 항상 나쁜 아이디어입니다. 전체 히스토리를 완전히 재작성하여 모든 커밋의 해시를 바꾸기 때문에, 협업 중인 다른 모든 사람에게 심각한 문제를 일으킵니다. 팀 전체와 사전에 긴밀히 협의된 특수한 상황이 아니라면 절대로 피해야 합니다.

이 실습은 로컬의 격리된 저장소에서 진행되므로, 학습을 위해 루트 리베이스를 실험해 보는 것은 안전합니다. 다만 실제 협업 프로젝트에서는 위의 경고를 반드시 기억하세요!

루트 리베이스를 시도해 봅시다.

git rebase -i --root

이전 리베이스와 비슷하지만 HEAD~3 대신 --root를 사용했습니다. 이는 저장소의 맨 처음 커밋부터 대화형 리베이스 대상으로 삼겠다는 뜻입니다.

편집기가 열리면 저장소의 첫 번째 커밋 ("Initial commit with important note" 또는 수정했던 메시지) 부터 모든 커밋이 나열됩니다. 이전 단계보다 훨씬 긴 목록을 볼 수 있습니다.

다음과 같은 목록이 보일 것입니다 (해시와 메시지는 실습 과정에 따라 다를 수 있습니다).

pick <hash_initial> Initial commit with important note
pick <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

우리의 루트 리베이스 계획:

이번 연습에서는 두 가지를 해보겠습니다.

  1. 최초 커밋의 메시지 수정: 첫 번째 커밋 메시지를 더 세련되게 다듬어 봅시다.
  2. 되돌리기 커밋을 원래 커밋에 합치기: 조금 억지스러운 예시일 수 있지만, 히스토리 조작 능력을 보여주기 위함입니다. "Revert" 커밋은 결국 실수를 바로잡는 과정이므로, 실수를 유발했던 커밋과 합쳐서 마치 실수가 없었던 것처럼 히스토리를 깨끗하게 만들 수 있습니다. (실제 상황에서는 되돌리기 기록을 남기는 것이 좋을 때가 많으므로 신중해야 합니다.)

계획에 맞춰 편집기 내용을 다음과 같이 수정하세요.

reword <hash_initial> Initial commit with important note
squash <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

첫 번째 커밋은 reword로, 두 번째 커밋 ("Add line to be reverted") 은 squash로 변경했습니다. "Revert" 커밋은 일단 pick으로 둡니다.

저장하고 닫습니다.

첫 번째 단계: 최초 커밋 메시지 수정

Git 이 멈추면 최초 커밋의 메시지를 수정합니다. "Initial setup and file creation" 정도로 바꿔봅시다. 저장하고 닫습니다.

두 번째 단계: 실수를 유발한 커밋을 최초 커밋에 합치기

Git 은 squash 명령을 처리하여 "Add line to be reverted"의 변경 사항을 이전 커밋 (방금 수정한 최초 커밋) 에 합칩니다. 메시지 편집기가 열리면 두 내용을 통합한 새로운 메시지를 작성합니다. 예를 들어:

Initial setup and file creation with subsequent line removal

저장하고 닫습니다.

리베이스 완료:

Git 이 나머지 커밋들을 처리하고 루트 리베이스를 마칩니다.

최종 히스토리 확인:

마지막으로 커밋 로그를 확인해 봅시다.

git log --oneline

전체 히스토리가 지시한 대로 바뀌어 있을 것입니다.

  • 최초 커밋이 새로운 메시지로 변경되었습니다.
  • 지시사항에 따라 커밋들이 합쳐지거나 유지되었습니다.
  • 루트 커밋부터 모든 커밋의 해시 값이 새로 생성되었습니다.

다시 한번 강조하지만, 공유 저장소에서의 루트 리베이스는 매우 위험합니다! 이 단계는 대화형 리베이스의 강력함과 유연성을 보여주기 위한 교육적 목적으로 진행되었습니다.

요약

축하합니다! Git 마스터로 한 걸음 더 다가섰습니다. 이번 실습에서 배운 강력한 기술들을 정리해 봅시다.

  1. 커밋 수정 (Amending): 새로운 커밋을 만들지 않고도 마지막 커밋의 사소한 실수나 누락된 변경 사항을 바로잡아 히스토리를 깔끔하게 유지하는 방법을 배웠습니다.
  2. 커밋 되돌리기 (Reverting): 이전 커밋을 취소하는 새로운 커밋을 생성하여 안전하게 실수를 바로잡는 방법을 익혔습니다. 이는 협업 환경에서 권장되는 비파괴적인 취소 방식입니다.
  3. 체리 피킹 (Cherry-picking): 다른 브랜치에서 필요한 특정 커밋만 골라 현재 브랜치에 적용하는 정교한 변경 사항 통합 기술을 배웠습니다.
  4. 대화형 리베이스 (Interactive Rebasing): 커밋 순서 변경, 합치기, 메시지 수정 등 히스토리를 자유자재로 재구성하여 작업을 공유하기 전 최상의 상태로 다듬는 예술을 마스터했습니다.
  5. 루트 리베이스 (Root Rebasing): 저장소의 시작점부터 전체 히스토리를 재설계하는 궁극의 제어 기술을 경험했습니다. 이 기능의 강력함과 그에 따른 책임 (위험성) 을 함께 이해했습니다.

이러한 고급 Git 작업들은 개발자의 도구 상자에서 매우 강력한 무기가 됩니다. 대규모 프로젝트나 팀 협업에서 깔끔하고 조직적인 커밋 히스토리를 유지하는 것은 단순히 보기 좋은 것을 넘어, 코드 리뷰와 디버깅을 용이하게 하고 프로젝트의 진화 과정을 명확히 이해하는 데 핵심적인 역할을 합니다.