실전 튜토리얼
지금까지 배운 Git의 기본 개념과 명령어들을 활용하여 실제 프로젝트에서 Git을 사용하는 방법을 실습을 통해 경험한다. Github flow 중심으로 브랜치 관리, 협업, 그리고 커밋 히스토리 관리를 단계별로 살펴본다.
Github Flow 실습
실습 개요
- Github Flow에 기반한 저장소 및 브랜치 생성
- 기능 개발을 위한 브랜치 생성 및 커밋
- Pull Request 생성 및 코드 리뷰
- 브랜치 병합 및 삭제
- hotfix 브랜치 생성 및 긴급 수정
1. 로컬 저장소 준비
먼저 다음과 같이 디렉토리를 만들고 git 저장소를 초기화한 다음 README.md 파일을 추가하고 커밋한다.
$ mkdir github-flow-test
$ cd github-flow-test
$ git init
$ touch README.md
$ echo "# Github Flow Test" > README.md
$ git add README.md
$ git commit -m "Initial commit"
[main (최상위-커밋) 8b30d03] Initial commit
1 file changed, 1 insertion(+)
create mode 100644 README.md
2. 원격 저장소 생성
다음으로 GitHub CLI를 사용하여 원격 저장소를 생성하고 로컬 저장소와 연결하고 로컬 저장소의 내용을 원격 저장소에 푸시한다.
$ gh repo create github-flow-test --public --source=. --push
✓ Created repository albks-edu/github-flow-test on github.com
https://github.com/albks-edu/github-flow-test
✓ Added remote https://github.com/albks-edu/github-flow-test.git
오브젝트 나열하는 중: 3, 완료.
오브젝트 개수 세는 중: 100% (3/3), 완료.
오브젝트 쓰는 중: 100% (3/3), 225 bytes | 225.00 KiB/s, 완료.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To https://github.com/albks-edu/github-flow-test.git
* [new branch] HEAD -> main
branch 'main' set up to track 'origin/main'.
✓ Pushed commits to https://github.com/albks-edu/github-flow-test.git
3. feature 브랜치 작업
이제 기능 개발을 위한 feature 브랜치를 생성하고 이동한다.
이제 feature 브랜치에서 새로운 파일을 추가하고 커밋한다.
$ touch feature-a.txt
$ echo "start feature-a" > feature-a.txt
$ git add .
$ git commit -m "feat: start feature-a"
[feature cf8de2a] feat: start feature-a
1 file changed, 1 insertion(+)
create mode 100644 feature-a.txt
파일을 수정한 다음 다시 커밋한다.
$ echo "update feature-a" >> feature-a.txt
$ git commit -am "feat: update feature-a"
[feature 4c63397] feat: update feature-a
1 file changed, 1 insertion(+)
- git에 추가된 파일의 수정이므로
git add명령어 없이git commit -am옵션으로 바로 커밋할 수 있다.
4. PR 생성
이제 feature 브랜치에서의 작업이 끝난것으로 가정하고 원격 저장소에 푸시한다.
$ git push origin feature
오브젝트 나열하는 중: 7, 완료.
오브젝트 개수 세는 중: 100% (7/7), 완료.
Delta compression using up to 10 threads
오브젝트 압축하는 중: 100% (4/4), 완료.
오브젝트 쓰는 중: 100% (6/6), 569 bytes | 569.00 KiB/s, 완료.
Total 6 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
remote:
remote: Create a pull request for 'feature' on GitHub by visiting:
remote: https://github.com/albks-edu/github-flow-test/pull/new/feature
remote:
To https://github.com/albks-edu/github-flow-test.git
* [new branch] feature -> feature
다음으로 GitHub CLI를 사용하여 Pull Request를 생성한다.
$ gh pr create --base main --head feature --title "feat: add feature-a" --body "feature-a 추가 완료."
Creating pull request for feature into main in albks-edu/github-flow-test
https://github.com/albks-edu/github-flow-test/pull/1
웹에서 Pull Request 생성
만일 Github CLI가 아닌 웹에서 pull request를 생성하려면 다음을 참고 한다.
브랜치에서 새로운 push가 발생한 경우 GitHub 웹사이트에 들어가면 자동으로 다음과 같이 검토 요청 메시지가 나타나는 것을 볼 수 있다. Compare & pull request 버튼을 클릭하여 변경 사항을 검토하고 Pull Request 생성도 가능하니 참고한다.

