Day 04 - Git/Github 브랜치 및 실습, 브랜치 전략, Pull Request/Merge/Conflict,

이유승·2024년 11월 3일
0

* 프로그래머스, 타입스크립트로 함께하는 웹 풀 사이클 개발(React, Node.js) 5기 강의 수강 내용을 정리하는 포스팅.

* 원활한 내용 이해를 위해 수업에서 제시된 자료 이외에, 개인적으로 조사한 자료 등을 덧붙이고 있음.



1. Git 브랜치

main?? master??

자료출처

  • 간혹 Github 관련 자료를 찾다보면, Git을 최초로 만들었을 때 자동으로 생성되는 브랜치 이름이 'Main'과 'Master'의 2가지로 혼용되고 있는 것을 볼 수 있다.

  • 따라서 Github 사용을 위해서 구글링 한 자료 등에서 제시된 명령어를 그대로 복붙했다가 브랜치 이름이 달라서 문제를 겪고는 하는데..

  • 도대체 왜 2가지 이름이 혼용되고 있는 것인가? 그 이유는 Git, Github 사이에 기본으로 사용되는 브랜치 이름이 일치하지 않기 때문이다. (물론 지금은 해결된 문제. 인터넷 상의 자료는 과거의 것이 많기 때문에 구글링해서 찾은 자료에 의존하면 여전히 문제가 될 수 있다.)



브랜치 이름 명명 규칙

  • 목적에 따라서 브랜치를 복수로 사용하는 것은 Github 사용의 상식과 같은 일이다.

  • 그렇다면 효율적인 협업을 위해 브랜치의 이름을 짓는 규칙이라는 게 존재하는 것도 당연한 일. 대략 아래와 같은 규칙이 존재한다.
    (실제 실무에서는 특정 회사, 부서, 팀, 파트 등의 단위에 따라 각자 다른 규칙을 사용하는 경우도 있으니 대략 참조만 할 것.)

메인 브랜치 (main/master):

  • 보통 main 또는 master로 명명하며, 프로젝트의 최종 코드라인을 의미.
    최종 배포 코드나 안정성이 검증된 코드만 포함.

기능 브랜치 (feature branches):

  • 새로운 기능을 개발할 때 사용.
    feature/ 접두사를 붙이고 기능 이름을 명시.

  • 예: feature/user-login, feature/add-shopping-cart

버그 수정 브랜치 (bugfix/fix branches):

  • 기존 기능에 대한 버그를 수정할 때 사용.
    bugfix/ 또는 fix/ 접두사를 붙임.

  • 예: bugfix/fix-login-issue, fix/cart-update-error

단계 브랜치 (staging branch):

  • 배포 전 최종 테스트를 위한 브랜치.
    보통 staging 또는 pre-release와 같은 이름을 사용.

  • 예: pre-release-1.1

핫픽스 브랜치 (hotfix branches):

  • 긴급하게 배포해야 하는 수정이 있을 때 사용.
    hotfix/ 접두사를 붙임.

  • 예: hotfix/fix-crash-on-launch, hotfix/security-patch

실험 브랜치 (experiment branches):

  • 새로운 아이디어나 실험적인 기능을 테스트할 때 사용.
    experiment/ 접두사를 붙임.

  • 예: experiment/new-ui-test, experiment/ai-recommendations



2. Git 브랜치 실습

브랜치 관련 명령어들

1. 브랜치 목록 확인

git branch

  • 현재 저장소의 브랜치 목록을 확인. 현재 선택된 브랜치는 * 표시로 표시.



2. 새 브랜치 생성

git branch 'branch-name'

  • 새로운 브랜치를 생성.
    예: git branch feature/login



3. 브랜치 생성과 동시에 이동

git checkout -b 'branch-name'

  • 새로운 브랜치를 생성하고, 생성한 브랜치로 바로 이동.
    예: git checkout -b feature/login



4. 브랜치 전환

git checkout 'branch-name'

  • 특정 브랜치로 이동.
    예: git checkout main



5. 브랜치 병합

