Git 실무 핵심 정리

Git은 소스 코드의 변경 이력을 관리하는 분산 버전 관리 시스템이다.

개인 프로젝트에서는 코드를 안전하게 저장하고 이전 상태로 되돌리는 용도로 사용할 수 있지만, 팀 개발에서는 역할이 조금 더 크다. 여러 개발자가 같은 프로젝트를 동시에 수정하고, 각자의 작업을 분리하고, 변경사항을 검토한 다음 하나의 코드베이스로 합치는 과정 전체를 관리한다.

실무에서 Git을 사용한다면 단순히 add, commit, push 명령어를 아는 것보다 다음 흐름을 이해하는 것이 중요하다.

Repository -> Clone / Fork -> Branch -> 개발 -> Commit -> Push -> Pull Request -> Code Review -> 수정 -> Merge -> 최신 코드 동기화

팀 개발에서 Git은 사실상 변경사항을 안전하게 공유하고 통합하기 위한 협업 시스템으로 사용된다.


Repository

Repository는 Git이 프로젝트의 파일과 변경 이력을 관리하는 저장소다.

Repository는 크게 Local과 Remote로 나눠서 생각할 수 있다.

Repository -> Local Repository / Remote Repository
Local Repository -> 내 컴퓨터에 존재
Remote Repository -> GitHub / GitLab 등에 존재

개발자는 로컬에서 코드를 수정하고 Commit을 만든다.

그리고 Remote Repository에 Push하면 다른 개발자도 해당 변경사항을 확인할 수 있다.

Local Repository -> Push -> Remote Repository

여기서 Git과 GitHub를 구분해야 한다.

Git -> 버전 관리 시스템
GitHub -> Git Repository를 호스팅하고 협업 기능을 제공하는 서비스

GitHub가 없어도 Git은 사용할 수 있다.


Clone

이미 존재하는 Repository를 내 컴퓨터로 가져오는 것이 Clone이다.

git clone https://github.com/example/project.git

Clone을 하면 프로젝트 파일뿐만 아니라 Git의 Repository 정보와 기존 Commit history도 함께 가져온다.

일반적인 회사 프로젝트라면 개발자는 처음 Repository를 Clone한 뒤 자신의 작업 환경을 만든다.

Remote Repository -> Clone -> Local Repository

Clone은 보통 프로젝트에 처음 참여할 때 한 번 수행한다.

그 이후에는 매번 Clone하는 것이 아니라 fetch, pull 등을 사용해서 최신 변경사항을 가져온다.


Fork

Fork는 Repository를 내 계정 아래에 복사하는 기능이다.

주로 오픈소스 프로젝트처럼 원본 Repository에 직접 Push 권한이 없는 경우 사용한다.

Original Repository -> Fork -> My Repository

예를 들어 원본 Repository가

github.com/company/game-server

이고 내 계정으로 Fork하면

github.com/my-account/game-server

같은 별도의 Repository가 생긴다.

이제 내 Repository에는 Push할 수 있다.

Original Repository -> Fork -> My Repository -> Clone -> 내 PC

여기서 중요한 것은 Fork와 Clone은 완전히 다른 개념이라는 것이다.

Fork -> Remote Repository를 내 계정으로 복사
Clone -> Remote Repository를 내 컴퓨터로 가져옴

Origin과 Upstream

Fork Workflow에서는 Remote를 두 개 사용하는 경우가 많다.

origin -> 내 Fork Repository
upstream -> 원본 Repository

예를 들어

git remote -v

를 실행하면 다음과 같은 형태가 될 수 있다.

origin -> git@github.com:my-account/project.git
upstream -> git@github.com:company/project.git

작업한 코드는 내 Repository로 Push한다.

git push origin feature/login

원본 Repository의 최신 변경사항은 upstream에서 가져온다.

git fetch upstream

이 구조를 이해하면 오픈소스 프로젝트에 기여하는 과정이 훨씬 명확해진다.


Branch

실무에서는 main에서 직접 작업하지 않는 것이 일반적이다.

