/ #GITHUB

Git 길라잡이

Git와 Github 차이

Git는 컴퓨터 파일의 변경사항을 추적하고 여러 명의 사용자들 간에 해당 파일들의 작업을 조율하기 위한 분산 버전 관리 시스템입니다. 원래는 Linux 소스코드를 관리할 목적으로 개발 되었습니다.

Git를 통해 버전을 관리하면 소스 코드가 변경된 이력을 쉽게 확인할 수 있으며, 특정 시점의 버전과 비교하거나 그 시점의 버전으로 파일을 되돌릴 수 있습니다. 또한 협업을 하는 경우 충돌을 감지하여 사용자에게 경고를 주기도 합니다.

GitHub는 Git Repository를 위한 웹 기반 호스팅 서비스 입니다. 클라우드 서버를 통해 로컬에서 관리하는 소스코드를 업로드하고 공유할 수 있어서 공동작업이 가능하도록 합니다. Git의 기본적인 기능을 제공하여 한 프로젝트에 여러 사람이 공동으로 작업하여도 효율적으로 버전관리를 할 수 있도록 합니다.

Git 용어

저장소(Repository)

저장소(Repository)는 말그대로 파일이나 폴더를 저장해 두는 장소를 말합니다. 파일의 변경 이력을 관리하며, 원격 저장소(Remote Repository)로컬 저장소(Local Repository)로 구분됩니다.

원격 저장소는 Github와 같이 소스코드를 공유하고 공동작업을 하기위한 저장소이고, 로컬 저장소는 개인 전용 저장소입니다.

평소에는 로컬 저장소에서 작업을 하지만 공동작업이 필요하다면 원격 저장소에 저장하고, clone 명령어를 통해 로컬 저장소로 코드를 가져올 수 있습니다.

작업 트리(Work Tree) / 인덱스(Index)

작업 트리(work tree)는 소스코드를 편집하는 작업 공간(workspace) 그 자체를 말하고, 인덱스(index)는 커밋을 하기 전에 작업 트리와 저장소 사이에 존재하는 공간을 말합니다.

커밋 작업은 작업 트리의 변경 이력을 바로 기록하지 않고 인덱스에 저장되어 있는 파일의 상태를 우선적으로 기록하게 됩니다.

인덱스에 파일 상태를 기록하는 것을 스테이징(staging)이라고 하며, add 명령어를 통해 스테이징 상태로 만들 수 있습니다. 커밋은 인덱스에 스테이징 상태로 있는 파일의 이력만 저장소에 저장하게 됩니다.

풀(pull) / 푸시(push)

풀(pull)은 원격 저장소에 저장된 파일을 로컬 저장소로 가져오는 것을 말하고, 푸시(push)는 로컬 저장소에 저장된 파일을 원격 저장소에 반영하는 것을 말합니다. 각각 pullpush 명령어를 통해 작업할 수 있습니다.

커밋(Commit)

commit파일의 변경 이력을 기록하는 명령어 입니다. 작업 트리(work tree)의 파일을 저장소(repository)에 스냅샷으로 기록하며, 시간순으로 저장되어 과거 변경 이력과 그 내용을 알 수 있습니다.

커밋들은 서로 체인구조를 형성하여 참조하고 있기 때문에 과거 커밋 내용이 훼손되면 그 이후의 커밋도 훼손되어 버립니다.

img01

위 그래프에서 각 노드는 커밋 시점을 나타냅니다. 보는 바와 같이 커밋 시점은 순차적으로 연결되어 있습니다.

각 커밋은 영문과 숫자로 구성된 40자리의 커밋 해시(commit hash)를 통해 구분되며, 커밋을 하기 위해서는 커밋 메시지의 작성이 필요합니다. 커밋 메시지를 작성하지 않으면 커밋이 이루어지지 않습니다.

커밋은 코드에 이력을 남기는 중요한 작업이므로 커밋 메시지에는 코드의 변경 내용과 변경 사유 등을 간략히 기록해 두는것이 권장됩니다.

브랜치(Branch)

브랜치(Branch)는 독립적인 작업을 진행할 수 있도록 해주는 기능입니다. 각각의 브랜치는 다른 브랜치의 영향을 받지않아서 여러 작업을 동시에 진행할 수 있습니다.

img06

로컬 저장소를 생성하면 기본적으로 메인(main) 브랜치에서 작업을 수행하게 되고, 체크아웃(checkout) 하기 전까지 모든 작업은 메인 브랜치에서 이루어집니다. 여기에서 체크아웃이란 브랜치를 옮기는 작업을 말합니다.

공동작업을 할때에는 우선 로컬 저장소의 메인 브랜치를 pull을 통해 원격 저장소의 메인 브랜치와 동기화 시킨 후, 작업전용 브랜치를 만들어 독립적인 작업을 하게 됩니다. 이때 브랜치가 생성되는데, 브랜치는 항상 최신 커밋을 참조하고 있습니다.

img11

브랜치는 작업에 따라 자유롭게 만들 수 있지만, 이를 효율적으로 관리하기 위해서는 사전에 팀원들과 어떤 방식으로 브랜치를 만들고 통합할 것인지 미리 정해두는 것이 좋습니다. 즉, 브랜치 전략이 있어야 합니다.

가정 널리 사용되는 브랜치 전략으로는 Git-Flow, Github-Flow, Gitlab-Flow가 있습니다.

Git-Flow

git flow메인(main) 브랜치(기존 master 브랜치)와 개발(devlop) 브랜치를 중점으로 운영하는 브랜치 전략입니다.

img07

