Git 워크플로 | 워크플로 비교

Git 워크플로 비교: 알아야 할 사항

Git은 오늘날 가장 일반적으로 사용되는 버전 제어 시스템입니다. Git 워크플로는 Git을 사용하여 일관되고 생산적인 방식으로 작업하는 방법에 대한 레시피 또는 권장 사항입니다. Git 워크플로는 개발자와 DevOps 팀이 Git을 효과적이고 일관되게 활용하도록 장려합니다. Git은 사용자가 변경 사항을 관리하는 방식에 많은 유연성을 제공합니다. Git이 유연성에 중점을 두고 있다는 점을 감안할 때 Git과 상호 작용하는 방법에 대한 표준화된 프로세스는 존재하지 않습니다. Git이 관리하는 프로젝트에서 팀과 함께 작업할 때는 변경 흐름이 어떻게 적용될지에 대해 팀 전체가 동의하는지 확인하는 것이 중요합니다. 팀이 같은 입장을 공유하도록 하려면 합의된 Git 워크플로를 개발하거나 선택해야 합니다. 팀에 적합할 수 있는 Git 워크플로가 몇 가지 공개되어 있습니다. 여기서는 이러한 Git 워크플로 옵션 중 일부에 대해 살펴보겠습니다.

사용 가능한 워크플로는 다양하기 때문에 직장에서 Git을 구현할 때 어디서부터 시작해야 할지 파악하기가 어려울 수 있습니다. 이 페이지는 소프트웨어 팀을 위한 가장 일반적인 Git 워크플로를 조사하는 것부터 시작합니다.

글을 읽는 동안 이러한 워크플로는 확고한 규칙이 아닌 가이드라인을 제공하기 위한 것임을 기억하세요. 다양한 워크플로의 여러 측면을 개별 요구 사항에 맞게 조합할 수 있도록 무엇이 가능한지 보여드리겠습니다.

성공적인 Git 워크플로란?

팀의 워크플로를 평가할 때는 팀 문화를 고려하는 것이 가장 중요합니다. 워크플로는 팀의 효율성을 높이면서 생산성을 제한하지 않아야 합니다. Git 워크플로를 평가할 때 고려해야 할 몇 가지 사항은 다음과 같습니다.

  • 이 워크플로는 팀 규모에 따라 확장됩니까?
  • 이 워크플로는 실수와 오류를 쉽게 실행 취소할 수 있습니까?
  • 이 워크플로로 인해 팀에 불필요한 인지 오버헤드가 새로 부과됩니까?

중앙 워크플로

git 워크플로 | 중앙 리포지토리 및 로컬 리포지토리

중앙 워크플로는 SVN에서 전환하는 팀을 위한 훌륭한 Git 워크플로입니다. Subversion과 마찬가지로 중앙 워크플로는 중앙 리포지토리를 사용하여 프로젝트의 모든 변경 사항에 대한 단일 진입점 역할을 합니다. trunk 대신 기본 개발 브랜치를 main이라고 하며 모든 변경 사항이 이 브랜치에 커밋됩니다. 이 워크플로에서는 main 외에 다른 브랜치가 필요하지 않습니다.

분산된 버전 제어 시스템으로 전환하는 것은 막막한 작업처럼 보일 수 있지만 Git을 활용하기 위해 기존 워크플로를 변경할 필요는 없습니다. 팀은 Subversion을 사용하는 것과 똑같은 방식으로 프로젝트를 개발할 수 있습니다.

그러나 Git을 사용하여 개발 워크플로를 강화하면 SVN에 비해 몇 가지 이점을 얻을 수 있습니다. 첫째, 모든 개발자에게 전체 프로젝트의 로컬 복사본을 제공합니다. 이렇게 분리된 환경에서 각 개발자는 프로젝트에 대한 다른 모든 변경 사항과 독립적으로 작업할 수 있습니다. 개발자는 로컬 리포지토리에 커밋을 추가하고 편리해질 때까지 업스트림 개발을 완전히 잊어버릴 수 있습니다.

