Git 패스트 포워드
Git 패스트 포워드(Git fast-forward)는 Git에서 현재 브랜치가 가리키는 커밋이 병합 대상 커밋의 조상(ancestor)인 경우, 별도의 병합 커밋(merge commit)을 만들지 않고 현재 브랜치의 포인터를 대상 커밋으로 이동시키는 병합 방식이다.[1]
Git에서 두 브랜치를 병합할 때 현재 브랜치와 대상 브랜치의 커밋 이력이 서로 갈라지지 않았다면 새로운 병합 커밋을 만들 필요가 없다. 현재 브랜치가 가리키는 커밋에서 대상 브랜치의 최신 커밋까지 하나의 커밋 계보를 따라 이동할 수 있기 때문이다.
예를 들어 다음과 같은 커밋 구조가 있다고 가정한다.
A---B main
\
C---D feature
`main` 브랜치의 최신 커밋 `B`는 `feature` 브랜치의 최신 커밋 `D`의 조상이다. 이 상태에서 `main`에 `feature`를 병합하면 Git은 `main`의 브랜치 포인터를 `D`까지 이동시킬 수 있다.
A---B---C---D main, feature
이 과정에서는 새로운 병합 커밋이 생성되지 않는다. Git 공식 Pro Git 문서에서도 한 브랜치가 다른 브랜치 이후의 커밋을 가리키고 있을 경우 브랜치 포인터를 최신 커밋으로 이동시키는 방식을 패스트 포워드라고 설명한다.[2]
일반적인 `git merge`는 패스트 포워드가 가능한 경우 기본적으로 이를 이용한다.[1]
예를 들어 다음과 같이 `feature` 브랜치를 `main`에 병합할 수 있다.
git switch main
git merge feature
패스트 포워드가 이루어지면 출력에 다음과 같이 `Fast-forward`가 표시될 수 있다.[2]
Updating abc1234..def5678 Fast-forward
이 경우 `main` 브랜치가 가리키는 커밋만 `feature`의 최신 커밋으로 이동하며 별도의 merge commit은 생성되지 않는다.
패스트 포워드가 가능하려면 현재 브랜치의 최신 커밋이 병합 대상 브랜치 최신 커밋의 조상이어야 한다.[1]
다음과 같은 경우 패스트 포워드가 가능하다.
A---B---C main
\
D---E feature
`main`이 가리키는 `C`는 `feature`가 가리키는 `E`의 조상이므로 `main`의 포인터를 `E`로 이동시키는 것만으로 병합을 완료할 수 있다.
반면 두 브랜치에서 각각 새로운 커밋이 만들어져 이력이 갈라진 경우에는 패스트 포워드만으로 병합할 수 없다.
C---D main
/
A---B
\
E---F feature
이 구조에서 `D`는 `F`의 조상이 아니다. 따라서 단순히 `main`의 포인터를 `F`로 이동시키면 `C`와 `D`의 변경 이력을 현재 브랜치에서 잃게 된다.
일반적인 `git merge feature`를 실행하면 Git은 두 브랜치의 끝과 공통 조상을 이용하는 3-way merge를 수행하고 그 결과를 새로운 병합 커밋으로 기록한다.[2]
C---D
/ \
A---B G main
\ /
E---F feature
`git merge`는 `--ff`, `--no-ff`, `--ff-only` 옵션으로 패스트 포워드 동작을 제어할 수 있다.[1]
| 옵션 | 동작 |
|---|---|
--ff
|
가능한 경우 패스트 포워드한다. 패스트 포워드가 불가능하면 일반적인 병합을 수행해 병합 커밋을 생성한다. |
--no-ff
|
패스트 포워드가 가능한 경우에도 병합 커밋을 생성한다. |
--ff-only
|
패스트 포워드가 가능한 경우에만 병합하고, 불가능하면 병합하지 않고 오류 상태로 종료한다. |
`--ff`는 병합 대상 이력이 현재 이력의 후손이라면 브랜치 포인터만 이동시키고 별도의 병합 커밋을 만들지 않는다. 일반적인 상황에서 `git merge`의 기본 동작이다. 다만 자연스러운 `refs/tags/` 위치에 저장되지 않은 annotated tag를 병합하는 경우 등의 예외가 있다.[1]
git merge --ff feature
`--no-ff`를 사용하면 패스트 포워드가 가능한 경우에도 새로운 병합 커밋을 생성한다.[1]
git merge --no-ff feature
예를 들어 원래 다음과 같은 구조에서,
A---B---C main
\
D---E feature
일반적인 패스트 포워드를 사용하면 다음처럼 된다.
A---B---C---D---E main, feature
반면 `--no-ff`를 사용하면 개념적으로 다음과 같이 기능 브랜치가 병합되었다는 사실을 나타내는 별도의 병합 커밋을 남길 수 있다.
A---B---C-------M main
\ /
D---E feature
따라서 기능 브랜치 단위의 병합 기록을 커밋 그래프에 명시적으로 남길 필요가 있는 개발 정책에서 사용할 수 있다.
`--ff-only`는 패스트 포워드가 가능한 경우에만 병합한다. 두 브랜치의 이력이 갈라져 패스트 포워드가 불가능하면 Git은 병합 커밋을 자동으로 생성하지 않고 명령을 실패시킨다.[1]
git merge --ff-only feature
따라서 병합으로 인해 의도하지 않은 비선형 커밋 이력이 생성되는 것을 방지하려는 경우 사용할 수 있다.
패스트 포워드와 비 패스트 포워드 병합의 가장 큰 차이는 새로운 병합 커밋의 생성 여부와 커밋 그래프의 형태이다.[1]
| 구분 | 패스트 포워드 | 비 패스트 포워드 병합 |
|---|---|---|
| 조건 | 현재 커밋이 대상 커밋의 조상 | 두 브랜치의 이력이 서로 갈라진 경우 등 |
| 브랜치 포인터 | 대상 커밋으로 이동 | 새 병합 커밋으로 이동 |
| 새 병합 커밋 | 생성하지 않음 | 일반적으로 생성 |
| 커밋 그래프 | 선형 구조를 유지 | 분기와 병합 구조가 남음 |
| 3-way merge | 필요하지 않음 | 일반적으로 수행 |
패스트 포워드는 서로 독립적으로 변경된 두 이력을 실제로 합치는 작업이 아니라 기존 커밋 계보를 따라 브랜치의 위치를 앞으로 이동시키는 작업에 가깝다. 따라서 일반적인 3-way merge에서 발생할 수 있는 변경 내용 간의 병합 충돌도 패스트 포워드 자체에서는 발생하지 않는다.[1]
Git rebase는 브랜치의 기반을 다른 커밋으로 변경하면서 기존 변경 사항을 새로운 기반 위에 다시 적용한다. 이를 이용하면 서로 갈라져 있던 브랜치를 패스트 포워드가 가능한 구조로 만들 수 있다.
예를 들어 다음과 같이 `main`과 `feature`가 갈라져 있다고 가정한다.
C---D main
/
A---B
\
E---F feature
이 상태에서는 `main`에서 `feature`로 바로 패스트 포워드할 수 없다.
`feature`를 `main` 위로 rebase하면 개념적으로 다음과 같이 커밋 이력을 구성할 수 있다.
A---B---C---D main
\
E'---F' feature
이후 `main`에서 `feature`를 병합하면 `main`의 포인터를 `F'`로 이동시키는 패스트 포워드가 가능하다.
다만 rebase와 패스트 포워드는 서로 다른 작업이다. rebase는 기존 변경 사항을 새로운 기반 위에 다시 적용하면서 새로운 커밋을 만들기 때문에 해당 커밋들의 ID가 변경될 수 있다. 반면 패스트 포워드 자체는 기존 커밋을 다시 작성하지 않고 브랜치 포인터를 기존 커밋으로 이동시킨다.
`git pull`은 원격 저장소에서 변경 사항을 가져온 뒤 현재 브랜치에 통합하는 명령이다. 현재 Git 공식 문서에서는 `git pull`이 먼저 `git fetch`를 실행한 뒤 선택된 방식에 따라 원격 브랜치의 변경 사항을 현재 브랜치에 통합한다고 설명한다.[3]
로컬 브랜치에 원격 브랜치와 갈라지는 별도의 이력이 없다면 패스트 포워드 방식으로 갱신할 수 있다.
git pull --ff-only
`--ff-only`를 사용하면 로컬 브랜치와 원격 브랜치의 이력이 갈라지지 않은 경우에만 갱신한다. 이력이 갈라져 패스트 포워드할 수 없다면 명령이 실패하므로, 자동으로 병합 커밋이 만들어지는 것을 원하지 않을 때 사용할 수 있다.[3]
패스트 포워드 정책은 명령행 옵션뿐 아니라 Git 설정을 통해 지정할 수도 있다. `merge.ff`를 `false`로 설정하면 `git merge --no-ff`와 같은 동작을 하며, `only`로 설정하면 `git merge --ff-only`와 같은 방식으로 패스트 포워드 병합만 허용한다.[1]
예를 들어 모든 저장소에서 패스트 포워드 가능한 병합만 허용하도록 설정하려면 다음과 같이 구성할 수 있다.
git config --global merge.ff only
특정 저장소에만 적용하려면 `--global`을 제외한다.
git config merge.ff only
`git pull`의 패스트 포워드 정책은 `pull.ff` 설정으로도 제어할 수 있다.[3]
- ↑ 1.00 1.01 1.02 1.03 1.04 1.05 1.06 1.07 1.08 1.09 Git, 「git-merge Documentation」, https://git-scm.com/docs/git-merge, 2026년 8월 19일 확인.
- ↑ 2.0 2.1 2.2 Scott Chacon, Ben Straub, 「Git 브랜치 - 브랜치와 Merge 의 기초」, https://git-scm.com/book/ko/v2/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EB%B8%8C%EB%9E%9C%EC%B9%98%EC%99%80-Merge-%EC%9D%98-%EA%B8%B0%EC%B4%88.html, 2026년 8월 19일 확인.
- ↑ 3.0 3.1 3.2 Git, 「git-pull Documentation」, https://git-scm.com/docs/git-pull, 2026년 8월 19일 확인.
