git push를 실행했는데 끝나지 않았다. 진행률 표시는 모든 파일이 100% 올라간 것처럼 보이는데, 그 상태로 계속 pushing에 머물렀다. 에러 로그도 없었다. 실패했다고 말해 주지 않으니 어디부터 봐야 할지 알 수 없는 악질적인 문제였다.

push 자체가 막힌 건 아니었다

처음에는 인증 정보나 remote 설정을 의심했다. 확인하려고 수정사항을 원복한 뒤 아주 조금만 고쳐서 다시 push했더니 정상적으로 올라갔다. 작은 push가 되니 credentials나 remote 설정 문제는 아닌 것으로 봤다.

그다음은 파일이다. 올라갈 수 없는 내용의 파일이 섞여 있는지 살펴봤지만, 그 정도로 의심 가는 파일은 없었다. 결국 남은 차이는 push의 크기뿐이었다.

원인은 HTTP POST 버퍼 크기 제한

Stack Overflow의 같은 증상 질문에서 원인을 찾았다. HTTP로 push할 때 쓰는 POST 버퍼의 크기 제한 때문에 생기는 현상이었다.

Git은 HTTP(S) remote에 push할 때 데이터를 POST로 보내고, 이때 http.postBuffer 크기의 버퍼를 쓴다. Git 문서에 따르면 기본값은 1 MiB다. 보낼 데이터가 이보다 크면 Git은 한 번에 보내지 않고 Transfer-Encoding: chunked로 나눠 보낸다. 서버나 중간 프록시가 이 chunked 전송을 제대로 처리하지 못하면, 클라이언트 쪽에서는 데이터를 다 보낸 것처럼 보이는데 응답이 오지 않는다. 작은 수정이 문제없이 올라간 것도 버퍼 안에 들어갔기 때문이라고 보면 설명이 된다.

http.postBuffer를 늘려 해결

버퍼 크기를 늘렸다.

# POST 버퍼를 150 MiB(157286400 bytes)로 늘린다
git config --global http.postBuffer 157286400

이후 push가 정상적으로 완료됐다.

참고

Git 문서는 이 값을 올리는 것이 chunked 전송을 끄는 효과만 있다고 설명한다. 원격 서버나 프록시가 HTTP/1.0만 지원하거나 표준을 제대로 따르지 않을 때 쓰는 설정이지, 대부분의 push 문제를 푸는 일반적인 해결책은 아니라는 것이다. 작은 push에도 버퍼 전체가 할당되므로 메모리 사용량이 크게 늘 수 있다는 경고도 함께 있다.

그래서 --global로 모든 저장소에 거는 것보다, 문제가 생긴 저장소에서만 --global 없이 설정하는 편이 부작용이 적다. 설정이 필요 없어지면 git config --global --unset http.postBuffer로 되돌릴 수 있다.