이 전략에는 다음과 같이 5개의 브랜치가 존재합니다.

  1. main : 정식 배포되는 브랜치 입니다.
  2. develop : 개발 브랜치로, 이 브랜치를 기준으로 하여 각자 작업한 기능등을 병합합니다.
  3. feature : 단위 기능을 개발하는 브랜치 입니다. 개발이 완료되면 develop 브랜치에 병합됩니다.
  4. release : main 브랜치에 보내기 전에 품질검사를 하기 위한 브랜치 입니다.
  5. hotpix : main 브랜치에 배포한 이후 버그가 생겼을 때 긴급 패치를 위한 브랜치 입니다.

이 전략의 운영과정은 다음과 같습니다.

  1. main 브랜치에서 develop 브랜치를 분기하고, 개발자들은 develop 브랜치에 커밋을 합니다.
  2. 기능 구현이 있는 경우 develop 브랜치에서 feature-* 브랜치를 분기합니다.
  3. 배포를 준비하기위해 develop 브랜치에서 release-* 브랜치를 분기합니다.
  4. 테스트를 진행하면서 발생하는 버그 수정은 release-* 브랜치에 직접 반영합니다.
  5. 테스트가 완료되면 release 브랜치를 main과 develop 브랜치에 병합합니다.

Github-Flow

github flow는 git flow가 Github에서 사용하기에는 너무 복잡하여 나온 브랜치 전략입니다.

img08

hotfix 브랜치와 feature 브랜치를 따로 구분하지 않으며, Pull Request를 사용하는 것을 권장합니다.

브랜치는 항상 메인 브랜치를 통해 분기하며, 브랜치 이름과 커밋 메시지는 목적에 맞도록 명확하게 작성하는 것이 좋습니다. 또한 원격 브랜치로 수시로 push하고 자신이 하고 있는 작업을 다른사람이 확인할 수 있도록 합니다.

피드백이나 도움요청, 또는 병합 준비가 되었을 때는 pull request를 생성합니다.

현재 우리팀에서는 이 전략을 통해 버전을 관리하고 있으며, 이슈를 기반으로 하여 브랜치를 생성합니다. 다음 글을 참고하세요.

GitHub로 프로젝트 관리하기

Gitlab-Flow

gitlab flow는 복잡하지 않고 효율성을 높이기 위해 생긴 브랜치 전략으로, master, feature, production 브랜치가 존재합니다.

img09

기능 구현은 feature 브랜치를 통해서 이루어지며, master 브랜치는 production 브랜치로 병합됩니다. production 브랜치는 오직 배포만을 담당합니다.

Gitlab을 사용해 본적이 없어서 자세한 내용은 생략하겠습니다.

헤드(HEAD)

헤드(HEAD)현재 체크아웃된 브랜치의 커밋에 대한 참조 포인터입니다. 평소에는 브랜치를 통해 간접적으로 최신 커밋을 참조하고 있습니다.

img12

브랜치를 설명할때 체크아웃을 브랜치를 옮기는 작업이라고 했지만, 정확하게는 헤드의 참조를 변경하는 작업입니다. 위에 그림처럼 헤드가 브랜치를 통해 간접적으로 커밋을 참조하는 상태를 Attached HEAD State라고 하며, 직접적으로 커밋을 참조하는 상태를 Detached HEAD State라고 합니다.

img13

체크아웃을 통해 헤드를 옮김으로써 이전 커밋 지점으로 되돌아가는 것이 가능합니다.

원격 브랜치(Remote Branch)

원격 브랜치(remote branch)는 원격 저장소의 브랜치를 트래킹(Tracking)하는 브랜치 입니다. 로컬에 존재하면서 원격 저장소와 통신하며 자동으로 업데이트 됩니다.

원격 저장소를 clone하면 origin이라는 이름의 원격 브랜치가 생성되고, 로컬 브랜치를 원격 브랜치와 동기화시킵니다.

img14

원격 브랜치와 로컬 브랜치는 독립적으로 존재하기 때문에 로컬 브랜치에서 커밋하더라도 원격 브랜치에 바로 반영되지는 않습니다.

img15

병합(merge)

병합은 브랜치로 분기되어 있는 커밋을 하나의 브랜치로 합치는 작업을 말합니다. 병합 방식에 따라 fast-forward merge3-way merge로 나눌 수 있습니다.

fast-forward merge

다음과 같이 브랜치가 분기점으로부터 변화가 없는 상태를 빨리감기(fast-forward) 상태라고 합니다.

img16

main 브랜치는 issue1 브랜치가 분기한 이후로 변화가 없는 상태이며, 이 경우 merge를 하면 커밋 지점을 따로 만들지 않고 브랜치의 참조만 변경하게 됩니다.

img17

non fast-forward merge

다음과 같이 빨리감기 상태가 아니라면 참조변수를 옮기는 것 만으로 병합을 할 수 없습니다.

img18

이 경우 새로운 커밋을 만들어서 병합을 수행하게 됩니다.

img19

이때 main 브랜치에서 수정한 파일을 issue1 브랜치에서도 수정했다면 병합충돌이 발생할 수 있습니다. 튜토리얼을 참고해주세요.

패치(fetch)

pull은 패치와 병합 2가지 작업으로 구성되어 있습니다.

패치(fetch)는 원격 브랜치만 원격 저장소와 동기화시키며, 병합은 위에서 설명한 것과 같이 로컬 저장소를 원격 저장소와 동기화 시킵니다.

패치작업의 경우 바로 로컬 저장소에 변경 내용이 반영되지 않기 때문에 헤드만 이동하여 원격 저장소의 커밋을 확인할 수 있어서 병합충돌의 위험을 줄일 수 있습니다.

img23