협업 시 git push -f가 위험한 이유

“커밋 메시지를 수정했는데 push가 안 돼. 그냥 -f로 강제 푸시하면 되겠지?”

이런 생각으로 git push -f를 실행하는 순간, 동료의 작업이 증발할 수 있습니다. 이번 포스트에서는 force push의 위험성과 안전한 사용법, 그리고 민감한 파일을 완전히 삭제하는 방법까지 다뤄보겠습니다.


왜 push가 거부되는가?

git commit --amendgit reset을 사용하면 로컬 저장소의 히스토리가 변경됩니다. 이때 원격 저장소에 push하려고 하면 Git은 다음과 같은 오류를 뱉습니다.

! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.

이유: 로컬의 히스토리가 원격보다 뒤처지거나 갈라졌기 때문입니다. Git은 기본적으로 히스토리를 덮어쓰는 것을 허용하지 않습니다.


Force Push: 강제로 덮어쓰기

이런 상황에서 사용하는 것이 force push입니다.

# 기본 force push (위험!)
git push origin [브랜치명] -f

# 또는 더 안전한 옵션 (추천)
git push origin [브랜치명] --force-with-lease

☠️ 협업 시 위험성

git push -f를 실행하는 순간, 원격 저장소의 히스토리가 내 로컬 기준으로 강제로 덮어씌워집니다.

시나리오:

  1. 나: 로컬에서 git reset HEAD~3으로 커밋 3개를 되돌림
  2. 동료 A: 그 사이에 새로운 커밋을 원격에 push
  3. 나: git push -f 실행
  4. 결과: 동료 A의 커밋이 영구적으로 삭제되어 증발

이것이 협업 환경에서 force push가 위험한 이유입니다. 동료의 작업을 무의식적으로 지워버릴 수 있기 때문이죠.


안전한 Force Push: –force-with-lease

--force-with-lease는 “누가 그 사이에 푸시하지 않았을 때만 강제 푸시해줘”라는 안전장치가 달린 옵션입니다.

작동 원리

git push origin main --force-with-lease

이 명령어는 다음과 같이 동작합니다:

  1. 원격 브랜치의 현재 상태를 확인: 마지막으로 fetch한 시점의 원격 브랜치 상태를 기억
  2. 상태 비교: 현재 원격 브랜치가 기억한 상태와 같으면 push 허용
  3. 상태가 다르면 거부: 누군가 그 사이에 push했다면 push를 거부하고 오류 메시지 출력

실무에서의 사용 패턴

✅ 안전한 사용처

나 혼자 쓰는 Feature 브랜치: PR을 올리기 전, 커밋 메시지를 예쁘게 정리(rebase)하거나 오타를 수정(amend)한 뒤에는 자유롭게 사용해도 됩니다.

# Feature 브랜치에서 커밋 정리 후
git rebase -i HEAD~5
git push origin feature/my-feature --force-with-lease

❌ 절대 하지 말아야 할 것

  • main/master 브랜치에 직접 force push: 팀 전체의 히스토리를 망가뜨릴 수 있습니다
  • 다른 사람이 작업 중인 브랜치에 force push: 동료의 작업을 날릴 수 있습니다

Pro Tip

실무에서는 --force-with-lease를 습관화하는 것이 좋습니다. -f보다 안전하면서도 필요한 상황에서는 동일하게 작동합니다.

# alias로 설정하면 더 편리합니다
git config --global alias.pushf 'push --force-with-lease'

BFG Repo-Cleaner: 민감한 파일 완전 삭제

git rm으로 파일을 지우고 커밋해도, Git의 과거 히스토리(Log)에는 파일이 여전히 남아있습니다. 해커들은 이 로그를 뒤져 AWS Key, 비밀번호, API 토큰 등을 찾아냅니다.

문제 상황

# 실수로 비밀번호 파일을 커밋
git add secret.json
git commit -m "Add config"
git push

# 나중에 깨달음
git rm secret.json
git commit -m "Remove secret file"
git push

이렇게 해도 git log를 보면 과거 커밋에 secret.json이 그대로 남아있습니다. 누구나 git show [커밋해시]:secret.json으로 내용을 볼 수 있습니다.

해결책: BFG Repo-Cleaner

BFG Repo-Cleanergit filter-branch보다 10~720배 빠르고 사용법이 간단한 도구입니다.

1. BFG 다운로드

BFG 공식 사이트에서 bfg.jar를 다운로드합니다.

2. 저장소 클론 (bare repository)

BFG는 bare repository에서만 작동합니다.

# 원격 저장소를 bare로 클론
git clone --mirror https://github.com/username/my-repo.git my-repo.git

3. 민감한 파일 삭제

# 저장소의 모든 역사에서 'secret.json'을 찾아 삭제
java -jar bfg.jar --delete-files secret.json my-repo.git

# 여러 파일을 한 번에 삭제
java -jar bfg.jar --delete-files "{secret.json,config.json,password.txt}" my-repo.git

추가 옵션

# 특정 크기 이상의 파일 삭제 (예: 100MB 이상)
java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git

# 특정 폴더 삭제
java -jar bfg.jar --delete-folders sensitive-data my-repo.git

⚠️ 주의사항

  1. 백업 필수: BFG는 히스토리를 영구적으로 변경합니다. 실행 전 반드시 백업하세요.
  2. 팀 협의: 공유 저장소라면 팀원들과 반드시 협의 후 진행하세요.
  3. 재클론 권장: 작업 후 팀원들은 저장소를 다시 클론하는 것이 안전합니다.

참고 자료

  • Git 공식 문서: git push, git reset, git rebase 사용 방법
  • BFG Repo-Cleaner 공식 문서: BFG 사용 예시와 주의사항
  • Git Pro 2nd Edition: 히스토리 재작성(History Rewrite) 관련 장

실무 체크리스트

Force Push 사용 전 확인사항

  • 혼자 쓰는 브랜치인가?
  • 동료가 이 브랜치에서 작업 중이 아닌가?
  • --force-with-lease를 사용하는가?
  • main/master 브랜치가 아닌가?

민감한 파일 커밋 시 대응

  • 즉시 git rm으로 삭제하고 커밋
  • BFG Repo-Cleaner로 히스토리에서 완전 삭제
  • 관련된 모든 키/토큰 재발급
  • 팀원들에게 알리고 재클론 안내

결론

“실수는 누구나 한다. 하지만 흔적 없이 지우는 건 실력이다.”

Git은 강력한 타임머신 기능을 제공합니다. git commit --amend, git reset, git push -f, BFG Repo-Cleaner를 이해한다면, 어떤 실수를 하더라도 당황하지 않고 우아하게 대처할 수 있을 것입니다.

다만 협업 환경에서는 항상 신중해야 합니다. --force-with-lease를 습관화하고, 히스토리를 변경하기 전에는 팀원들과 소통하는 것이 가장 중요합니다.


궁금한 점이나 추가로 다뤘으면 하는 내용이 있다면 댓글로 남겨 주세요.

댓글남기기