git merge 'branch-name'

  • 현재 브랜치에 지정한 브랜치를 병합.
    예: git merge feature/login



6. 브랜치 삭제 (로컬)

git branch -d 'branch-name'

  • 작업을 완료한 브랜치를 삭제.
    예: git branch -d feature/login
  • 만약 브랜치 병합이 완료되지 않아 강제로 삭제하려면 -D 옵션을 사용할 수 있음.
    예: git branch -D feature/login



7. 브랜치 삭제 (원격)

git push origin --delete 'branch-name'

  • 원격 저장소에서 브랜치를 삭제.
    예: git push origin --delete feature/login



8. 원격 브랜치 가져오기 (fetch)

git fetch origin 'branch-name'

  • 원격 저장소의 특정 브랜치를 로컬로 가져오기.
    예: git fetch origin feature/login



9. 원격 브랜치를 로컬로 체크아웃

git checkout -t origin/'branch-name'

  • 원격 브랜치를 로컬로 체크아웃하여 로컬 브랜치를 생성.
    예: git checkout -t origin/feature/login



10. 브랜치 이름 변경

git branch -m 'new-branch-name'

  • 현재 브랜치 이름을 변경.
    예: git branch -m new-feature



11. 브랜치 이름 변경 (특정 브랜치)

git branch -m 'old-branch-name' 'new-branch-name'

  • 특정 브랜치 이름을 변경.
    예: git branch -m feature/login feature/user-login



12. 현재 브랜치 확인

git branch --show-current

  • 현재 위치한 브랜치 이름을 표시.



13. 원격 브랜치 목록 보기

git branch -r

  • 원격 저장소의 브랜치 목록을 확인.



14. 로컬 및 원격 브랜치 목록 보기

git branch -a

  • 로컬과 원격 저장소의 모든 브랜치 목록을 확인.



15. 브랜치 간 차이점 확인

git diff 'branch-A' 'branch-B'

  • 두 브랜치 간의 변경 사항을 비교.
    예: git diff main feature/login



커밋이 이루어져야 그때부터 브랜치가 된다

  • 특정 브랜치에서 작업하여 수정된 파일을, 다른 브랜치로 이동해보면 수정된 파일이 그대로 다른 브랜치까지 이동된다.

  • 이렇게되면 브랜치를 복수로 사용하는 이유가 없는데, 어째서 이런 일이 생기는가?

  • 수정된 작업을 커밋해두지 않으면, 수정된 내용은 브랜치를 이동해도 그대로 유지되기 때문이다.

  • 커밋만 해두면, 같은 프로젝트 파일이라고 해도 브랜치 상태에 따라서 각자 다른 버전의 파일을 제어하게 된다.

  • 따라서, 작업을 수행할 때에는 자신이 어느 브랜치에 존재하는지 반드시 확인하고 해야한다. 잘못된 브랜치에서 작업을 진행하고 커밋까지 진행하면 이를 되돌리는게 대단히 힘들기 때문. VSCode 기준으로 좌측 하단에 사용자가 어느 브랜치에 접속 중인지 보여주고 있으니 참조.



3. Github 브랜치

  • GUI 방식의 Github에서는 더 쉽게 사용할 수 있는 브랜치 관련 기능들을 제공해주고 있다.

  • 로컬에서 CLI로 브랜치를 추가했다고 해도, 자동으로 Github에 브랜치가 생성되어 업로드 되진 않는다. 로컬에서 만든건 로컬에서만 존재한다. 로컬 생성 브랜치는 따로 조치를 취해야 원격저장소에서도 적용된다.



로컬 브랜치를 원격저장소에 업로드

git push origin 'branch-name'

git push -u origin 'branch-name'

  • 로컬에서 생성한 브랜치에서 작업을 수행하고, 커밋까지 수행한 뒤. 위 명령어로 변경사항을 푸시해주면된다.

  • 로컬 브랜치를 원격 저장소에 업로드하는 명령어. -u 옵션은 원격 브랜치와 로컬 브랜치 간에 추적 관계를 설정해 주는 것으로, 이후부터는 git push 명령어만으로도 쉽게 푸시할 수 있게 된다.

  • Github에서 친절하게 어느 브랜치가 새롭게 푸시되었다고도 알려준다.



