git push --force-with-lease도 동료의 커밋을 지울 수 있다: 백그라운드 fetch와 --force-if-includes
rebase 뒤 강제 푸시할 때 --force-with-lease가 무엇을 비교하는지, IDE의 백그라운드 fetch가 그 보호를 무력화하는 이유, Git 2.30에 추가된 --force-if-includes와 기대값을 직접 적는 방법을 Git 공식 문서로 정리했습니다.
rebase로 정리한 브랜치를 다시 올리려면 강제 푸시가 필요합니다. 이때 --force 대신 --force-with-lease를 쓰라는 조언을 흔히 듣지만, 옵션만 바꾼다고 동료의 커밋이 늘 안전해지지는 않습니다. 이 옵션이 무엇과 무엇을 비교하는지 알아야 보호가 언제 사라지는지 보입니다.
이 글은 Git 2.56.0(2026년 9월 28일) 기준 git-scm.com의 git-push·git-config 문서를 따릅니다. git-push 문서는 2.55.0에서 마지막으로 바뀌었습니다.
강제 푸시가 필요한 이유
git push는 보통 원격 브랜치를 fast-forward가 아닌 방식으로 갱신하지 않습니다. 즉 원격 브랜치의 끝 커밋이 새로 올릴 커밋의 조상이어야 합니다. rebase는 이미 올린 커밋을 새 커밋으로 바꾸므로, 원격에 남은 옛 끝 커밋은 새 이력의 조상이 아니게 됩니다. --force와 refspec 앞의 +는 이 검사를 끕니다.
문서는 --force를 이력을 잃을 생각일 때 쓰는 방법이라고 설명하고, rebase하는 사이 누군가 원래 이력 위에 커밋을 올렸다면 무작정 --force로 푸시할 때 그 작업이 사라진다고 경고합니다. 또 --force는 푸시하는 모든 ref에 적용되므로 push.default가 matching이면 현재 브랜치가 아닌 ref까지 덮어쓸 수 있습니다. 브랜치 하나만 강제하려면 git push origin +feature처럼 refspec 앞에 +를 붙입니다.
–force-with-lease가 비교하는 값
--force-with-lease는 원격 ref의 현재 값이 기대한 값과 같을 때만 갱신하고, 다르면 푸시를 실패시킵니다. 기대값을 어디서 가져오는지는 형식마다 다릅니다.
| 형식 | 기대값 |
|---|---|
--force-with-lease |
갱신할 각 ref에 대응하는 원격 추적 브랜치(예: origin/feature) |
--force-with-lease=<ref> |
그 ref 하나의 원격 추적 브랜치 |
--force-with-lease=<ref>:<expect> |
직접 적은 <expect>. 빈 문자열이면 그 ref가 원격에 아직 없어야 함 |
문서는 기대값을 직접 적는 마지막 형식을 뺀 나머지는 아직 실험적이어서 의미가 바뀔 수 있다고 밝힙니다.
백그라운드 fetch가 보호를 없애는 과정
기대값을 적지 않은 형식은 내 원격 추적 브랜치를 내가 본 원격 상태로 간주합니다. 편집기나 cron이 git fetch origin을 백그라운드에서 실행하면 이 가정이 깨집니다.
- 내가
feature를 rebase하는 동안 동료가 원격feature에 커밋 X를 푸시합니다. - 편집기가 백그라운드에서 fetch해
origin/feature가 X를 가리킵니다. 나는 X를 본 적도, rebase에 넣은 적도 없습니다. git push --force-with-lease를 실행하면 원격의 현재 값과origin/feature가 둘 다 X이므로 검사를 통과하고, X는 원격 브랜치에서 사라집니다.
문서도 이 조합이 매우 나쁘게 상호작용한다고 적습니다. Git에는 원격 추적 정보 말고 판단 근거가 없으므로, 백그라운드에서 ref를 갱신하는 프로세스가 있으면 --force보다 나은 점이 사라진다는 설명입니다.
막는 방법 두 가지
첫째, 기대값을 직접 적습니다. 문서의 예처럼 rebase 전에 본 지점에 태그를 달아 두면, 백그라운드 fetch가 origin/master를 바꿔도 원격이 여전히 그 커밋일 때만 덮어씁니다.
git fetch # 원격의 master를 가져옴
git tag base master # 내가 본 기준점 표시
git rebase -i master # 커밋 다시 쓰기
git push --force-with-lease=master:base master:master
둘째, Git 2.30.0부터 있는 --force-if-includes를 함께 씁니다. 이 옵션은 원격 추적 ref의 끝 커밋이 로컬 브랜치의 reflog 항목 중 하나에서 도달 가능한지 확인하고, 아니면 강제 갱신을 거부합니다. 위 예에서 X는 내 feature의 어떤 reflog 항목에서도 도달할 수 없으므로 푸시가 거부됩니다.
git push --force-with-lease --force-if-includes
# 매번 적기 번거로우면
git config --global push.useForceIfIncludes true
push.useForceIfIncludes를 true로 두면 --force-if-includes를 준 것과 같고, 푸시할 때 --no-force-if-includes로 끌 수 있습니다. 주의할 점도 있습니다. --force-if-includes는 --force-with-lease 없이 쓰거나 <ref>:<expect> 형식과 함께 쓰면 아무 일도 하지 않습니다. 또 문서에 따르면 --force는 --force-with-lease의 검사까지 끕니다.
원격 추적 브랜치의 끝 커밋이 내 reflog에서 도달할 수 없어 --force-if-includes가 푸시를 거부했다면, 내가 반영하지 않은 원격 작업이 있다는 뜻입니다. 이력을 다시 쓰거나 푸시하기 전에 fetch해서 그 작업부터 확인합니다. 로컬과 원격 추적 브랜치가 어디서 갈라졌는지 볼 때 쓰는 ... 표기는 git log와 git diff의 ..과 …에서 다뤘습니다.
참고한 문서
- git-push (2.55.0에서 마지막 변경)
- git-push 2.30.0 문서:
--force-if-includes가 처음 실린 버전 - Git 2.30.0 릴리스 노트:
--force-if-includes를 새 옵션으로 명시 - git-config (2.56.0):
push.useForceIfIncludes
링크와 내용은 2026년 10월 6일에 확인했습니다.