새로운 기능이나 버그 수정은 별도의 Branch에서 작업한다.

main -> feature/login / feature/inventory / fix/player-crash

예를 들어

git switch main
git pull
git switch -c feature/login

처럼 작업 Branch를 만든다.

Branch 이름은 프로젝트 규칙에 맞추면 된다.

대표적으로

feature/login
feature/inventory
fix/player-crash
refactor/network
hotfix/server-crash

같은 형태를 많이 사용한다.

중요한 것은 Branch 하나가 하나의 작업 목적을 가지도록 만드는 것이다.


왜 main에서 직접 개발하지 않는가

main은 일반적으로 다른 개발자들이 함께 사용하는 안정적인 기준 Branch다.

여기서 직접 개발하면 작업 중인 코드가 다른 사람에게 바로 영향을 줄 수 있다.

main -> 개발자 A의 작업 / 개발자 B의 작업 / 개발자 C의 작업

이런 상태에서는 서로의 작업이 쉽게 섞인다.

반면 Feature Branch를 사용하면

main -> feature/login / feature/inventory / fix/crash

처럼 작업을 분리할 수 있다.

작업이 끝난 뒤 Pull Request를 통해 검토하고 main에 통합한다.


개발

Branch를 만든 다음 실제 개발을 진행한다.

git switch -c feature/inventory

이후 코드를 수정하고 테스트한다.

Branch 생성 -> 코드 수정 -> Build -> Test -> 코드 수정 -> 다시 Test

여기서 중요한 것은 Git을 백업 도구처럼 사용하는 것이 아니라 작업 단위별로 변경사항을 관리하는 것이다.


Commit

작업한 내용을 Commit으로 기록한다.

git add Player.cpp
git commit -m "Add player inventory"

Commit은 단순한 저장이 아니다.

하나의 의미 있는 변경을 기록하는 것이 좋다.

예를 들어

Add inventory system

이라는 기능을 개발하면서

Add inventory data model
Add inventory manager
Add inventory UI
Fix inventory slot validation

처럼 의미 있는 단위로 Commit할 수 있다.

반대로

update
fix
test
final
final2

처럼 의미 없는 Commit을 계속 만드는 것은 나중에 history를 확인하기 어렵게 만든다.


Commit을 작게 만드는 이유

작은 Commit은 변경사항을 추적하기 쉽다.

예를 들어 버그가 발생했을 때

Commit A -> Player 이동 수정
Commit B -> Player 공격 수정
Commit C -> Inventory 수정

처럼 되어 있다면 어느 변경이 문제를 만들었는지 추적하기 쉽다.

또한 특정 Commit만 되돌리거나 Cherry-pick하는 것도 편해진다.

실무에서는 Commit의 개수보다 각 Commit의 의미가 명확한지가 중요하다.


Push

Local에서 만든 Commit을 Remote Repository로 보내는 것이 Push다.

git push origin feature/inventory

그러면 Local과 Remote의 Commit 상태가 동기화된다.

Local Repository -> Push -> Remote Repository

Fork Workflow라면 origin이 내 Repository이므로 내 Fork에 Push하게 된다.


Pull Request

실무에서 Git을 사용하는 가장 중요한 과정 중 하나다.

Pull Request, 줄여서 PR은 내 Branch의 변경사항을 다른 Branch에 반영해달라고 요청하는 것이다.

예를 들어

feature/inventory -> Pull Request -> main

구조가 된다.

Git 자체의 기능이라기보다 GitHub, GitLab 같은 Git 호스팅 서비스에서 제공하는 협업 기능이다.

PR에는 보통 다음 정보가 포함된다.

제목 / 변경 내용 / 변경 이유 / 테스트 방법 / 주의할 사항 / 관련 Issue

예를 들어

Title

Add player inventory system

본문에는

## 변경 내용

- Inventory 데이터 구조 추가
- 아이템 추가 / 제거 기능 구현
- 슬롯 제한 구현

## 테스트

- 아이템 추가
- 아이템 제거
- 슬롯 초과 상황 확인