브랜치 전략

  • 브랜치 전략. 소프트웨어 개발 과정에서 Git 브랜치를 효율적으로 관리하고 협업하기 위해 사용하는 규칙과 방법.



Fast-Forward

  • Fast-Forward 병합은 병합하려는 브랜치가 단순히 앞쪽으로 이동하는 방식.

  • Git은 기본적으로 히스토리가 분기되지 않고 직선으로 이어진 경우, 기존 커밋 히스토리를 유지하면서 HEAD를 앞으로 이동.

  • 이 과정에서 새로운 병합 커밋이 생성되지 않으며, 브랜치의 포인터가 빠르게 이동하는 것처럼 보인다고 해서 Fast-Forward라는 이름이 붙었다.

  • C 기능까지 수행하고, 새 브랜치를 만들어서 D/E를 수행한 뒤, 두 브랜치를 병합시키는 것.

  • 별도의 병합 커밋을 생성하지 않기 때문에 커밋 히스토리가 깔끔하게 유지되고, 히스토리가 깔끔하게 이어지므로, 로그를 봤을 때 병합 작업이 일어난 부분을 명확하게 구분하지 않아도 된다는 장점이 있다.

  • 경우에 따라 히스토리의 명확성을 위해 병합 커밋을 남기고 싶은 경우, --no-ff 옵션을 사용하여 No Fast-Forward 병합을 강제로 수행할 수 있다.

git merge --no-ff 'branch-name'



3-Way

  • Git에서 브랜치를 병합할 때, 두 개 이상의 브랜치가 다른 경로로 발전하여 Fast-Forward 병합이 불가능할 때 사용하는 병합 방식.

  • 두 브랜치와 공통 조상(공통 부모 커밋)을 참고하여 세 가지 커밋을 기반으로 병합하는 방식이라 3-Way 병합이라 명명됨.

  • 두 브랜치가 분기된 시점의 공통 조상 커밋, 현재 병합을 수행하려는 브랜치의 최신 커밋, 병합하려는 대상 브랜치의 최신 커밋의 3개 커밋을 참고하여 병합을 수행한다.

  • 부모 커밋의 정보가 포함하는 새로운 병합 커밋을 생성, 각 브랜치가 다른 변경 사항을 가지고 발전했을 때도 병합을 처리할 수 있음.

  • 두 브랜치가 분기된 이후 각각 독립적으로 여러 커밋을 거쳐 다른 변경 사항을 포함하게 된 경우, 기능 개발이 완료된 기능 브랜치를 메인 브랜치로 병합하거나, 여러 사람이 독립적으로 작업한 브랜치를 병합할 때 자주 사용함.

  • 다만, 만약 같은 파일의 동일한 부분이 두 브랜치에서 각각 다르게 수정되었다면, Git이 자동으로 병합할 수 없어 병합 충돌(merge conflict)이 발생할 수 있고 이는 사용자가 스스로 해결해야한다.

  • O는 A와 B가 분기된 공통 조상 커밋
  • A와 B는 각자의 변경 사항이 있는 두 브랜치
  • M은 A와 B의 변경 사항이 모두 포함된 병합 커밋.



  • 얼핏 이해하기가 어려운 내용인데.. 사실 이게 가장 흔하게 사용되는 브랜치 전략 중 하나이다. 서로 다른 기능 구현을 위해 브랜치가 나뉘어지고, 작업이 길어질 수록 두 브랜치에서 다루는 파일의 내용들이 점차 상이해진다.

  • 그런데 테스트든 배포든 결국에는 완전히 다른 상태인 브랜치를 하나로 합쳐주어야 한다. 3-way라고 말을 어렵게 표현해서 그렇지, 결국에는 브랜치를 나누어서 작업하다가 나중에 하나로 합친다는 소리.



Git Flow

  • 가장 널리 알려진 브랜치 전략 중 하나로, main(또는 master)과 develop 브랜치를 기반으로 여러 기능과 수정 작업을 관리.