둘째, Git의 강력한 브랜칭 및 병합 모델에 액세스할 수 있습니다. SVN과는 달리 Git 브랜치는 코드를 통합하고 리포지토리 간에 변경 사항을 공유하기 위한 안전 장치 메커니즘으로 설계되었습니다. 중앙 워크플로는 개발자가 푸시하고 풀하는 원격 서버 측 호스팅 리포지토리를 활용한다는 점에서 다른 워크플로와 비슷합니다. 다른 워크플로와 비교하면 중앙 워크플로에는 정의된 풀리퀘스트 또는 포킹 패턴이 없습니다. 중앙 워크플로는 일반적으로 SVN에서 Git으로 마이그레이션하는 팀 및 소규모 팀에 더 적합합니다.

작동 방식

개발자는 중앙 리포지토리를 복제하여 시작합니다. SVN에서와 같이 프로젝트의 자체 로컬 복사본에서 파일을 편집하고 변경 사항을 커밋합니다. 그러나 이러한 새 커밋은 로컬에 저장되므로 중앙 리포지토리와 완전히 분리됩니다. 이를 통해 개발자는 편리한 중단점에 도달할 때까지 업스트림 동기화를 연기할 수 있습니다.

공식 프로젝트에 변경 사항을 게시하기 위해 개발자는 로컬 main 브랜치를 중앙 리포지토리로 "푸시"합니다. 이것은 아직 중앙 main 브랜치에 없는 로컬 커밋을 모두 추가한다는 점을 제외하면 svn commit과 동일합니다.

중앙 리포지토리 초기화

Git 워크플로: 중앙 베어(bare) 리포지토리 초기화

먼저 누군가가 서버에 중앙 리포지토리를 만들어야 합니다. 새 프로젝트라면 빈 리포지토리를 초기화할 수 있습니다. 그렇지 않으면 기존 Git 또는 SVN 리포지토리를 가져와야 합니다.

중앙 리포지토리는 항상 베어(bare) 리포지토리(작업 디렉터리가 없어야 함)여야 하며 다음과 같이 만들 수 있습니다.

ssh user@host git init --bare /path/to/repo.git

user에는 유효한 SSH 사용자 이름, host에는 서버의 도메인 또는 IP 주소, /path/to/repo.git에는 리포지토리를 저장하려는 위치를 사용해야 합니다. 참고로 .git 확장자가 일반적으로 리포지토리 이름에 추가되어 베어 리포지토리임을 나타냅니다.

중앙 리포지토리 호스팅

중앙 리포지토리는 Bitbucket Cloud 또는 Bitbucket Server와 같은 타사 Git 호스팅 서비스를 통해 만들어지는 경우가 많습니다. 위에서 설명한 베어 리포지토리를 초기화하는 프로세스는 호스팅 서비스에서 처리해 줍니다. 그런 다음 호스팅 서비스는 로컬 리포지토리에서 액세스할 수 있는 중앙 리포지토리 주소를 제공합니다.

중앙 리포지토리 복제

다음으로, 각 개발자는 전체 프로젝트의 로컬 복사본을 만듭니다. 이 작업은 git clone 명령을 통해 수행합니다.

git clone ssh://user@host/path/to/repo.git

리포지토리를 복제하면 Git에서 자동으로 origin이라는 바로 가기를 추가합니다. 이 바로 가기는 나중에 해당 리포지토리와 상호 작용할 것이라는 전제 하에 “상위” 리포지토리를 다시 가리킵니다.

변경 및 커밋

리포지토리를 로컬로 복제하면 개발자는 표준 Git 커밋 프로세스인 편집, 스테이징 및 커밋을 사용하여 변경을 수행할 수 있습니다. 스테이징에 익숙하지 않다면 작업 디렉터리에 모든 변경 사항을 포함시키지 않고도 커밋을 준비할 수 있는 방법을 뜻합니다. 이를 통해 많은 로컬 변경을 수행한 경우에도 고도로 집중된 커밋을 만들 수 있습니다.

