초보의 첫 PR 사이클: squash merge 전후로 막힌 5가지

squash merge

DART 클라이언트 브랜치를 push하고, PR을 올리고, squash merge한 뒤 브랜치를 정리하기까지. PR 한 사이클을 처음 돌면서 다섯 번 막혔다.

돌아보면 원인은 대부분 같았다. 로컬 브랜치와 원격 브랜치가 별개라는 것, 그리고 squash가 커밋을 새로 만든다는 것을 몰랐다. 이번 글은 막혔던 5가지 작업 순서대로 정리한 빌드로그다.

1. 첫 push가 거부됐다: upstream

내가 겪은 것: push가 안 됐던 건 원격 브랜치가 연결돼 있지 않아서였다. 로컬에 브랜치를 만들었다고 원격에도 생기는 게 아니었다. 로컬 브랜치는 원격의 어느 브랜치와 연결돼 있는지 스스로 모른다. 그 연결 정보를 upstream(추적 브랜치)이라고 한다.

새 브랜치에서 처음 git push를 하자 이런 에러가 났다.

fatal: The current branch feature/dart-client has no upstream branch.

git push는 “연결된 원격 브랜치로 보내라”는 뜻인데, 연결된 곳이 없으니 어디로 보낼지 모르는 것이다.

# 처음 한 번: 보내면서 연결까지 설정 (-u = --set-upstream)
git push -u origin feature/dart-client

# 이후엔 그냥
git push

# 연결 상태 확인
git branch -vv
#   feature/dart-client  a1b2c3d [origin/feature/dart-client] ...
#   old-branch           d4e5f6a [origin/old-branch: gone] ...  ← 원격이 지워짐

매번 -u를 붙이기 귀찮다면 git config --global push.autoSetupRemote true 로 자동화할 수 있다. 다만 개념을 익힌 뒤에 켜는 게 낫다고 생각했다.

같이 놓친 것: push한 뒤 README를 고쳤는데, 그 커밋을 push하지 않은 채 merge 직전까지 갔다. 이후로는 push 전에 git status로 “ahead of origin”이 남아 있는지 확인한다.

2. Claude가 PR 공동 작성자로 떴다: Co-Authored-By

PR을 열었더니 작성자 옆에 Claude가 공동 작성자로 떠 있었다. 원인은 커밋 메시지 끝에 붙은 한 줄이었다.

feat: DART 클라이언트에 재시도 로직 추가

Co-Authored-By: Claude <noreply@anthropic.com>

내가 겪은 것: Claude가 공동 작성자로 떠서 그 줄을 지우고 싶었다. 어디서 오는지 알고 나니, squash merge 메시지를 편집해서 지울 수 있다는 걸 알게 됐다.

커밋 메시지 마지막의 Co-Authored-By: 줄을 trailer라고 한다. GitHub은 이 trailer를 읽어서 공동 작성자로 표시한다. AI 코딩 도구가 커밋을 만들 때 이 줄을 자동으로 붙이기도 한다.

해결 방법

  • squash merge할 때: GitHub이 보여주는 최종 커밋 메시지 편집 창에서 해당 줄을 지운다. squash는 커밋을 새로 만드니 이 메시지가 master에 남는 유일한 기록이 된다.
  • 앞으로: claude 도구 설정에서 커밋 자동 서명(co-author 표시)을 끈다.
~/.claude/settings.json에서 "includeCoAuthoredBy": false 구문 추가

3. squash merge 후 git branch -d가 거부됐다

squash merge는 내 커밋들을 master에 그대로 옮기는 게 아니라, 내용을 합친 새 커밋 하나를 만든다. 그래서 Git 입장에서는 내 원래 커밋들이 master에 “없는” 것처럼 보인다.

내 브랜치:  A ─ B ─ C
                                       ↓ squash merge
master:       ... ─ M ─ S   (S = A+B+C 내용을 합친 새 커밋)

A, B, C와 S는 내용이 같아도 서로 다른 커밋이다. 그래서 브랜치를 지우려고 하면 Git이 막는다.

git branch -d feature/dart-client
# error: The branch 'feature/dart-client' is not fully merged.

-d는 “다른 곳에 합쳐진 브랜치만 지운다”는 안전한 삭제다. squash 후에는 이 검사를 통과하지 못한다. GitHub에서 PR이 merge됐는지 직접 확인한 뒤 강제 삭제를 쓴다.

git branch -D feature/dart-client   # 대문자 D = 강제 삭제

# 원격에서 지워진 브랜치 정리
git fetch --prune
git branch -vv                      # [origin/...: gone] 표시된 게 지워도 되는 후보