주요 브랜치
main/master: 최종 배포 코드를 유지하는 안정적인 브랜치.
develop: 개발 중인 코드가 모이는 브랜치.

하위 브랜치
feature: 기능 개발을 위한 브랜치.
release: 배포 준비를 위해 테스트하는 브랜치.
hotfix: 긴급한 수정이 필요할 때 사용하는 브랜치.

  • 특징: 여러 단계의 브랜치 구조를 통해 큰 프로젝트에서도 안정적인 배포와 개발이 가능. 다만 구조가 복잡해 작은 프로젝트에서는 비효율적일 수 있음.



GitHub Flow

  • GitHub에서 사용하는 간단한 브랜치 전략, main과 기능 브랜치만을 사용.

주요 브랜치
main: 배포 가능한 최신 코드가 유지되는 브랜치.

하위 브랜치
feature: 기능 개발을 위한 브랜치로, 작업이 완료되면 main에 병합합니다.

  • 특징: 구조가 간단하고 빠르게 작업을 완료해 배포하기에 적합, Pull Request(PR) 기능을 통해 코드 리뷰와 협업이 용이. 주로 작은 프로젝트나 자주 배포가 필요한 프로젝트에 적합.



GitLab Flow

  • GitLab에서 제안한 브랜치 전략으로, main과 production, staging 브랜치를 사용하여 배포 환경을 명확히 구분하는 것이 특징.

주요 브랜치
main: 개발이 진행되는 기본 브랜치.
staging: 배포 전 테스트를 진행하는 브랜치.
production: 실제 서비스가 운영되는 배포 브랜치.

  • 특징: 배포 환경을 명확히 구분하여 코드 안정성을 높이는데 유리, CI/CD(지속적 통합 및 배포) 파이프라인과 결합하여 자동 배포와 테스트를 효율적으로 설정할 수 있음.



Trunk-Based Development

  • main(또는 trunk) 브랜치를 중심으로 작업하며, 새로운 기능이나 수정 사항은 짧은 주기로 main 브랜치에 바로 병합하는 방식.

주요 브랜치
main: 모든 개발 작업이 직접 병합되는 기본 브랜치.

하위 브랜치
기능이나 버그 수정 시 임시 브랜치를 만들지만, 작업이 완료되면 빠르게 main에 병합하고 삭제합니다.

  • 특징: 빠르게 작업을 병합해 배포할 수 있어 CI/CD를 적극 활용하는 프로젝트에 적합. 병합 주기가 짧아야 하므로 협업 간 갈등을 줄이고 빠른 배포가 가능. 다만, 병합 속도가 빠르기 때문에 각 작업에서 테스트와 코드 품질에 주의해야 한다.



4. Github 브랜치 실습

브랜치 병합

  • 브랜치 병합은 각자 독립적으로 작업한 브랜치를 하나로 합치는 작업, 여러 개발자가 각자의 브랜치에서 기능을 개발하거나 버그를 수정한 뒤, 이 작업을 메인 브랜치로 병합하여 하나의 통합된 코드로 만들게 된다.

  • 브랜치 전략이라는 것은 목적을 위해 복수의 브랜치를 나누어 사용하는 것이다. 그런데 나누어진 브랜치는 언젠가는 반드시 하나의 브랜치로 다시 합쳐져야한다.

  • 브랜치 생성, 작업 수행 및 완료/커밋, PR 생성, 코드 리뷰 및 피드백, 피드백 반영, 병합 승인 및 완료의 순서로 진행된다.



Pull Request (PR)

  • Pull Request. GitHub와 같은 원격 저장소에서 브랜치를 병합하기 전에, 코드 리뷰와 논의를 위한 요청을 보내는 것. 팀 협업에서 중요한 역할을 하며, 코드 품질을 높이고 버그를 사전에 예방하는 데 도움이 된다.

  • PR을 통해 다른 사람의 코드를 검토하고 피드백을 주고 받아 코드의 품질을 높이고 오류를 방지할 수 있다.

  • 특정 기능이나 버그 수정에 대해 팀원들과 논의하여 팀이 같은 방향으로 나아가도록 조율할 수 있다.

  • PR에 자동화된 테스트나 빌드 파이프라인이 연결된 경우, 코드가 병합되기 전에 오류가 있는지 확인할 수 있다.