git status # View the state of the repo
git add <some-file> # Stage a file
git commit # Commit a file</some-file>

이러한 명령은 로컬 커밋을 만들기 때문에 John은 중앙 리포지토리에서 무슨 일이 발생하는지 걱정할 필요 없이 원하는 만큼 이 프로세스를 반복할 수 있다는 것을 기억하세요. 이것은 더 단순하고 원자성 부분으로 세분화해야 하는 대규모 기능에 매우 유용할 수 있습니다.

새 커밋을 중앙 리포지토리에 푸시

로컬 리포지토리에 새로운 변경 사항을 커밋하면, 이러한 변경 사항을 푸시하여 프로젝트의 다른 개발자와 공유해야 합니다.

 git push origin main

이 명령은 새로 커밋된 변경 사항을 중앙 리포지토리로 푸시합니다. 변경 사항을 중앙 리포지토리로 푸시할 때 의도한 푸시 업데이트와 충돌하는 코드가 포함된 다른 개발자의 업데이트가 이전에 푸시되었을 수 있습니다. Git은 이러한 충돌을 나타내는 메시지를 출력합니다. 이와 같은 상황에서는 먼저 git pull을 실행해야 합니다. 이 충돌 시나리오는 다음 섹션에서 더 자세히 설명하겠습니다.

충돌 관리

중앙 리포지토리는 공식 프로젝트를 나타내므로 이 리포지토리의 커밋 기록은 신성하고 변경이 불가능한 것으로 취급해야 합니다. 개발자의 로컬 커밋이 중앙 리포지토리와 다른 경우 공식 커밋을 덮어쓰기 때문에 Git은 변경 사항 푸시를 거부합니다.

Git 워크플로: 충돌 관리

개발자는 기능을 게시하기 전에 먼저 업데이트된 중앙 커밋을 가져와서 이를 기반으로 변경 사항을 rebase해야 합니다. "모두가 이미 수행한 작업에 내 변경 사항을 추가해 주세요."라고 말하는 것과 같습니다. 결과는 기존 SVN 워크플로와 마찬가지로 완벽하게 선형적인 기록입니다.

로컬 변경 사항이 업스트림 커밋과 직접 충돌하는 경우 Git은 rebase 프로세스를 일시 중지하고 충돌을 수동으로 해결할 수 있는 기회를 줍니다. Git의 좋은 점은 커밋을 생성하고 병합 충돌을 해결하기 위해 동일한 git statusgit add 명령을 사용한다는 것입니다. 따라서 신규 개발자가 자신의 병합을 쉽게 관리할 수 있습니다. 또한 문제가 발생해도 Git을 사용하여 아주 쉽게 전체 rebase를 중단하고 다시 시도(또는 도움을 요청)할 수 있습니다.

일반적인 소규모 팀이 이 워크플로를 사용하여 공동 작업하는 방법에 대한 일반적인 예를 들어보겠습니다. John과 Mary라는 두 명의 개발자가 서로 다른 기능에 대해 작업하고 중앙 리포지토리를 통해 기여를 공유하는 방법을 알아보겠습니다.

자신의 기능에 대해 작업하는 John

Git 워크플로: 편집 스테이지 커밋 기능 프로세스

John은 로컬 리포지토리에서 표준 Git 커밋 프로세스인 편집, 스테이징 및 커밋을 사용하여 기능을 개발할 수 있습니다.

이러한 명령은 로컬 커밋을 만들기 때문에 John은 중앙 리포지토리에서 무슨 일이 발생하는지 걱정할 필요 없이 원하는 만큼 이 프로세스를 반복할 수 있다는 것을 기억하세요.

자신의 기능에 대해 작업하는 Mary

Git 워크플로: 편집 스테이지 커밋 기능