5. PR 검토 및 수정 요청
GitHub 웹사이트에서 Pull Request를 열고 변경 사항을 검토한다. 필요한 경우 코드 리뷰를 통해 수정 요청을 남긴다.
Pull requests -> Commits 탭에서 커밋들을 확인할 수 있다. update feature-a 커밋을 선택하면 Files changed 탭에서 변경된 파일들을 확인할 수 있다. 추가된 update feature-a 커밋에 대해 코드 리뷰를 남긴다.(마우스를 해당 코드 라인에 위치하면 + 버튼이 나타난다.)
코멘트가 단순 코멘트인지 리뷰인지 구분해서 Comment 또는 Start a review 버튼을 클릭하여 리뷰를 시작한다.

리뷰어가 남긴 수정 요청을 반영하여 로컬에서 코드를 수정하고 다시 커밋한다. 요청은 update->end로 고치는 것이지만 편의를 위해 새로운 라인을 추가하는 것으로 처리한다.
$ echo "end feature-a" >> feature-a.txt
$ git commit -am "feat: end feature-a"
$ git push origin feature
다시 GitHub에 접속해서 수정 했음을 reply 한다. 문제가 해결된 것이라면 Resolve comment 버튼을 클릭하여 문제 해결을 확인한다.

Pending
Start a review버튼을 클릭하여 리뷰를 시작한 경우 submit 하기전에는 상태가 Pending 으로 표시된다.- Pending 상태는 리뷰 메시지를 작성자 본인만 확인 가능한 상태 이다.
Submit review버튼을 클릭하여 리뷰를 완료해야 다른 사람들도 확인 가능하다.
6. 병합 및 브랜치 삭제
리뷰가 완료되면 Pull Request를 병합한다. GitHub 웹사이트에서 Merge pull request 버튼을 클릭하고 Confirm merge 버튼을 눌러 병합을 완료한다. 병합 유형은 기본값인 Create a merge commit 으로 진행한다. 메시지와 설명은 기본값을 사용하거나 원하는대로 수정할 수 있다.