같은 내용을 작성할 수 있다.


Pull Request의 본질

PR은 단순히 코드를 main에 넣는 버튼이 아니다.

코드 변경을 다른 개발자에게 공개하고 검토받는 과정이다.

개발자 -> Feature Branch -> Pull Request -> Code Review -> 승인 -> Merge

Review 과정에서 수정 요청이 발생하면

Pull Request -> 수정 요청 -> 추가 Commit -> Push -> 기존 PR 갱신

형태로 진행된다.

그래서 PR을 만들었다고 개발이 끝난 것이 아니다.

Review가 끝나고 Merge될 때까지가 하나의 작업 과정이다.


Code Review

Reviewer는 PR의 변경사항을 확인한다.

보통 다음과 같은 부분을 본다.

기능이 요구사항에 맞는가? / 버그가 없는가? / 기존 코드와 충돌하지 않는가? / 설계가 적절한가? / 불필요한 코드가 들어갔는가? / 성능 문제가 없는가? / 보안 문제가 없는가? / 테스트가 충분한가?

여기서 중요한 것은 Code Review가 코드 스타일만 검사하는 과정이 아니라는 것이다.

변경된 코드가 프로젝트의 기존 구조와 요구사항에 적합한지 판단하는 과정이다.


Review Comment

Reviewer가 문제가 있다고 판단하면 PR에 Comment를 남긴다.

예를 들어

이 로직은 Player가 제거된 이후에도 호출될 가능성이 있습니다.
수명 관리 방식을 확인해주세요.

개발자는 코드를 수정하고 다시 Push한다.

git add .
git commit -m "Fix player lifetime handling"
git push origin feature/inventory

그러면 기존 PR에 새로운 Commit이 자동으로 반영된다.

PR을 새로 만들 필요는 없다.

PR -> Commit A / Commit B / Commit C

이렇게 변경사항이 계속 누적된다.


Merge

Review가 끝나고 승인이 되면 Feature Branch를 대상 Branch에 Merge한다.

feature/inventory -> Merge -> main

GitHub나 GitLab에서는 PR 화면에서 Merge 버튼을 통해 처리하는 경우가 많다.


Squash Merge

실무에서는 Squash Merge도 많이 사용한다.

Feature Branch에서

D -> E -> F -> G -> H

처럼 여러 Commit을 만들었다고 하자.

Squash Merge를 사용하면 이들을 하나의 Commit으로 합쳐서 main에 반영할 수 있다.

Feature Branch -> D / E / F / G / H -> Squash -> main -> S

여기서 S는 Feature Branch의 전체 변경사항을 하나로 합친 Commit이다.

Feature Branch에서 작업 과정 중 Commit이 많더라도 main의 history는 깔끔하게 유지할 수 있다.


Merge Commit

일반적인 Merge는 Branch가 합쳐진 사실을 history에 남긴다.

main -> Merge Commit -> feature/inventory

Branch가 어떻게 분기되고 합쳐졌는지를 그대로 볼 수 있다는 장점이 있다.


Rebase Merge

Rebase를 이용해 Feature Branch의 Commit을 최신 main 위로 재배치한 뒤 통합하는 방식도 있다.

기존 -> A -> B -> C -> D -> E
Rebase -> A -> B -> C -> D' -> E'

History가 선형적으로 유지된다.

다만 Rebase는 Commit history를 재작성하기 때문에 이미 여러 사람이 공유한 Branch에서 함부로 사용하는 것은 피해야 한다.


세 가지 Merge 방식

실무에서 PR을 Merge할 때 자주 접하게 되는 방식은 다음 세 가지다.

Merge Commit -> Branch history를 그대로 유지
Squash Merge -> PR의 Commit을 하나로 합침
Rebase Merge -> Commit을 선형적으로 재배치

팀마다 정책이 다르기 때문에 프로젝트의 Git 전략을 따라야 한다.

개인적으로 어떤 방식이 무조건 정답이라고 볼 수는 없다.


Merge 후 Branch 정리

PR이 Merge되면 작업 Branch는 더 이상 필요하지 않은 경우가 많다.

