[Git] Git의 기본 개념 및 활용
전 세계 개발 관련 직종을 가지고 있는 인원들이라면 아주 흔하게 사용하는 Git.
그렇다면 Git은 무엇일까?
Git
Git은 버전 관리 시스템이며 그 중 DVCS(Distributed Version Control System)에 해당한다.
혹시라도 버전 관리 시스템에 대한 개념을 알고 싶다면, [CS] Version Control System, 버전 관리 시스템 을 참고하면 된다.
태생이 VCS인 만큼 버전 관리는 물론 소스 컨트롤을 위한 기능들이 포함되어 있다.
개발자들이 이전보다 더 효율적으로 협업을 하며 코드의 품질을 유지할 수 있도록 도와준다.
Git은 그 중 속도, 데이터 무결성, 분산된 비선형 워크플로우를 지향하는 버전 관리 시스템이다.
역사
Git은 2005년에 리누스 토발즈가 개발했으며, 개발 초기에는 리눅스 커널 프로젝트에서만 사용되었지만 Git의 효율성과 여러 기능 덕분에 빠르게 인기를 얻었고, 다양한 오픈 소스 프로젝트 및 기업에서 채택되었다.
현재는 대략 95%의 유저들이 버전 관리를 위해 사용중이다.(GitHub, SourceForge, BitBucket, GitLab의 기반은 Git이다.)
주요 특징
분산 버전 관리 시스템
디바이스별로 Repository(repo)라 불리우는 소스 코드의 저장소 내용을 로컬에서도 관리할 수 있도록 복사본을 보유하게 된다.
내용 중에는 이력은 물론 버전 추적 기능, 중앙 저장소와 분리된 환경 등을 제공하며, repo는 버전 제어 기능을 제공하기 위해 .git
과 같은 숨김 파일과 함께 저장된다.
이런 특징 덕분에 중앙 서버의 장애에도 데이터를 보호할 수 있고, 필요 시 복원도 가능하게된다.
브랜칭 및 병합
다양한 브랜칭 모델을 지원하여 독립적인 작업 흐름을 쉽게 관리할 수 있다.
이로써 중심이 되는 소스 코드를 매번 수정하지 않고 분리된 브랜치 영영에서 개발을 진행 후 해당 브랜치를 중점으로 다루다가 중심이 되는 코드와 merge를 진행하여 하나로 합칠 수 있다.
병합 과정에서 충돌을 효과적으로 해결할 수 있는 도구도 제공해준다.
이외에도 비선형 개발, 시스템 및 프로토콜 호환성, 대형 프로젝트의 효율적인 처리, 암호화 등의 특징을 가지고 있다.
개념은 이정도로 잡아두고 실제로 활용하는 방법에 대해 알아보자.
Git 활용
설치
GitHub와 GitLab 과 같은 플랫폼은 Git을 기반으로 하기에 로컬 디바이스에 GIt을 설치하여 플랫폼을 핸들링하겠다.
먼저 Git 사이트에 접근해보자.
사이트에 접근하게 되면 아래와 같은 메인 페이지가 노출된다.
About, Documentaion에 접근하여 Git에 대한 정보를 얻을 수 있음은 물론 Downloads로 git을 설치하여 사용할 수 있다.
우리는 설치하여 활용할 예정이기에 곧 바로 다운로드를 수행하겠다.
필자는 MacOS를 사용하기에 이를 기준으로 설명하겠다.
우측 “Download for Mac” 에 접근하면
와 같은 설치 가이드가 나온다.
git을 설치하여 관리하기 위해서는 Homebrew 혹은 MacPorts를 통해 설치하기 때문에 사전에 설치할 필요가 있다.
이 포스트에서는 Homebrew를 기준으로 진행할 것이기에 설치 되지 않았다면 https://brew.sh/를 통해 설치를 마치자.
설치를 마친 환경에서 터미널을 통해
$ brew intall git
의 커멘드를 입력하여 설치를 진행하자.
설치가 완료된 이후에 제대로 설치 되었는지 확인하기 위해
$ brew list
$ git --version
의 커멘드를 입력해보자.
정상적으로 설치가 되었다면 이제 git을 사용해보자.
git의 기본 커멘드는
$ _git_ [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]
[--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]
[--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]
<command> [<args>]
이며 커멘드에 대해 설명이 필요하다면
$ man [Command]
or
$ git help [Command]
을 통해 설명을 볼 수 있다.
https://git-scm.com/docs을 통해서도 커맨드에 대한 설명을 볼 수 있다.
사용
이번 포스트에서는 원격 저장소와 연동하지 않고 Git 자체를 사용하는 방법을 다루겠다.
설정
먼저 git 사용자의 정보를 설정해야한다.
기본적으로 설정할 영역은
- user.name
- user.email
이며 git으로 관리중인 파일을 누가 조작했는지에 대한 기록을 남기기 위함이다.
$ git config --global user.name "Your Name"
$ git config --global user.email "your.email@example.com"
와 같은 방법으로 설정이 가능하며, --global
의 경우에는 전역 설정으로 프로젝트마다 다르게 하고 싶으면 제외하고 사용할 수 있다.
나중에 GitHub 와 같은 원격 리포지토리를 사용할 수 있으니 해당 계정에 맞는 설정을 하는 것을 권장한다.
설정을 마친 뒤에는
$ git config --list
의 커맨드를 통해 설정된 값들을 열람할 수 있다.
git 저장소 생성
git으로 관리할 저장소를 생성하겠다.
먼저 디렉토리를 생성할 위치로 cd로 이동한다.
이후
$ mkdir local_git
$ cd local_git
$ git init
의 커맨드들을 이용하여 디렉토리를 생성하고, 접근하여 git을 초기화 시킨다.
git init
을 통해 초기화한다는 것은 현재 디렉토리를 git으로 관리할 수 있는 상태로 만든다는 의미이며
- .git 디렉토리 생성.
- 현재 디렉토리에 모든 메타데이터와 객체를 저장하는 .git 이라는 숨겨진 디렉토리가 생성된다.
- 기본 디렉토리 및 파일 생성.
- .git 내부에는 아래와 같은 디렉토리 및 파일들이 생성된다.
- HEAD: 현재 체크아웃된 브랜치 정보 파일.
- config: 저장소별 구성 설정 파일.
- description: 저장소 설명 파일로 기본적으로 비어 있으며, Git 웹 인터페이스에서 사용.
- hooks/: 커밋이나 푸시 등의 이벤트에 반응하는 스크립트.
- info/: 추가 정보 파일들.
- objects/: Git 객체 (블롭, 트리, 커밋) 파일들.
- refs/: 브랜치와 태그를 가리키는 파일들.
- .git 내부에는 아래와 같은 디렉토리 및 파일들이 생성된다.
의 과정들이 수행된다.
빈 디렉토리 내부에 숨겨진 .git 디렉토리 내부를 보면 이를 확인할 수 있다.
commit 상태 확인 및 add
다시 git init을 수행한 디렉토리로 돌아와
$ git status
를 수행하면 현재 git의 상태를 확인할 수 있다.
현재는 아무것도 수행하지 않았으니
와 같은 텍스트가 출력 될 것이다.
이제 파일을 추가해보자.
$ echo 'Hello, Git' > hello.txt
로 txt 파일을 생성한 뒤
$ git add hello.txt
$ git status
$ git commit -m "Commit hello.txt"
의 커맨드를 수행해보자.
commit할 파일을 add 를 통해 지정해준다.
현재는 hello.txt 만 있지만 모든 파일을 add 하고 싶을 때는 git add .
를 통해 모든 파일을 add할 수 있다.
add 한다는 의미는 commit 시 반영될 파일들은 스테이징 영역에 추가한다는 의미이며, commit은 스테이징된 파일들을 반영한다는 의미이다.
add 이후 status를 통해 해당 파일의 상태를 확인할 수 있으며, commit 시 메시지와 함께 리포지토리에 이를 반영한 것을 확인할 수 있다.
git status
는 물론 git log
를 통해 git으로 수행한 동작을 열람할 수 있다.
파일 수정
이제 hello.txt 파일은 git에 의해 관리되고 있다.
hello.txt를 echo 등으로 내용을 수정하고 git status
를 수행했을 때
와 같이 변경된 파일을 추적하는 것을 확인할 수 있다.
만약 이 내용을 반영하고 싶다면 이전과 같이
$ git add hello.txt
$ git commit -m "Update hello.txt"
를 수행하여 최신 상태로 유지시킬 수 있다.
브랜치 생성 및 이동
Branch는 일종의 분기를 나누는 것을 의미한다.
기본적으로 main branch만 존재하는 데 협업 시에는 main에서만 파일을 수정한다면 서로 곂치는 부분의 수정으로 충돌이 난다던가, 기능별로 유지보수 하기 힘든 환경에서 작업을 하게된다.
그렇기에 main은 최종적으로 반영하고 싶은 상태를 유지하고 필요한 기능이 있다면 이를 새로운 브랜치로 분기하여 그 브랜치로 이동하고, 파일을 통합하고, 반영하면서 최종적으로 main과 합치는 merge 동작을 수행하게된다.
이제 새로운 branch를 생성해보자.
$ git branch new_feature
$ git branch
$ git checkout new_feature
main 이외에 새로운 브랜치를 생성하여 해당 브랜치로 이동하는 동작을 수행했다.
현재 선택된 브랜치는 new_feature가 되겠다.
이제 이 브랜치에서 commit을 수행해보자.
$ echo 'This is a new feature.' > feature.txt
$ git add feature.txt
$ git commit -m "Add feature.txt in new_feature branch"
이후 new_feature 브랜치에서 git log
를 수행해보면
와 같다.
하지만 git checkout main
을 수행하여 git status
를 확인해보면
와 같이 new_feature에서 생성한 feature.txt가 나타나지 않게되고, 디렉토리에 접근하여도 파일이 존재 하지 않는다.
그러다 다시 git checkout new_feature
를 수행하면 feature.txt가 나타난다.
이는 브랜치 마다의 스냅샷이 서로 다르기 때문이다.
main 보다 나중에 commit이 이루어진 new_feture는 새로운 스냅샷을 가지고 있지만 main에서는 다른 브랜치에서 일어난 commit에 대해서는 관여하지 않기 때문이다.
그렇기에 main에서도 다른 브랜치에서 일어난 일들을 반영하기 위한 merge 작업을 수행해줘야한다.
브랜치 통합(merge)
위에서 설명했다시피 브랜치는 각각의 독립된 작업 흐름을 유지하기에 브랜치마다 어떤 동작이 이루어진지 모른다.
이 것을 알게하는것 혹은 이를 하나로 합치는 방법이 merge가 되겠다.
merge를 위해서는 merge를 수행하고 싶은 브랜치로 이동하여 타겟 브랜치를 merge를 지정해주면된다.
우리는 main에서 new_feature와 병합하고 싶으니
$ git checkout main
$ git merge new_feature
와 같은 명령어를 입력하여 각 브랜치의 반영 정보를 하나로 합칠 수 있다.
이제 main에서도 new_feature에서 수정한 내용을 확인할 수 있다.
충돌(Conflict)
하지만 같은 파일을 서로 수정했다면 어떻게 될까?
Conflict는 각 브랜치에서 같은 파일을 수정한 뒤 병합하려는 시점에 발생하는 이슈다.
이를 알기 위해서 실험을 해보자.
먼저 main 브랜치에서 파일을 수정하고 이를 add, commit해보자.
$ echo 'This is a new feature and modified at main' > feature.txt
$ git add .
% git commmit -m "files are modified at main"
이후 git checkout new_feature
를 통해 브랜치를 옮기면 main에서 수정한 내용이 new_feature에서는 확인 되지 않을 것이다.
이 떄도 물론 main을 merge 혹은 rebase를 통해 최신 상태로 유지하는 것이 좋지만 항상 최신 상태로 유지하는 것은 매우 번거로울 것이다.
new_feature 브랜치에서 main에서 수행한 내용을 merge 하지 않아 최신 상태가 아닐 때 파일을 수정해보자.
$ echo 'This is a new feature and modified at new_feature' > feature.txt
$ git add feature.txt
$ git commit -m "feature.txt is modified at new_feature"
파일을 수정하고 add, commit하는데 문제가 생기지 않는다.
하지만 이를 merge한다면?
$ git merge main
와 같이 충돌이 났다는 것을 알려준다.
그리고 git은 conflict가 난 파일에 어떤 부분이 어떻게 다른지 기록을 해준다.
feature.txt를 열어보면(편의상 파일을 직접 열어보자.)
<<<<<<< HEAD
This is a new feature and modified at new_feature
=======
This is a new feature and modified at main
>>>>>>> main
와 같이 이상하게 변경되어 있는 것을 확인할 수 있다.
이는
-
<<<<<<< HEAD
: 현재 브랜치(main)의 변경 사항. -
=======
: 두 변경 사항의 경계. -
>>>>>>> new_feature
: 병합하려는 브랜치(new_feature)의 변경 사항.
를 의미하고, 해석하자면
현재 브랜치(HEAD)에서 “This is a new feature and modified at new_feature”의 내용로 변경했고, main 브랜치에서는 “This is a new feature and modified at main” 와 같이 변경 되었다는 것을 의미한다.
충돌을 해결하기 위해서는 몇 가지 방법이 있다.
- 두 가지 변경 사항을 반영하여 수동으로 파일을 수정하고 최종본으로 배포한다.
- reset, checkout, restore 등을 통해 마지막 commit 이전으로 돌아간 뒤 rebase, merge를 하여 main의 최신 commit 읽어와 이를 토대로 다시 작업한다.
이 외에도 여러가지 있겠지만 “두 가지 변경 사항을 반영하여 수동으로 파일을 수정하고 최종본으로 배포한다.” 의 방법이 가장 일반적이기 때문에 자세히 다루진 않을 것이다.
이제 변경사항을 수동으로 조합하여 최종 버전을 만들어야한다.
$ echo 'Change in main and new_feature' > feature.txt
$ git add feature.txt
$ git commit -m "Resolve merge conflict in feature.txt"
$ git merge main
의 과정을 통해 파일을 변경하고 이를 다시 한번 merge하여 다른 충돌을 없는지 확인한다.
제대로 commit이 되었다면
$ git checkout main
$ git merge new_feature
를 통해 main도 변경 사항을 추적할 수 있게하자.
이외에도 파일을 마지막 커밋 상태로 되돌리는 방법, git으로 추적하지 않도록 .gitignore를 설정하는 방법, 스테이지를 컨트롤 하는 방법, 작성자를 변경하는 방법 등등 아직도 다양항 사용법 있다.
하지만 직접 사용해 보면서 이슈 발생 시 찾아보고 정리하는 것이 효율적이라 생각하기 떄문에 자세히 다루지는 않겠다.
실무 환경에서 commit 이력이라던가 branch 등을 지우는 등의 행위로 협업에 방해만 되지 않게 신중하게 사용해보자.
Ref.