내가 겪은 것: squash를 하면 master 히스토리에는 커밋 하나만 남고, 세부 커밋은 브랜치를 지우는 순간 사라진다. 실무에서는 세부 히스토리를 남겨야 하는지, 없어도 상관없는지 판단한 뒤에 squash를 쓰기로 했다.

4. squash된 브랜치 위에서 갈라진 브랜치 옮기기: rebase --onto

3장의 문제는 브랜치를 지울 때만 생기지 않는다. squash되기 전 브랜치에서 다음 작업 브랜치를 갈라놓았다면, 그 브랜치는 이미 master에 들어간 A, B, C를 여전히 들고 있다.

master:   ... ─ M ─ S              (S = A+B+C)
                \
                 A ─ B ─ C ─ D ─ E   ← 다음 작업 브랜치 (내 커밋은 D, E)

이대로 PR을 올리면 A, B, C가 다시 들어가고, S와 내용이 겹쳐 충돌이 나기 쉽다. 필요한 건 D, E만 골라 최신 master 위로 옮기는 것이다.

git fetch
git rebase --onto origin/master C   # C = 옛 부모 커밋 (내 작업 직전)

읽는 법은 “C 다음부터 현재 브랜치 끝까지의 커밋을, origin/master 위에 다시 쌓아라”다.

내 브랜치: ... ─ M ─ S ─ D' ─ E'   (D', E' = 새 해시로 다시 만들어진 내 커밋)

진행 중에 알아두면 덜 놀라는 것들

  • git branch 에 (no branch, rebasing ...) 가 보인다. detached HEAD 상태지만 rebase 중에는 정상이다.
  • 충돌이 나면: 파일을 고치고 → git add → git rebase --continue. 새 커밋이나 merge는 필요 없다.
  • 꼬였다 싶으면 git rebase --abort 로 rebase 시작 전으로 돌아간다.

내가 겪은 것: 이 부분은 아직 자주 발생하지 않아서 익숙하지 않다.

5. git fetch 했는데 브랜치가 안 보인다

여러 PC로 작업하다보니 GitHub에 feature/loader 브랜치가 생겨서 git fetch 를 했는데, git branch 에 나오지 않았다. 원인은 1장과 같다. 로컬 브랜치와 원격 브랜치는 별개다.

fetch 는 원격 내용을 origin/feature/loader 로만 받아온다. 이것을 원격 추적 브랜치라고 하는데, 원격의 상태를 기록해둔 읽기 전용 책갈피다. 내가 작업할 로컬 브랜치는 만들지 않는다.

git branch        # 로컬 브랜치만 → feature/loader 없음
git branch -a     # 로컬 + remotes/origin/feature/loader 까지 보임
git branch -r     # 원격 추적 브랜치만

해결은 git switch 다.

git switch feature/loader
# 같은 이름의 원격 브랜치가 하나뿐이면,
# 로컬 브랜치를 만들고 upstream까지 자동으로 연결해준다

# 예전 방식
git checkout -b feature/loader origin/feature/loader
명령하는 일로컬 브랜치를 만드나
git fetch원격 내용을 origin/... 에 받아오기만아니오
git pullfetch + 현재 브랜치에 병합아니오
git switch <이름>원격 추적 브랜치를 보고 로컬 브랜치 생성 + 연결예

주의할 점: git pull 은 내가 지금 있는 브랜치에 병합한다. master에서 git pull 을 해도 feature/loader 로컬 브랜치는 생기지 않는다.

내가 겪은 것: 원격에는 브랜치가 있고 로컬에는 없는데, git fetch 만으로는 로컬에 생기지 않았다. fetch는 받아오기만 하고, 로컬에서 작업하려면 git switch 로 로컬 브랜치를 만들어야 한다.

마무리: 상황별 명령어 요약

다섯 번 막힌 이유는 결국 두 가지였다. 로컬과 원격은 따로 있고, squash는 커밋을 새로 만든다.

상황명령
새 브랜치 첫 pushgit push -u origin <브랜치>
연결 상태 확인git branch -vv
push 전 빠진 커밋 확인git status
PR에 원치 않는 공동 작성자squash merge 메시지에서 Co-Authored-By: 줄 삭제
squash merge한 브랜치 삭제merge 확인 후 git branch -D <브랜치>
원격에서 지워진 브랜치 정리git fetch --prune
squash된 브랜치 위의 작업 옮기기git rebase --onto origin/master <옛 부모>
rebase 후 다시 pushgit push --force-with-lease
원격에만 있는 브랜치로 이동git switch <브랜치>
원격 브랜치 목록 보기git branch -a