한편 Mary는 동일한 편집/스테이지/커밋 프로세스를 사용하여 자신의 로컬 리포지토리에서 기능에 대해 작업하고 있습니다. John과 마찬가지로 Mary는 중앙 리포지토리에서 무슨 일이 일어나는지 신경 쓰지 않으며 John이 자신의 로컬 리포지토리에서 어떤 작업을 하고 있는지도 별로 신경 쓰지 않습니다. 모든 로컬 리포지토리는 비공개이기 때문입니다.

기능을 게시하는 John

Git 워크플로: 게시 기능

John이 기능을 완료하면 다른 팀원들이 액세스할 수 있도록 로컬 커밋을 중앙 리포지토리에 게시해야 합니다. 그는 git push 명령을 사용하여 이 작업을 수행할 수 있습니다.

 git push origin main

origin은 John이 중앙 리포지토리를 복제할 때 Git이 만든 중앙 리포지토리에 대한 원격 연결이라는 것을 기억하세요. main 인수는 originmain 브랜치가 자신의 로컬 main 브랜치처럼 보이도록 Git에 지시합니다. John이 복제한 후 중앙 리포지토리가 업데이트되지 않았으므로 충돌이 발생하지 않으며 푸시가 예상대로 작동합니다.

기능을 게시하려는 Mary

Git 워크플로: 명령 오류 푸시

John이 중앙 리포지토리에 변경 사항을 성공적으로 게시한 후 Mary가 자신의 기능을 푸시하려고 하면 어떻게 되는지 살펴봅시다. Mary는 똑같은 푸시 명령을 사용할 수 있습니다.

 git push origin main

하지만 Mary의 로컬 기록이 중앙 리포지토리에서 분기되었기 때문에 Git은 세부 정보가 포함된 오류 메시지와 함께 요청을 거부합니다.

error: failed to push some refs to '/path/to/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

이것은 Mary가 공식 커밋을 덮어쓰지 않도록 합니다. Mary는 John의 업데이트를 자신의 리포지토리로 풀하여 로컬 변경 사항과 통합한 다음 다시 시도해야 합니다.

John의 커밋을 기반으로 rebase하는 Mary

Git 워크플로: Git 풀 다시 지정(rebase)

Mary는 git pull을 사용하여 업스트림 변경 사항을 리포지토리에 통합할 수 있습니다. 이 명령은 일종의 svn update와 같습니다. 전체 업스트림 커밋 기록을 Mary의 로컬 리포지토리로 풀하여 Mary의 로컬 커밋과 통합하려고 시도합니다.

 git pull --rebase origin main

--rebase 옵션은 아래 표시된 것처럼 Mary의 모든 커밋을 중앙 리포지토리의 변경 사항과 동기화한 후에 main 브랜치의 팁으로 이동하도록 Git에게 지시합니다.

Git 워크플로: 마스터로 다시 지정(rebase)

이 옵션을 잊어버리더라도 풀은 여전히 작동하지만 누군가가 중앙 리포지토리와 동기화해야 할 때마다 불필요한 “병합 커밋”이 발생하게 됩니다. 이 워크플로에서는 병합 커밋을 생성하는 대신 rebase를 수행하는 것이 좋습니다.

병합 충돌을 해결하는 Mary

Git 워크플로: 커밋에 다시 지정(rebase)

rebase는 각 로컬 커밋을 업데이트된 main 브랜치로 한 번에 하나씩 전송하여 작동합니다. 즉, 하나의 대규모 병합 커밋으로 모두 해결하는 대신 커밋별로 병합 충돌을 해결한다는 것을 의미합니다. 이렇게 하면 커밋에 최대한 집중하고 프로젝트 기록을 깔끔하게 유지할 수 있습니다. 결과적으로 버그가 발생한 위치를 훨씬 쉽게 파악하고 필요한 경우 프로젝트에 미치는 영향을 최소화하면서 변경 사항을 롤백할 수 있습니다.