Remote Branch를 삭제할 수 있다.

git push origin --delete feature/inventory

Local Branch도 삭제한다.

git branch -d feature/inventory

그리고 최신 main으로 돌아간다.

git switch main
git pull

새로운 작업을 시작할 때 다시 Branch를 만든다.

git switch -c feature/new-system

Fork Workflow

오픈소스 프로젝트처럼 원본 Repository에 직접 Push할 수 없는 경우에는 Fork Workflow를 사용한다.

전체 과정은 다음과 같다.

Original Repository -> Fork -> My Repository -> Clone -> Local Repository -> Branch 생성 -> 개발 -> Commit -> Push -> My Repository -> Pull Request -> Original Repository -> Code Review -> Merge

이것이 일반적인 오픈소스 기여 Workflow다.


Fork Repository 동기화

Fork를 해놓고 시간이 지나면 원본 Repository의 main에는 계속 새로운 Commit이 들어온다.

예를 들어

Original -> A -> B -> C -> D -> E
My Fork -> A -> B -> C

내 Fork가 오래된 상태가 되는 것이다.

이때 upstream을 통해 원본의 변경사항을 가져온다.

git fetch upstream

그리고 최신 원본 Branch를 기준으로 내 main을 업데이트한다.

git switch main
git merge upstream/main

또는 프로젝트 정책에 따라 Rebase를 사용할 수도 있다.

그다음 내 Fork에 Push한다.

git push origin main

전체적인 흐름은 다음과 같다.

Original Repository -> fetch -> Local Repository -> merge / rebase -> Local main -> push -> My Fork

Fork에서 Feature Branch를 만드는 이유

Fork했다고 main에서 직접 작업하는 것은 좋지 않다.

My Fork -> main / feature/login / fix/crash / feature/inventory

각 작업을 별도의 Branch로 분리한다.

그리고 해당 Branch를 내 Fork에 Push한다.

git push origin feature/login

이후 원본 Repository의 main을 대상으로 PR을 생성한다.

My Fork -> feature/login -> Pull Request -> Original Repository -> main

일반적인 팀 개발 Workflow

회사에서 직접 권한을 가진 Repository에 참여하는 경우라면 Fork가 필요하지 않을 수 있다.

보통 다음과 같은 구조를 사용한다.

Company Repository -> Clone -> Local Repository -> main 최신화 -> Feature Branch -> 개발 -> Commit -> Push -> Pull Request -> Code Review -> 수정 반영 -> 승인 -> Merge -> main

이후 다시 최신 main을 가져오고 새로운 Branch에서 다음 작업을 시작한다.


작업 시작 전에 main을 최신화하는 이유

다른 개발자가 이미 main에 코드를 Merge했을 수 있다.

내 Branch가 오래된 main에서 만들어졌다면 나중에 Merge할 때 Conflict가 발생할 가능성이 높아진다.

그래서 새로운 작업을 시작할 때는 보통

git switch main
git pull origin main
git switch -c feature/new-feature

처럼 최신 상태에서 Branch를 만든다.

작업이 오래 걸린다면 작업 중간에도 최신 main을 반영할 수 있다.

main -> A -> B -> C -> D
feature -> A -> B -> C -> E -> F

최신 mainD를 Feature Branch에 반영해서

main -> A -> B -> C -> D
feature -> A -> B -> C -> D -> E -> F

같은 최신 기준으로 작업할 수 있다.

팀의 정책에 따라 Merge 또는 Rebase를 사용한다.


Conflict가 발생했을 때

Conflict는 협업에서 피하기 어려운 문제다.

예를 들어

main -> A -> B -> C
feature -> A -> B -> D

에서 같은 파일의 같은 부분을 수정했다면 Merge 과정에서 충돌이 발생할 수 있다.

Git은 다음처럼 충돌 위치를 표시한다.

<<<<<<< HEAD
main의 코드
=======
feature의 코드
>>>>>>> feature

개발자는 실제 요구사항을 기준으로 어떤 코드를 남길지 결정한다.