Merge (병합)

  • Pull Request가 승인된 후, 최종적으로 두 브랜치의 코드를 하나로 합치는 작업. GitHub에서는 PR을 통해 병합하는 기능을 제공하며, 병합 과정에서 Fast-Forward 병합이나 3-Way 병합 등의 방법이 사용된다.

  • GitHub에서 지원하는 병합 방식
    -> Merge Commit: 새로운 병합 커밋을 생성하여 두 브랜치를 병합. 커밋 히스토리가 남기 때문에, PR의 기록을 명확히 볼 수 있다.
    -> Squash and Merge: PR의 모든 커밋을 하나로 압축(squash)하여 병합. 브랜치 히스토리가 깔끔해지지만, 개별 커밋 기록이 사라진다.
    -> Rebase and Merge: 현재 브랜치의 커밋을 리베이스하여 main 브랜치에 병합. 히스토리가 직선으로 유지되지만, 커밋 순서가 변경될 수 있다.



Branch Protection Rule

  • GitHub에서 특정 브랜치의 안정성과 품질을 유지하기 위해 설정할 수 있는 규칙.

  • 주요 브랜치에서 실수로 잘못된 코드가 병합되거나 삭제되는 것을 방지하고, 협업 시 코드 품질을 관리하기 위해 사용한다.

  • 코드 리뷰 필수 설정, CI 검사 실행 여부, 변경사항 승인 후 병합, 강제 푸시 방지, 병합 전 모든 커밋 검토, 최신 상태로 유지, 서명된 커밋만 허용 등의 규칙을 설정할 수 있다.



Pull Request/Merge 실습

  • 로컬에서 새 브랜치에서 작업한 내용을 푸시하게 되면, Github UI에서는 위와 같은 알림과 어느 버튼을 출력해준다. 저 버튼을 클릭하면 PR/병합 과정을 진행할 수 있다.

  • 시간 경과 등의 이유로 알림 UI가 사라졌을 경우, Contribute 버튼을 클릭하여 PR 작업을 진행할 수 있다.

  • 어느 브랜치에서 Main 브랜치로 합쳐지는지, 충돌의 발생 여부는 어떠한지, 해당 PR 작업의 제목/설명, 각 파트의 내용 (작업자, 리뷰어, 작업의 성격, 프로젝트 및 마일스톤 내용) 등의 내용을 기재할 수 있다.

  • description 파트는 마크다운 문법을 지원한다. 이왕이면 가독성 있게 내용을 작성하는게 협업에도 도움이 된다.

  • 이후 코드 변경 내용을 확인하고, 리뷰어로부터 승인을 받아 최종적으로 브랜치를 병합할 수 있다.

  • PR의 제목 및 설명, 상태, 팀원 등이 리뷰를 남기는 댓글, 병합 조건 및 가능 상태, 병합 버튼 등이 존재한다.

  • 병합 작업은 대단히 중요하고, 되돌리기 힘든 것이기에 확인 작업을 몇 단계 거치게 된다. 다만 Github에서 Revert를 제공해주고 있기 때문에, 한번 병합하면 절대 못 되돌린다 이런 것까지는 아니지만, 주의하고 또 주의해서 병합을 진행해야한다.

  • 병합되고 남은 브랜치는 삭제를 권장한다.

  • 정상적으로 병합되었다면, 커밋 기록에서 병합 작업의 기록을 확인할 수 있다.



5. Github 브랜치 실습 2

원격저장소의 브랜치를 로컬 Git으로

  • Github에서 병합 이후 삭제한 브랜치는, 당연하지만 로컬 Git에는 여전히 남아있다.



로컬 브랜치 상태 업데이트

git fetch -p

  • 로컬 저장소에 원격 브랜치 상태를 업데이트. -p 옵션은 prune의 약자로, 로컬에 남아있는 삭제된 원격 브랜치를 제거하는 용도.