Mary와 John이 관련 없는 기능을 작업하고 있다면 rebase 프로세스로 인해 충돌이 발생할 가능성은 거의 없습니다. 그러나 충돌이 발생할 경우, Git은 현재 커밋에서 rebase를 일시 중지하고 관련 설명과 함께 다음 메시지를 출력합니다.

CONFLICT (content): Merge conflict in <some-file>
Git 워크플로: 충돌 해결

Git의 가장 큰 장점은 누구나 자신의 병합 충돌을 해결할 수 있다는 것입니다. 이 예시에서 Mary는 git status를 실행하여 문제가 발생한 위치를 확인합니다. 충돌 파일은 병합되지 않은 경로 섹션에 나타납니다.

# Unmerged paths:
# (use "git reset HEAD <some-file>..." to unstage)
# (use "git add/rm <some-file>..." as appropriate to mark resolution)
#
# both modified: <some-file>

그런 다음 Mary는 원하는 대로 파일을 편집합니다. 결과에 만족하면 일반적인 방식으로 파일을 스테이징하고 나머지는 git rebase가 처리하도록 합니다.

git add <some-file>
git rebase --continue

이것이 전부입니다. Git은 다음 커밋으로 이동하여 충돌을 생성하는 다른 커밋에 대해 프로세스를 반복합니다.

이 시점에 도달했을 때 무슨 일이 일어나고 있는지 전혀 모른다고 해도 당황하지 마세요. 다음 명령을 실행하기만 하면 시작한 곳으로 바로 돌아갈 수 있습니다.

git rebase --abort

성공적으로 기능을 게시한 Mary

Git 워크플로: 중앙 리포지토리 동기화

중앙 리포지토리와 동기화를 완료한 후 Mary는 변경 사항을 성공적으로 게시할 수 있습니다.

 git push origin main

여기에서 나아가야 할 방향

보시다시피, 몇 개의 Git 명령만으로 기존의 Subversion 개발 환경을 복제할 수 있습니다. 이것은 팀을 SVN에서 전환하는 데 유용하지만 Git의 분산 특성을 활용하지는 않습니다.

중앙 워크플로는 소규모 팀에 적합합니다. 위에서 설명한 충돌 해결 프로세스는 팀의 규모가 확장됨에 따라 병목 현상을 일으킬 수 있습니다. 팀이 중앙 워크플로에 익숙하지만 공동 작업을 간소화하려는 경우 기능 브랜치 워크플로의 이점을 살펴보는 것이 좋습니다. 각 기능마다 분리된 브랜치를 지정하면 새로운 추가 사항을 공식 프로젝트에 통합하기 전에 추가 사항에 대한 심층적인 논의를 시작할 수 있습니다.

기타 일반적인 워크플로

기본적으로 중앙 워크플로는 다른 Git 워크플로의 빌딩 블록입니다. 가장 인기 있는 Git 워크플로에는 개별 개발자가 푸시 및 풀할 수 있는 일종의 중앙 리포지토리가 있습니다. 다른 인기 있는 Git 워크플로에 대해서는 아래에서 간략히 설명하겠습니다. 이러한 확장 워크플로는 기능 개발, 핫픽스 및 최종 릴리스를 위한 브랜치 관리와 관련하여 보다 특수화된 패턴을 제공합니다.

기능 브랜치

기능 브랜칭은 중앙 워크플로의 논리적 확장 기능입니다. 기능 브랜치 워크플로의 핵심 개념은 모든 기능 개발이 main 브랜치가 아닌 전용 브랜치에서 이루어져야 한다는 것입니다. 이 캡슐화를 통해 여러 개발자가 main 코드베이스를 방해하지 않고도 특정 기능을 대해 쉽게 작업할 수 있습니다. 또한 main 브랜치에 중단된 코드가 절대 포함되지 않는다는 뜻이기도 하며, 이것은 지속적 통합 환경에 큰 이점을 제공합니다.

Gitflow 워크플로

