Published on

Git rebase 후 강제푸시 막힘 - 보호규칙 해제법

Authors

서론

git rebase로 커밋 히스토리를 정리한 뒤 git push --force(또는 --force-with-lease)를 하려는데 “protected branch”, “pre-receive hook declined”, “You are not allowed to force push” 같은 메시지로 막히는 상황은 흔합니다.

문제는 Git이 아니라 원격 저장소(예: GitHub/GitLab)의 보호 규칙(Branch protection / Protected branches) 입니다. 보호 규칙은 기본적으로 “히스토리 재작성(=force push)”을 위험 작업으로 보고 차단합니다.

이 글에서는 다음을 다룹니다.

  • rebase 후 왜 강제 푸시가 필요한지, 그리고 왜 막히는지
  • GitHub/GitLab에서 보호 규칙을 임시 해제하거나 예외를 부여하는 방법
  • 팀 사고(커밋 유실, PR 꼬임)를 피하는 안전한 절차
  • 강제 푸시 대신 선택할 수 있는 대안

CI/CD가 얽혀 있으면 작은 히스토리 변경이 파이프라인 전체를 흔들기도 합니다. 캐시나 아티팩트가 꼬여 “같은 커밋인데 결과가 다르게 나오는” 상황도 있는데, 이런 경우는 별도로 GitLab CI 캐시 꼬임 - 빌드 완전 초기화 가이드도 함께 참고하면 좋습니다.


1) rebase 후 강제 푸시가 필요한 이유

rebase는 커밋을 “새로 만든 커밋”으로 재작성합니다. 즉, 로컬 브랜치의 커밋 SHA들이 바뀝니다.

원격 브랜치가 기존 SHA들을 가리키고 있으면, 일반 git pushfast-forward가 아니므로 거부됩니다.

# 예시: main에서 feature 브랜치 커밋을 정리
git checkout feature
git fetch origin
git rebase origin/main

# 이후 원격 feature를 덮어써야 하므로 강제 푸시 필요
git push --force-with-lease origin feature

여기서 핵심은 --force보다 --force-with-lease를 우선 쓰는 것입니다.

  • --force: 원격이 누가 바꿨든 무조건 덮어씀
  • --force-with-lease: “내가 마지막으로 본 원격 상태”에서 변한 게 있으면 푸시를 거부(안전장치)

하지만 브랜치 보호 규칙이 켜져 있으면 --force-with-lease도 동일하게 막힙니다.


2) “막힘”의 정체: 브랜치 보호 규칙/훅

대표적인 거부 메시지 유형은 다음과 같습니다.

  • GitHub: Protected branch update failed / Cannot force-push to this branch
  • GitLab: You are not allowed to force push code to a protected branch on this project
  • 서버 훅: pre-receive hook declined

대부분은 아래 중 하나입니다.

  1. 브랜치가 Protected로 설정됨(main/master/release 등)
  2. Force push 금지 옵션이 켜짐
  3. 권한(Role) 부족(maintainer/admin만 가능)
  4. 필수 상태 체크(required status checks) 또는 필수 리뷰(required approvals) 정책

중요: “보호 규칙을 꺼야만 한다”가 정답은 아닙니다. 대부분의 팀에서는 보호 규칙은 유지하고, “rebase된 브랜치를 새 브랜치로 올려 PR”이 더 안전합니다. 다만 이미 운영 중인 브랜치(예: release)에서 히스토리 정리가 반드시 필요한 경우에만 예외적으로 해제합니다.


3) 먼저 확인할 것: 정말 force push가 필요한가?

보호 규칙을 풀기 전에, 아래 대안을 검토하세요.

대안 A: 새 브랜치로 푸시 후 PR 만들기(권장)

기존 브랜치를 덮어쓰지 않고, rebase 결과를 새 브랜치로 올립니다.

# rebase 결과를 새 브랜치로 분기
git checkout feature
git checkout -b feature-rebased
git push origin feature-rebased

이후 PR에서 머지 전략을 팀 규칙에 맞춰 선택합니다(squash merge 등).

대안 B: git revert로 되돌리기(히스토리 보존)

이미 공유된 브랜치(main 등)라면 rebase 대신 revert가 사고를 줄입니다.

# 특정 커밋을 되돌리는 새 커밋 생성
git revert <bad-commit-sha>

대안 C: merge로 해결(히스토리 정리 포기)

히스토리 미관보다 안전이 우선이면 merge가 낫습니다.


4) GitHub: 보호 규칙 해제/예외 설정 방법

GitHub는 “Branch protection rules”에서 force push 허용 여부를 제어합니다.

4-1. 규칙 위치

  • Repository → SettingsBranches → Branch protection rules

4-2. 임시 해제(가장 단순하지만 위험)

  1. 해당 브랜치 규칙(Edit) 진입
  2. 아래 옵션을 상황에 맞게 조정
    • Require a pull request before merging (필요 시 잠시 해제)
    • Require status checks to pass (필요 시 잠시 해제)
    • Restrict who can push to matching branches (권한 조정)
    • Allow force pushes 체크
  3. 작업 후 즉시 원복

권장 운영 팁:

  • “Allow force pushes”는 항상 켜두는 옵션이 아니라, 필요할 때만 켜는 게 일반적입니다.

4-3. 특정 사용자/팀만 예외로 두기

조직/팀 운영에서는 “누구나 force push 허용”보다 “관리자/릴리즈 매니저만 허용”이 안전합니다.

  • Restrict who can push를 사용해 푸시 권한을 제한
  • 규칙을 우회할 수 있는 역할(관리자)만 임시 작업