로컬 브랜치 삭제

git branch -d 'branch-name'

  • 이후에는 필요 없어진 로컬 브랜치를 수동으로 삭제해야한다. 우선 삭제하려는 브랜치에서 나와서 다른 브랜치로 이동해주어야 한다.

  • 그런데 바로 삭제를 시도하면, 병합되지 않은 브랜치를 삭제하려 한다는 경고가 출력된다. Github에서는 병합을 했지만, 로컬에서는 하지 않았기 때문. 더 정확하게는 원격 저장소의 상태가 로컬 저장소에 업데이트되지 않았기 때문이다.

git pull origin main

  • 따라서 원격 저장소의 내용을 로컬로 가져와서 업데이트 해준 뒤에, 로컬 브랜치를 삭제해주어야 한다.



요약

  1. git fetch -p를 통해 원격에서 삭제된 브랜치를 로컬에서도 인식하도록 수행.

  2. 이후, 로컬에 남아 있는 브랜치를 git branch -d 'branch-name' 명령어로 수동 삭제.

  3. 최종적으로, git pull origin main을 통해 원격의 최신 상태를 로컬에 반영, 모든 기록이 동기화되도록 수행.



6. Conflict (충돌)

  • 두 브랜치에서 동일한 파일의 같은 부분을 다르게 수정했을 때 발생하는 문제. Git은 기본적으로 각 커밋을 병합할 때 변경 사항을 자동으로 병합하지만, 같은 위치의 내용이 다르게 변경된 경우에는 자동 병합이 불가능하여 사용자가 수동으로 충돌을 해결해야한다.

  • 동일 파일, 동일한 줄을 서로 다르게 수정 / 한 브랜치에서는 파일을 수정하고 다른 브랜치에서는 삭제 / 병합 시 여러 브랜치에서 동일한 부분이 반복적으로 수정된 경우 등에서 충돌이 발생한다.

  • 충돌이 발생하면 Git은 자동으로 해결할 수 없는 부분을 알려주고, 사용자가 직접 충돌을 해결하도록 요청한다.

  • Github 사용 중에 경험하는 대단히 빡치는 일 중 하나. 쉽게 해결되는 상황이라고 해도 귀찮은 일이 하나 더해지는 것이고, 해결이 어려울 수록 빡침의 농도가 깊어진다.

  • Git 충돌 및 해결 방법에 대한 내용은 매우 중요한 것으로, 향후 독립된 포스팅에서 상세하게 다룰 예정이다.



Conflict 해결

  • Git, Github는 어느 파일의 / 어느 부분에서 충돌이 발생했는지 자동으로 파악하여 사용자에게 알려준다.
<<<<<<< HEAD
// 현재 브랜치의 변경 사항
=======
// 병합하려는 브랜치의 변경 사항
>>>>>>> branch-name
  • 필요에 따라 "현재 브랜치의 변경 내용" 또는 "병합하려는 브랜치의 변경 내용" 중 하나를 선택하거나, 두 내용을 합친 새로운 내용을 작성하여 충돌을 해결할 수 있다.

  • 충돌 구분자(<<<<<<<, =======, >>>>>>>)는 반드시 삭제해야한다.

  • 충돌을 해결한 파일을 저장하고, 변경 사항을 저장소에 반영. 충돌이 해결된 파일을 Git에 다시 스테이징하여 처리하면 완료.



Conflict를 미연에 방지하자

  • 충돌은 일단 발생하는 순간부터 작업에 지장을 주는 요소이다. 따라서 처음부터 충돌을 방지하는 것이 가장 좋은 충돌 해결 방법이다.

  • 작업 전 git pull로 최신 상태 유지, 협업 시에는 PR을 사용하여 충돌을 사전에 해결하고 코드 리뷰를 통해 오류를 최소화, 작업을 자주 커밋하고 푸시 (다른 팀원들이 최신 변경 사항을 빠르게 반영할 수 있도록) 해주는 방법 등을 적용해봄직 하다.

profile
프론트엔드 개발자를 준비하고 있습니다.

0개의 댓글