수정한 뒤

git add .
git commit

또는 Rebase 중이라면

git add .
git rebase --continue

같은 방식으로 Conflict 해결을 완료한다.

Conflict를 무조건 피해야 하는 문제가 아니라 충돌이 발생했을 때 어떤 코드가 올바른지 판단하는 것이 개발자의 역할이다.


Git에서 자주 발생하는 실수

main에서 직접 작업

main -> 코드 수정 -> Commit

팀 개발에서는 작업 Branch를 만드는 것이 안전하다.

의미 없는 Commit

fix / update / test / final

나중에 history를 이해하기 어렵다.

너무 큰 PR

하나의 PR에

기능 추가 / 리팩터링 / 파일 구조 변경 / 코드 스타일 수정 / 버그 수정

을 전부 넣으면 Review하기 어렵다.

가능하면 하나의 PR이 하나의 명확한 목적을 갖도록 만드는 편이 좋다.

PR을 만든 뒤 추가 작업을 별도로 만들기

이미 PR이 열려 있다면 같은 Branch에 Commit하고 Push하면 기존 PR에 자동으로 반영된다.

오래된 Branch에서 계속 작업

최신 main과 차이가 너무 커지면 나중에 Conflict 해결 비용이 증가한다.


Git 실무 Workflow 한 번에 보기

일반적인 팀 개발

Repository -> Clone -> main -> 최신화 -> Feature Branch -> 코드 작성 -> Test -> Commit -> Push -> Pull Request -> Code Review -> 승인 -> Merge -> Branch 삭제 -> main 최신화

Review에서 수정 요청이 발생하면 다음 흐름으로 돌아간다.

Code Review -> 수정 요청 -> Commit -> Push -> Pull Request

Fork 기반 Workflow

Original Repository -> Fork -> My Repository -> Clone -> Local Repository -> Feature Branch -> 개발 -> Commit -> Push -> My Repository -> Pull Request -> Original Repository -> Code Review -> Merge

원본 Repository가 변경되면 다음 흐름으로 동기화한다.

Original Repository -> upstream fetch -> Local Repository -> merge / rebase -> Local main -> origin push -> My Repository

Git을 실무에서 어떻게 이해하면 되는가

Git 명령어 자체는 많지만 실무에서 반복하는 작업은 생각보다 단순하다.

최신 코드 가져오기 -> Branch 만들기 -> 개발 -> Commit -> Push -> PR -> Review -> 수정 -> Merge -> 최신화

Fork가 필요한 프로젝트라면 앞에

Fork -> Clone

이 추가된다.

Git을 제대로 사용한다는 것은 모든 명령어를 외우는 것이 아니다.

현재 내가

어떤 Repository에 있는지 / 어떤 Branch에서 작업하는지 / 내 변경사항이 어디까지 Commit됐는지 / Remote와 얼마나 차이가 나는지 / 어떤 Branch에 Merge하려는지

를 항상 알고 있는 것이 더 중요하다.


정리

실무에서 Git은 단순한 버전 관리 도구가 아니라 협업 Workflow를 구성하는 기반이다.

가장 기본적인 구조는 다음과 같다.

Repository -> Branch -> Commit -> Push -> Pull Request -> Review -> Merge

Fork를 사용하는 프로젝트라면

Original Repository -> Fork -> Clone -> Branch -> Commit -> Push -> Pull Request -> Review -> Merge

가 된다.

그리고 PR이 Merge된 뒤에는 다시 최신 main을 가져와 다음 작업을 시작한다.

Merge -> main 최신화 -> 새로운 Branch -> 새로운 작업

Git에서 가장 중요한 것은 git addgit commit 같은 명령어를 많이 아는 것이 아니다.

내 작업을 다른 작업과 분리하고, 변경사항을 의미 있는 단위로 기록하고, 다른 개발자의 검토를 거쳐 안전하게 하나의 코드베이스로 통합하는 것.

실무에서 Git을 사용하는 이유는 결국 이 Workflow를 안정적으로 유지하기 위해서다.