병합이 완료되면 Delete branch 버튼을 클릭하여 원격의 feature 브랜치를 삭제할 수 있다. 로컬 브랜치도 다음 명령어로 삭제한다. 주의할점은 로컬 브랜치는 반드시 main 브랜치로 체크아웃한 상태에서 삭제해야 한다는 것이며 로컬의 main은 아직 원격과 동기화 되어 있지 않으므로 먼저 pull 명령어로 동기화 한 후 삭제한다.
로컬 브랜치는 main과 병합되지 않았지만 이미 원격 저장소에 병합이 완료되었고 pull로 동기화 했기 때문에 안전하게 삭제할 수 있다. 다만 병합이 안되었기 때문에 -d 옵션이 아니니 -D를 사용하여 강제로 삭제해야 한다.
7. 히스토리 확인
작업 히스토리는 다음과 같이 확인할 수 있다. github desktop 이나 sourcetree 와 같은 GUI 툴을 사용하면 좀 더 직관적으로 확인할 수 있다.
앞에서 설명한대로 머지 전략에 따라 히스토리 모습이 달라지므로 각각의 경우를 살펴본다.
먼저 기본 머지 전략인 Create a merge commit 의 경우는 커밋 기록들을 모두 유지하면서 머지 커밋이 추가되는 형태이다. 다음은 main 브랜치에서의 히스토리 모습이다.
| create a merge commit | |
|---|---|
- main 브랜치는 9라인의 initial commit에서 시작한다.
- feature 브랜치는 7->6->5라인의 커밋들로 구성된다.
- 3라인의 merge 커밋이 생성되어 feature 브랜치의 모든 커밋들이 main 브랜치에 병합된다.
다음은 squash and merge 의 경우이다. hotfix-1 브랜치를 새로 만들고 앞에서와 비슷하게 새로운 파일을 추가하고 2개의 커밋을 만든 이후 push로 원격 저장소 까지 브랜치를 동기화 한 다음 hotfix-1 브랜치에서의 히스토리는 다음과 같다.
- 기존의 main 브랜치 히스토리는 동일하다.
- 1-2: hotfix-1 브랜치는 2개의 커밋으로 구성되는 것을 확인할 수 있다.
이제 pull request를 생성하고 squash and merge 로 병합한 후 main 브랜치를 pull 해서 업데이트 한 다음 히스토리를 확인하면 다음과 같다.
| squash and merge | |
|---|---|
- main 브랜치에 hotfix-1 브랜치의 커밋들이 하나로 합쳐져서 추가된 것을 확인할 수 있다.
- 즉 hotfix-1 브랜치의 커밋들은 보이지 않고 머지 커밋만 보이게 된다.
마지막으로 rebase and merge 의 경우이다. hotfix-2 브랜치를 새로 만들고 앞에서와 같이 새로운 파일을 추가하고 2개의 커밋을 만든 이후 push로 원격 저장소 까지 브랜치를 동기화 한 다음 hotfix-2 브랜치에서의 히스토리는 다음과 같다.
이제 pull request를 생성하고 rebase and merge 로 병합한 후 main 브랜치를 pull 해서 업데이트 한 다음 히스토리를 확인하면 다음과 같다.
- rebase를 진행했기 때문에 브랜치가 아니라 main 브랜치에 바로 커밋들이 추가되었다.
- 즉 hotfix-2 브랜치의 커밋들이 그대로 main 브랜치에 통합되어 반영된 것을 볼 수 있다.
Sponsored
실전 상황별 실습
임시 저장
상황
특정 브랜치에서 작업 중인데 급하게 다른 브랜치로 이동해야 하는 상황이 발생했다. 현재 작업 중인 내용을 커밋하지 않고 임시로 저장하고 다른 브랜치로 이동한 후, 다시 돌아와서 작업을 이어가고 싶다.
해결 방법
git stash를 사용하여 현재 변경 사항을 잠시 "치워두고" (Stash), 워킹 디렉터리를 깨끗하게(마지막 커밋 상태로) 되돌린다.- 다른 브랜치로 이동하여 필요한 작업을 수행한다.
- 다시 원래 브랜치로 돌아와
git stash pop명령어를 사용하여 이전에 저장한 변경 사항을 복원한다.
설명
git stash는 현재 작업 중인 변경 사항을 임시로 저장하고 워킹 디렉터리를 깨끗한 상태로 되돌리는 데 사용된다. 이렇게 하면 커밋하지 않은 변경 사항을 잃지 않고도 다른 브랜치로 전환할 수 있다. 작업이 끝난 후에는 stash에 저장된 변경 사항을 다시 적용하여 작업을 이어갈 수 있다.
git stash는 내부적으로 스택(Stack) 구조를 가지는데 나중에 저장한 것이 가장 위에 쌓이며 stash@{0}, stash@{1} 같은 인덱스로 관리된다.
| 명령어 | 설명 |
|---|---|
| git stash | 현재 작업 내역을 스택에 저장하고 워킹 디렉토리를 비움git stash save "설명문" 형태도 가능 |
| git stash list | 스택에 저장된 stash 목록을 확인 |
| git stash apply | 가장 최근의(또는 특정) stash를 현재 브랜치에 적용 (삭제 안 함) |
| git stash pop | 가장 최근의 stash를 적용하고 스택에서 제거 |
| git stash drop | 스택에서 특정 stash를 제거 |
| git stash clear | 스택에 있는 모든 stash를 삭제 |
| git stash 예제 | |
|---|---|
- 4: stash 수행결과 file.txt 파일의 변경 사항이 사라진 것을 확인할 수 있다. 즉 아무 작업도 하지 않은 상태가 되어 혹시라도 현재 브랜치로 다른 작업을 진행할때 충돌이 발생하는 것을 방지한다.
- 10: stash에 저장된 변경 사항이 다시 file.txt 파일에 적용된 것을 확인할 수 있다.
커밋 리셋과 이동
상황
계속되는 작업 과정에 특정 커밋으로 돌아가서 그 시점부터 다시 작업을 시작하거나 특정 커밋만 선택적으로 현재 브랜치에 적용하고자 하는 경우가 있을 수 있다. 또한 커밋 메시지를 수정하거나 불필요한 커밋을 제거하고 싶을 때도 있다.
해결 방법
- 특정 커밋으로 돌아가려면
git reset혹은git revert를 사용할 수 있다. - 커밋 메시지를 수정하려면
git commit --amend를 사용한다. - 불필요한 커밋을 제거하거나 합치려면
git rebase -i로 인터랙티브 리베이스를 사용한다. - 특정 커밋만 선택적으로 골라 현재 브랜치에 적용하려면
git cherry-pick을 사용한다.
reset과 revert
두 명령어는 모두 커밋 히스토리를 변경하는 데 사용되지만, 그 방식과 목적이 다르다. 모두 "과거로 되돌아간다"는 목적은 동일하지만 그 방식이 기존 이력을 삭제하느냐(Reset) 아니면 새로운 이력으로 덮어쓰느냐(Revert)에서 결정적인 차이가 있다.
특히 협업 중인 원격 저장소에 이미 Push를 했다면 이 차이는 매우 중요하다.
| 구분 | Git Reset | Git Revert |
|---|---|---|
| 핵심 개념 | 시간을 과거로 되돌림 (커밋 삭제) | 과거를 되돌리는 새로운 커밋 생성 (반대 작업) |
| 히스토리 | 선택한 커밋 이후의 이력이 사라짐 | 모든 이력이 남으며, 취소 기록도 커밋으로 남음 |
| 사용 환경 | 주로 로컬 작업 중 (혼자 작업할 때) | 주로 원격 공유 중 (협업 중일 때) |
| 위험성 | Push된 이력을 Reset하면 다른 팀원과 충돌 발생 | 안전함 (기존 이력을 건드리지 않음) |
reset에는 다음과같은 옵션들이 있으니 참고한다.
| 옵션 | Head 이동 | Index(Staging) 반영 | Working Directory 반영 | 설명 |
|---|---|---|---|---|
| --soft | YES | NO | NO | 커밋만 취소하고 수정사항은 Stage에 남김 |
| --mixed | YES | YES | NO | (기본값) 커밋/Add 취소, 수정사항은 파일로 남김 |
| --hard | YES | YES | YES | 위험! 모든 수정사항을 삭제하고 과거로 완전히 복구 |
| git revert 예제 | |
|---|---|
인터랙티브 리베이스
상황
오랜기간 개발이 진행되는 프로젝트에서는 수많은 커밋들이 쌓이게 된다. 이때 커밋 히스토리를 깔끔하게 관리하기 위한 방법이 필요하다. 예를 들어, 여러 개의 작은 커밋을 하나로 합치거나, 커밋 메시지를 수정하거나, 불필요한 커밋을 제거하고 싶을 때가 있다.
해결 방법
git rebase -i명령어를 사용하여 인터랙티브 리베이스를 수행한다.- 텍스트 편집기에서 커밋들을 어떻게 처리할지 선택한다. 예를 들어,
pick을squash로 변경하여 커밋을 합치거나,edit으로 변경하여 커밋 메시지를 수정할 수 있다.
설명
인터랙티브 리베이스(git rebase -i)는 단순히 브랜치를 옮기는 것을 넘어, 과거의 커밋 히스토리를 사용자가 원하는 대로 수정, 삭제, 병합(Squash)할 수 있게 해주는 아주 강력한 도구이다. 마치 타임머신을 타고 과거로 가서 일기를 다시 쓰는 것과 비슷하다고 볼 수 있다.
리베이스를 실행하면 텍스트 에디터가 열리며, 각 커밋 앞에 실행할 작업을 지정할 수 있다.
| 명령어 | 약어 | 설명 |
|---|---|---|
| pick | p | 커밋을 그대로 유지함 |
| reword | r | 커밋 메시지만 수정함 |
| edit | e | 커밋 내용을 수정함 (작업 도중 멈춤) |
| squash | s | 해당 커밋을 이전 커밋과 하나로 합침 (메시지 통합) |
| fixup | f | squash와 같지만 메시지는 버리고 이전 것만 남김 |
| drop | d | 해당 커밋을 아예 삭제함 |
# 1. 최근 3개의 커밋에 대해 인터랙티브 리베이스 시작
$ git rebase -i HEAD~3
# 2. 명령어를 입력하면 에디터에 다음과 같이 나타난다:
pick a1b2c3d 첫 번째 기능 구현
pick e5f6g7h 오타 수정 (합치고 싶은 커밋)
pick i9j0k1l 스타일 수정 (합치고 싶은 커밋)
# 3. 두 번째와 세 번째 커밋을 첫 번째 커밋과 합치기 위해 'pick'을 'squash'로 변경
pick a1b2c3d 첫 번째 기능 구현
squash e5f6g7h 오타 수정
squash i9j0k1l 스타일 수정
# 4. 편집기를 저장하고 닫으면, 다음 단계로 넘어가 커밋 메시지를 편집할 수 있다.
# 5. 최종 커밋 메시지를 작성하고 저장하면 리베이스가 완료된다.
# 6. 로그를 확인하여 커밋이 합쳐졌는지 확인
$ git log --oneline --graph
히스토리 초기화
상황
때로는 커밋 히스토리를 완전히 초기화하고 싶을 때가 있다. 예를 들어 단순하고 자잘한 작업들이 너무 많이 쌓여서 히스토리가 복잡해진 경우 기존 히스토리를 모두 지우고 새로운 시작점을 만들고 싶을 수 있다.
해결 방법
이 경우 여러 해결 방법이 있을 수 있지만 여기서는 가장 간단한 방법을 소개한다.
- 현재 작업이 모두 커밋되어 있고 원격 저장소와 동기화 되어 있는지 확인한다.
- 필요하다면 백업을 받아두도록 한다.(물리적으로 폴더를 복사)
- 새로운 orphan 브랜치를 생성하고 checkout 한다음 모든 파일을 추가하고 커밋한다.
- main 브랜치를 삭제 한다.
- orphan 브랜치의 이름을
-m 옵션을 이용해 main 으로 변경한다. - 원격 저장소와 강제 push 한다.