Gitflow 워크플로Vincent Driessen이 nvie에 처음 게시하여 매우 높게 평가받은 2010년 블로그 게시물에서 비롯되었습니다. Gitflow 워크플로는 프로젝트 릴리스를 중심으로 설계된 엄격한 브랜칭 모델을 정의합니다. 이 워크플로는 기능 브랜치 워크플로에 필요한 것 이상으로 새로운 개념이나 명령을 추가하지 않습니다. 대신 서로 다른 브랜치에 매우 구체적인 역할을 할당하고 상호 작용하는 방식 및 시기를 정의합니다.

포킹 워크플로

포킹 워크플로는 이 자습서에서 설명한 다른 워크플로와 근본적으로 다릅니다. 이 워크플로는 단일 서버 측 리포지토리를 사용하여 "중앙" 코드베이스 역할을 하도록 하는 대신 모든 개발자에게 서버 측 리포지토리를 제공합니다. 즉, 각 기여자가 하나가 아닌 개인 로컬 리포지토리와 공용 서버 측 리포지토리, 총 2개의 Git 리포지토리를 갖게 됩니다.

가이드라인

모든 상황에 맞는 Git 워크플로는 없습니다. 앞서 언급했듯이 팀의 생산성을 향상하는 Git 워크플로를 개발하는 것이 중요합니다. 워크플로는 팀 문화는 물론 비즈니스 문화도 보완해야 합니다. 브랜치 및 태그와 같은 Git 기능은 비즈니스 릴리스 일정을 보완해야 합니다. 팀에서 작업 추적 프로젝트 관리 소프트웨어를 사용하는 경우 진행 중인 작업에 해당하는 브랜치를 사용하는 것이 좋습니다. 또한 워크플로를 결정할 때 고려해야 할 몇 가지 가이드라인은 다음과 같습니다.

수명이 짧은 브랜치

브랜치가 프로덕션 브랜치와 분리되어 있는 시간이 길어질수록 병합 충돌과 배포 문제의 위험이 높아집니다. 수명이 짧은 브랜치는 더 깔끔한 병합 및 배포를 촉진합니다.

되돌리기 최소화 및 단순화

되돌려야 하는 병합을 사전에 방지하는 데 도움이 되는 워크플로를 갖추는 것이 중요합니다. 브랜치를 main 브랜치에 병합하기 전에 브랜치를 테스트하는 워크플로를 예로 들 수 있습니다. 하지만 사고는 발생하기 마련입니다. 따라서 다른 팀원의 흐름을 방해하지 않고 쉽게 되돌릴 수 있는 워크플로를 갖는 것이 좋습니다.

릴리스 일정 일치

워크플로는 비즈니스의 소프트웨어 개발 릴리스 주기를 보완해야 합니다. 하루에 여러 번 릴리스할 계획이라면 main 브랜치를 안정적으로 유지하는 것이 좋습니다. 릴리스 일정의 빈도가 낮은 경우 Git 태그를 사용하여 브랜치를 버전에 태그하는 것도 고려할 수 있습니다.

요약

이 문서에서는 Git 워크플로에 대해 살펴봤습니다. 실질적인 예시를 통해 중앙 워크플로에 대해 자세히 알아보았으며, 중앙 워크플로와 함께 특수 워크플로에 대해서도 추가로 살펴봤습니다. 이 문서에서 설명하는 몇 가지 주요 내용은 다음과 같습니다.

  • 모든 상황에 맞는 Git 워크플로는 없습니다
  • 워크플로는 간단해야 하고 팀의 생산성을 향상해야 합니다
  • 비즈니스 요구 사항은 Git 워크플로를 형성하는 데 도움이 됩니다

다음 Git 워크플로에 대해 알아보려면 기능 브랜치 워크플로의 포괄적인 세부 사항을 확인하세요.

Git을 배울 준비가 되었습니까?

이 대화형 자습서를 사용해 보세요.

지금 시작하기