📌 포스팅 읽기에 앞서...
옳지 않은 정보가 있을 수도 있습니다.
정보 수정은 언제나 환영입니다!
옳지 않은 정보가 있다면 댓글로 남겨주시면 감사하겠습니다 👍

처음으로 협업을 하게되면서 브랜치를 통한 작업을 진행해나갔다.
급하게 작업을 하며 그냥 develop 브랜치에서 바로 작업을 한 뒤 push를 하려 했을 때 위와 같은 에러 문구를 수많이 겪었다.
정확하게 모르며 그냥 해결하기 위해서
$ git pull --rebase origin develop
를 입력하여 원격 develop 브랜치의 커밋을 로컬 develop에 다 땡겨와 어찌저찌 해결 시켜 넘어갔다.
해당 문제는 협업을 할 때 가장 기본이 되는 문제라 생각하여 해커톤이 끝난 후 가장 먼저 자세히 살펴보았다.
진행 전 해당 포스팅에 사용될 용어와 환경들을 간략하게 정리해보자.
📌 용어 정리
원격 리포지토리 : 깃허브(웹 페이지) 상에서 생성한 리포지토리
로컬 리포지토리 : 원격 리포지토리와 연결된 내 PC 상의 리포지토리
현재 원격 리포지토리의 main 브랜치 내에는
README.md파일만 존재한다.
현재 브랜치는 2개(main, develop)이 있으며, develop의 내용은 main과 동일.
develop에서 feature/* 브랜치들을 만들어 새로운 기능(파일)을 추가한다.
$ git clone {원격 리포지토리}를 통해 로컬 리포지토리를 생성하였다.
작업환경은 VS code이고,
git graph익스텐션을 사용하여 브랜치의 분기를 살펴본다.
📌 참고 !
git graph를 통해 보았을 때origin/HEAD,origin/develop이 보인다.
이는 로컬 리포지토리와 연결된 원격 리포지토리의 브랜치들을 나타내는 것이다.
origin/HEAD=origin/main이다.
이는 디폴트 브랜치가origin/HEAD가 되는 것으로 해당 내용은 어렵지 않으니 따로 검색을 해보기 바란다.
디폴트 브랜치 변경은 원격 리포지토리 깃허브 페이지에서 가능하다.
앞서 본 에러는 develop 브랜치에 git push를 했을 때 일어나는 에러였다.

에러 문구에서 확인할 수 있듯이 해당 에러는 로컬에서 작업을 끝난 후 develop 브랜치로 push를 보낼 때 원격의 develop 브랜치와 커밋이 다르기 때문이다.
즉, 내가 develop에서 작업을 하던 도중 다른 누군가 develop에 자기 작업물을 merge한 것이다.

다음과 같은 상황이 있다고 가정하자.
다른 팀원이 작업을 마친 후, 로컬 → 원격 develop 브랜치로 push 시켰다.
그렇다면, 현재 원격 리포지토리의 develop 브랜치는 팀원의 커밋이 추가로 붙어있을 것이다.
이제, 내가 작업을 마치고 로컬 → 원격 develop 브랜치로 push를 시켰을 때 커밋 내역이 다르기 때문에 "내가 작업을 커밋이 어디 들어가야 하지?"라는 부분때문에 위와 같은 오류가 발생하는 것이다.
📌 "내가
feature/A, 팀원이feature/B브랜치를 따로 파서 push한 뒤 PR을 통해develop에 합치는 건 왜 문제가 안 되는거지?다음과 같은 생각이 들 수도 있지만, 이건 우리가 push를 한 건
- 로컬 리포지토리의
feature/*→ 원격 리포지토리의feature/*실제로 브랜치간 병합이 일어난 것은
- 원격 리포지토리의
feature/*→ 원격 리포지토리의develop이므로 로컬의
develop과 원격의develop이 달라도 전혀 문제가 되지 않는 것이다.
단지, 작업이 끝난 후 로컬에서develop브랜치로 checkout 하여$ git pull origin develop만 잘 해주면 된다.
위에 내용을 이어 이야기하자면, 에러 문구에 해결책으로 나온 것이 git pull을 통해 커밋 내용을 맞추라는 이야기이다.
즉, $ git pull origin develop을 통해
원격 develop 커밋 → 로컬 develop 커밋에 포함(동기화)시키라는 이야기이다.
git pull 전략에 대한 내용을 들어가기에 앞서 git pull에 대해 잠깐 알아보고자 한다.
왜냐하면, 'git pull rebase, ff'에 대해 검색해보았을 때 git merge ...를 통한 설명이 많았기 때문이다.
에러 설정은 git pull을 하라는 이야기인데, rebase, ff에 대해 검색하니 git merge로 이야기가 나와서 약긴 불편했었다.
그냥 내가 깃에 대해 잘 모르는 거였지만
처음에는 그저 git pull이 "원격 리포지토리 내용을 가져오는 거구나" 정도로 알고 있었다. 틀린 이야기는 아니다.
git pull은 git fetch와 git merge가 합쳐진 명령어이다.
git fetch는 원격 리포지토리에 있던 내용을 내 로컬 리포지토리에 가져오고,
git merge는 체크아웃된 브랜치를 병합시킨다.
"git pull이 원격에 있던 내용을 내 로컬로 가져오는 거 아니였음?"
이 헷갈리는 개념은 예제 사진을 보면 이해하기가 쉽다.

앞서, vscode에서 $ git branch -al 명령어를 통해 로컬과 원격의 리포지토리를 확인하였다.
❗️ 이때, 빨간 색으로 보이는 remote/origin/*는 원격 리포지토리를 나타내긴 하지만, 원격에 있는 브랜치와 동일하지는 않다.
git fetch 명령어를 입력하면 원격에 있는 브랜치의 내용을 가져와 remote/origin/{브랜치명}에 원격에 있는 브랜치 내용을 저장한다.
로컬에
origin/{브랜치명}이 없으면 checkout을 통해origin/{브랜치명}을 만든 뒤에 거기에 원격 브랜치 내용을 저장한다.
이후 git merge를 통해 로컬에서 origin/{브랜치명} → {브랜치명}으로 병합하는 것이다.
자 이제 git pull 전략에 대해 알아보자.
우선 develop 브랜치를 로컬에서 생성해준다.
feature/A 브랜치를 만들어준다.
현재 초기 시점(커밋)에feature/A,develop,main,origin/HEAD가 위치한 것을 확인할 수 있다.
feature/A에서 A.txt를 만든다.
현재 커밋을 보내지 않았기 떄문에 uncommitted changes라 뜬다.
A.txt를 커밋(make A)까지 해주니
feature/A가 초기 위치의 브랜치들의 모임에서 빠져나온 것을 확인할 수 있다.
A 파일에 수정을 하여 새로운 커밋을 생성한다.
똑같이 uncommitted changes라 뜬다. 이제 수정했다는 커밋을 보내보자.
modify A 커밋 시점으로 feature/A가 위치한 것을 확인할 수 있다.
📌 이제 feature/A를 develop에 합쳐보자
develop 브랜치로 바꾸자 현재 위치가 맨 아래로 돌아간 것을 확인할 수 있다.
아무 옵션도 주지않고 feature/A를 develop에 병합을 하자 develop 태그가 featureA의 분기로 간 것을 알 수 있다.
이는 ff 방식으로, 병합하려는 브랜치가 ff 관계이면 알아서 ff 방식 병합한다.
feature/B 생성 후 파일을 하나 만들고 여러 커밋이 발생했다.
이 상태에서
develop으로 돌아오면 B.txt 파일이 삭제된 것을 알 수 있다.
B.txt는feature/B에서 생성한 파일이니 없어지는 게 당연하다.
📌 이 때, develop에 새로운 파일을 하나 만들어 보자.
develop에서 새로운 파일 Develop.txt를 만들면 git graph 분기가 생긴 것을 확인할 수 있다.
feature/A를 테스트했을 때와 달리develop과feature/B의 커밋이 다르기 때문이다.
develop의 변경사항을 커밋을 하면develop태그의 위치가 바뀐 것을 알 수 있다.
📌 이제 feature/B → develop로 머지를 시켜보며 각 merge 전략을 테스트해보겠습니다
📗 merge commit
merge commit은 병합 시 브랜치 내 모든 커밋을 베이스 브랜치로 합친 뒤 병합 시점의 커밋 메시지도 추가로 남기는 전략이다.
$ git merge feature/B를 하자 다음과 같은 문구가 떴다.
병합하려는
develop과feature/B의 커밋의 차이가 발생하였기 때문에$ git merge feature/B명령어 실행 시 merge commit으로 병합이 진행된다.따라서, 터미널에
feature/B가develop에 병합 되는 시점의 커밋을 남겨야한다는 문구가 나온다.
i를 눌러 vim 입력 모드로 들어가준 뒤 커밋 메시지를 작성해줍니다.
커밋 메시지를 작성 후 :wq를 입력하여 커밋 메시지 작성을 완료하면 다음과 같이 병합된 시점에 커밋이 하나 생기며 병합된 것을 확인할 수 있다.
다른 전략을 테스트해보기 위해
$ git reset HEAD^로 최근 커밋을 지우겠다.
그럼 다음과 같이 병합 하기전 상태로 돌아온 것을 확인할 수 있다.
참고로 B.txt 파일이 untracked 상태로develop에 남아있다면 discrad 해버리면 된다.
📗 squash and merge
squash and merge는 병합 시 브랜치 내 모든 커밋을 하나의 커밋 메시지로 통합시켜 베이스 브랜치에 해당 커밋을 반영하여 병합하는 전략이다.
$ git merge --squash feature/B를 통해 squash merge를 하니 다음과 같은 문구와 커밋 메시지가 나온다.과연 뭘까?
squash and merge는 브랜치의 모든 커밋들을 하나의 커밋으로 통일시켜 베이스 브랜치에 합치기 때문에 그 커밋의 메시지를 정해줘야하는 것이다.
커밋 메시지는 'squash and merge - feature/B to develop' 으로 하겠다.
다음과 같이
feature/B브랜치의 커밋은develop의 그래프에 합쳐지지 않고,develop에 커밋 하나만 남겨져있는 것을 확인할 수 있다.
$ git log를 통해 살펴보면develop에선 A에 대한 커밋은 확인 가능하지만, B에 대한 커밋은 나타나지 않으며, 병합 시 남겼던 커밋 'squash and merge - feature/B to develop'만 확인이 가능하다.
다시 리셋을 통해 병합하기 이전 시점으로 돌아간다.
앞서 설명하였듯이 B.txt 파일이 남아있다면 discrad해버리면 된다.
📗 rebase
rebase은 병합 시 브랜치 내 모든 커밋을 베이스 브랜치의 끝에 추가하는 병합 전략이다.
삭제한 뒤 원래 상태로 돌아왔다.
이제 rebase 전략을 확인해보자.
$ git rebase feature/B입력 후 git graph를 보면 분기가 사라진 것을 확인할 수 있다.당연하다.
rebase는 해당 브랜치의 커밋을 베이스 브랜치로 모두 옮긴다는 것이기 때문이다.
따라서,feature/B는 modifyB 커밋에 머물러 있고,develop은 modifyB를 자신의 커밋에 포함 시키고 있습니다.📌 참고
rebase를 하면feature/B의 커밋이develop커밋 맨 뒤에 붙어야한다.
그러나, 이 예제에선 'make Develop'이 맨 뒤에 붙어있는 것을 나중에 확인했습니다..그 이유는
feature/B가 만들어지고 난 다음develop에서 Develop.txt가 생성되었기 때문에 당연히feature/B의 커밋 내에선 Develop.txt 관련된 커밋이 없지만,develop은 'make develop' 커밋에 있으므로 맨 마지막에는 make develop 커밋이 자리잡게 되었다.해당 사항을 확인하여,
develop에 추가적인 커밋없이feature/D를 만들고(ff 관계) rebase를 테스트해보았다.
정상적으로feature/D의 커밋이 맨 마지막에 이어 붙고,develop과feature/D가 맨 끝 커밋을 가르키고 있음을 알 수 있다.
📗 fast forward
ff은 병합하려는 두 브랜치가 ff 관계일 때, 베이스 브랜치의 위치를 병합하려는 브랜치의 최종 커밋 위치로 옮기는 것이다.
develop에서feature/C를 만들고 'make C', 'modify C' 커밋을 남겼다.
분명feature/C를 만들었는데 한 줄의 그래프로 나와서 당황할 수 있겠지만 구부러진 그래프가 원래 한 줄 모양이기 때문에 이렇게 나오는 것이다.
다르게 생각하면 한 줄이어야 fast forward 관계라고 할 수 있다.
fast forward는 베이스 브랜치 위치를 타겟 브랜치로 맨 마지막 커밋 위치로 옮기는 것이다.
당연히 한 줄에 있기 때문에 커밋 순서 등이 헷갈릴 일이 없고, git lens에선 태그의 위치만 바꿔주게 되는 것이다..!
태그 위치만 옮기는 것이기에 커밋 메시지를 따로 남길 필요가 없다 !
이것이 ff 관계 여부에 따른 merge commit과 fast forward의 차이이다.
병합을 할 두 브랜치가 ff 상태이면
$ git merge시 자동으로 ff로 머지가 된다.그러나 병합 시점의 병합이 되었다는 커밋을 남기고 싶다면?
$ git merge --no-ff feature/*
자 그러면 이제 초반에 일어났던 에러를 해결해보자!

$ git push origin develop 시 에러가 난 상태는 위와 같은 상태이다.
에러를 해결하기 위해 $ git pull --rebase origin develop을 한다면?


$ git pull --rebase origin develop을 통해 원격 브랜치와 로컬 브랜치를 동기화 시켜놓았기 때문에 이제 push에 문제가 없다!