4-4. 안전한 강제 푸시 절차(GitHub 공통)

보호 규칙을 풀었다면, 푸시 자체는 다음처럼 진행합니다.

# 1) 최신 원격 상태 확인
git fetch origin

# 2) 내가 덮어쓰려는 대상 브랜치 확인
git log --oneline --decorate -n 5 origin/main
git log --oneline --decorate -n 5 main

# 3) 안전 강제 푸시
git push --force-with-lease origin main

--force-with-lease가 거부되면, 누군가 이미 원격을 바꿨을 수 있습니다. 이때는 억지로 --force로 밀어버리기 전에 팀에 공유하고 재확인하세요.


5) GitLab: Protected branches에서 force push 허용하기

GitLab은 브랜치 단위로 “Protected”를 걸고, 푸시/머지 권한을 Role로 통제합니다.

5-1. 설정 위치

  • Project → SettingsRepositoryProtected branches

5-2. force push 허용(또는 보호 해제)

GitLab 버전에 따라 UI 문구가 조금씩 다르지만 보통 아래 중 하나가 있습니다.

  • 브랜치 보호 항목에서
    • Allowed to push: Maintainers(또는 특정 사용자)
    • Allowed to merge: Maintainers/Developers
    • Allow force push: 체크

또는 더 단순하게 보호를 잠시 해제(Unprotect)할 수도 있습니다.

5-3. 흔한 함정: Maintainer가 아니면 안 됨

GitLab은 “보호 브랜치 강제 푸시” 자체를 Maintainer 이상만 허용하는 구성이 흔합니다. 즉, 설정을 바꿀 권한이 없으면 로컬에서 아무리 --force-with-lease를 해도 해결되지 않습니다.

이때는 다음 중 하나를 선택해야 합니다.

  • Maintainer에게 임시 권한 요청
  • 새 브랜치로 푸시 후 MR(Merge Request)로 처리

6) 강제 푸시 전 반드시 백업하기(사고 방지 체크리스트)

강제 푸시는 “원격 브랜치가 가리키는 커밋 포인터”를 바꿉니다. 잘못하면 팀원이 참조하던 커밋이 브랜치에서 사라져 혼란이 생깁니다(커밋이 즉시 삭제되는 건 아니지만, 접근성이 떨어짐).

6-1. 원격 브랜치 백업 태그/브랜치 만들기

푸시 전에 원격 HEAD를 별도 참조로 남겨두면 복구가 매우 쉬워집니다.

# 원격 main의 현재 커밋을 로컬로 가져온 뒤
git fetch origin

# 백업 브랜치 생성(로컬)
git branch backup/main-before-rebase origin/main

# 원격에도 백업 브랜치 푸시
git push origin backup/main-before-rebase

6-2. PR/MR이 열려 있다면 영향 확인

  • rebase + force push는 PR의 커밋 리뷰 스레드를 꼬이게 만들 수 있습니다.
  • CI가 “새 커밋”으로 인식해 전체 재실행될 수 있습니다.

6-3. 팀 공지 템플릿(예시)

  • 대상 브랜치: release/1.2
  • 작업 시간: 10:00~10:10
  • 내용: rebase로 커밋 정리 후 --force-with-lease 예정
  • 대응: 작업 중 해당 브랜치로 push 금지, 로컬 브랜치 재동기화 안내

7) 작업 후 팀원 로컬 복구 가이드

강제 푸시 이후 팀원 로컬은 “원격과 다른 히스토리”가 됩니다. 보통 아래 중 하나로 정리합니다.

7-1. 로컬 변경이 없을 때: hard reset

git fetch origin
git checkout main
git reset --hard origin/main

7-2. 로컬에 작업이 있을 때: rebase 또는 cherry-pick

# 내 작업 브랜치를 새 main 위로 다시 얹기
git fetch origin
git checkout my-feature
git rebase origin/main

충돌이 잦다면, 내 커밋만 골라 새 브랜치에 옮기는 방식도 안전합니다.

git checkout -b my-feature-new origin/main
git cherry-pick <commit1> <commit2>

8) “보호 규칙을 풀었는데도” 여전히 막히는 경우

보호 규칙 외에도 다음이 원인일 수 있습니다.

  • 서버 측 훅(pre-receive)에서 커밋 메시지/서명/이슈키를 강제
  • DCO/서명(Verified) 정책
  • CI 상태 체크가 필수인데, 체크가 아직 없거나 실패
  • 권한 토큰 범위(scope) 부족(예: PAT에 write 권한 없음)

특히 GitLab에서는 그룹 정책이나 커스텀 훅이 걸려 있으면 UI에서 force push를 허용해도 별도 훅이 막을 수 있습니다.


9) 결론: 해제는 “절차”로 관리해야 안전하다

rebase 후 강제 푸시는 기술적으로는 간단하지만, 협업 환경에서는 사고 가능성이 큽니다. 정리하면 다음 순서가 가장 안전합니다.

  1. 정말 force push가 필요한지 재검토(가능하면 새 브랜치 + PR/MR)
  2. 불가피하면 원격 백업 브랜치/태그 생성
  3. GitHub/GitLab 보호 규칙에서 최소 범위로 임시 허용(가능하면 특정 역할만)
  4. git push --force-with-lease로 반영
  5. 보호 규칙 원복 + 팀원 로컬 재동기화 안내

운영 환경에서의 변경은 항상 “되돌릴 수 있는가”가 핵심입니다. 강제 푸시도 백업과 절차만 갖추면 충분히 통제 가능한 작업이 됩니다.