오늘은 첫 수업으로 git에 대해 배웠다. 강사님으로는 코딩 공부를 해본 사람이라면 모르는 사람이 없을 상당히 유명하신 분이 오셨다.
직전학기 때 소프트웨어공학 수업을 들으면서 git을 어느 정도 알고는 있었지만 이번 수업을 듣고 나서는 더 제대로 git을 이해할 수 있게 된 것 같다.
버전 관리의 필요성과 git에 대해 간단한 설명을 듣고 난 뒤 폴더를 생성해서 git 저장소로 선언하고 안의 파일들을 수정한 뒤 add와 commit을 해 보며 git log와 git status, 그 밖의 자잘한 git 커맨드들을 알려 주셨다.
학교 수업을 들을 때 HEAD와 master의 개념이 잘 이해가 가지 않았는데 이번 시간에 그림을 그려 가면서 이 둘의 차이를 잘 설명해주셨다. checkout을 그냥 브랜치를 옮기는 명령어라고만 생각하고 있었는데 checkout을 왜 하는지 reset과 무슨 차이가 있는지도 알게 되었다.
그리고 add가 수정된 파일을 스테이지 영역으로 올리는 것 뿐만 아니라 untracked 파일을 tracked로 전환시키거나 conflict를 해결했다는 것을 알려주는 의미로도 사용된다는 것을 알게 되었다.
그 밖에도 HEAD와 master의 설명에서 이어진 브랜치의 개념, conflict 해결 방법, 깃허브를 사용하여 협업 작업 시 push, pull의 주의사항, gitignore등 많은 내용을 알아갈 수 있었고 2학기까지 해야 할 졸업프로젝트에도 많은 도움이 될 것 같은 좋은 수업이었다.
1. Git이란?
Git은 우리가 작업하는 파일들의 버전 관리를 자동으로 해주는 버전 관리 시스템이다.
버전 관리를 통해 우리는 디버깅, 백업, 협업 등이 가능해진다.
2. 저장소 생성
git init
위의 명령어를 통해 현재의 디렉토리를 git이 버전관리를 하도록 해준다.
로컬 저장소를 생성한다고도 할 수 있으며, 위의 명령어를 실행하면 해당 폴더에는 .git이라는 숨김폴더가 생성되는데 이 폴더는 해당 저장소의 변경이력을 저장한다.
3. git이 관리하는 프로젝트의 구조
git init으로 버전 관리를 하는 프로젝트는 위와 같은 구조를 갖고 있다.
앞서 말한 .git폴더는 버전 변경 이력을 관리하는 repository(저장소=리포지토리)라고 불리며
working directory는 우리가 직접 작업하는 폴더를 말한다.
워킹 디렉토리에서 수정된 파일은 stage area(인덱스라고도 함)라는 영역으로 보낼 수 있고, 이를 add 한다고 한다.
커밋된 적이 없는 파일은 명시적으로 add를 한 번은 해줘야 해당 파일을 git이 추척할 수 있게 된다. add를 해주지 않은 파일은 untracked file이라고 하며 git이 관리하지 못한다.
하지만 add를 했다고 바로 그 파일의 버전이 관리되는 것이 아니다. add되어 스테이지 영역으로 올라간 파일을 리포지토리로 보내주는 것을 commit 한다고 한다. commit을 하게 되면 비로소 지금까지 작업한 내용에 대한 새로운 버전이 만들어져 리포지토리에 기록이 된다.
- git add 명령어
git add
add는 위의 명령어로 할 수 있으며
뒤에 . 을 붙이면 작업 영역의 모든 파일을 add하고, 특정 파일만 지정해서 add할 수 있다.
- git commit 명령어
git commit -am '커밋메세지'
commit은 위의 명령어로 할 수 있으며 -m옵션은 커밋에 대한 정보를 기록할 커밋 메세지 입력, -a옵션은 add까지 한번에 수행해준다.
파일에 -a 옵션이 적용되려면 최초로 add 작업을 따로 한 번은 해 주어야 한다.
4. HEAD와 master 및 branch(브랜치)
commit을 통해 버전들이 생성되면 기본적으로 HEAD와 master라는 포인터가 버전들을 가리키게 된다.
우선 그림으로 확인을 해보자
저장소를 생성하고 A,B,C,D라는 커밋을 차례로 진행했을 때의 간단한 그림이다.
처음에 저장소를 생성하면 master는 현재의 버전을 가리키고, HEAD는 master를 가리킨다.
커밋을 하게 되면 기존의 버전을 부모 버전으로 삼는 새로운 버전이 생기게 된다.
그림을 보면 B 커밋의 부모는 A, C 커밋의 부모는 B 이런 식으로 되는 것이다. 그리고 master는 마지막으로 커밋된 버전을 따라가게 된다.
한 구절로 정의하기가 어렵지만 위의 그림의 상황에서 최대한 단순하게 표현하면
HEAD -> 현재 작업 중인 working dir이 어떤 버전인지를 가리킨다.
master -> 마지막으로 커밋된 버전을 가리킨다.
여기서 checkout이라는 작업을 통해 HEAD가 가리키는 버전을 바꿀 수가 있다.
git checkout '커밋ID'
위의 명령어를 통해 HEAD가 가리키는 버전 즉, 현재 작업중인 버전을 바꿀 수가 있는데
만약 위의 그림에서 git checkout 'C버전의커밋ID' 명령어를 실행하게 되면 HEAD와 master가 가리키는 버전이 아래의 그림과 같이 달라진다.
위의 그림과 같이 HEAD가 master를 가리키는 것이 아니라 직접 버전을 가리키게 되는 경우를
detached HEAD state 라고 한다.
다시 기존의 최신 버전으로 돌아가려면 D나 master로 체크아웃 하면 되긴 하지만
현재 작업하는 버전과 마지막으로 커밋된 버전이 다르다면 복잡한 커밋ID를 일일이 기억해야 하고, git log 명령어를 통해 버전을 확인할 때 HEAD가 가리키는 버전부터 부모 버전을 따라가며 표시하기 때문에 버전 확인에도 불편함이 있기 때문에 detached HEAD state는 최대한 피하는 것이 좋다.
HEAD는 되도록이면 버전을 직접 가리키지 않도록 해야 하기 때문에 브랜치라는 개념을 알아야 한다.
HEAD가 가리킬 수 있는 master 같은 포인터를 더 만들어 주어서 메인이 되는 작업과 실험적인 작업을 병렬적으로 수행하도록 해 주는 것이 바로 branch(브랜치)다.
브랜치를 사용하게 되면 커밋ID를 기억해가며 HEAD를 체크아웃 할 필요가 없어진다.
위의 그림을 보면 master가 아닌 exp라는 새로운 포인터가 있고 exp는 master가 가리키는 버전과 다른 버전을 가리키고 있다. 여기서 HEAD만 exp를 가리키도록 checkout 해주면서 버전 이동이 가능해진다.
저장소 생성 시점부터 존재하는 master는 git의 디폴트 브랜치라는 것을 알 수 있다.
브랜치 생성 명령어는
git branch 브랜치명
이고, git branch만 입력하면 생성된 브랜치들도 확인이 가능하다.
5. 버전 이력 확인
git log 명령어를 통해 버전들을 직관적으로 확인이 가능하다.
git log --oneline --all --graph
git log는 버전 관리 이력을 출력하여 확인할 수 있는 명령어로 --가 붙는 여러 옵션을 사용해 더 편리하게 확인이 가능하다.
- --oneline : 한줄로 축약하여 보여줌
- --all : 모든 버전을 보여줌
- --graph : 그래프의 형식으로 직관적으로 보여줌
위의 그림은 git log 명령어로 버전 이력을 확인해 본 그림이다.
exp라는 브랜치가 master와 별도로 작업 되다가 master와 합쳐졌고, 이 후 exp2라는 브랜치가 다시 별도로 작업되어 master와 합쳐진 것을 알 수 있다.
HEAD가 가리키고 있는 현재 작업 중인 영역은 master가 가리키고 있는 가장 최신 버전이라는 것도 알 수 있다.
6. 브랜치 병합
메인이 되는 작업 중 실험적인 작업을 별도로 테스트 하기 위해 별도의 브랜치를 만들어서 테스트를 성공적으로 마치면 메인 브랜치에 실험적인 브랜치의 내용을 반영해줘야 한다. 이를 위해 브랜치를 병합할 수 있으며 merge 명령어를 통해 가능하다.
git merge 병합할브랜치명
위의 명령어를 입력하면 현재 HEAD가 가리키고 있는 브랜치에 명령어에 입력한 브랜치의 내용을 병합해준다.
HEAD가 master 브랜치를 가리키고 있는 상황에서 exp브랜치를 머지한 경우의 그림을 살펴보자.
두 브랜치를 커밋하게 되면, 각 브랜치의 마지막 버전을 부모 버전으로 하는 새로운 커밋이 만들어지며 HEAD가 가리키고 있던 master 브랜치가 새로운 커밋을 따라가게 된다. exp 브랜치는 새로운 커밋을 쫓아가지 않는다.
두 브랜치가 서로 다른 파일에서만 작업을 하였다면 별 문제 없이 머지가 수행된다.
하지만 서로 다른 두 브랜치가 같은 파일을 수정하게 된다면 충돌이 일어난다.
위의 그림에서는 master 브랜치와 exp 브랜치에서 같은 파일을 수정했다. 해당 파일의 1번째 줄은 두 브랜치 모두 수정하지 않았고, 2번째 줄은 master 브랜치에서만, 3번째 줄은 exp브랜치에서만 수정하였고, 4번째 줄은 두 브랜치에서 모두 수정한 경우다.
이 때, git은 충돌을 최대한 피하기 위해 두 브랜치의 공통된 조상이 되는 버전(Base)을 기준으로 한쪽 브랜치에서만 수정된 부분은 수정된 쪽으로 반영을 해주고, 두 브랜치 모두 같은 부분을 수정하여 우선순위를 알 수 없는 경우에만 우리에게 충돌을 해결해 달라고 별도로 요청한다.
이런 방식의 병합을 3-way merge라고 한다.
3-way merge로도 해결할 수 없는 충돌은 우리가 직접 해결해주어야 한다.
직접 충돌된 부분을 수정해주면 수정된 파일은 add를 별도로 한 번 해 주어야 한다.
위와 같이 충돌을 해결한 파일을 add 해달라는 메세지를 확인할 수 있으며 add를 해 준 뒤 commit 해주면 된다.
지금까지 add가 사용된 부분을 보면 add는 3가지 의미를 갖는다는 것을 알 수 있다.
- working directory의 수정사항을 stage area로 올림
- untracked 파일을 git이 추적 가능한 tracked 파일로 변경
- conflict를 해결했다는 것을 알려줌
7. checkout과 reset
checkout은 HEAD가 가리키는 버전을 변경한다고 했다. 이를 커밋ID가 아닌 브랜치명으로 명령어를 실행해주면서 브랜치를 옮겨 다닐 수 있게 된다.
reset이라는 명령어는 HEAD가 가리키는 브랜치가 가리키는 버전을 옮기는 명령어라고 생각하면 되는데 삭제와 동시에 복원의 기능도 갖고 있다.
git reset --hard '커밋ID'
일반적으로 이런 형식으로 사용 가능하며 입력한 커밋ID에 해당하는 버전으로 돌아간다.
위의 그림을 통해 보면 A -> B로 새로운 버전이 커밋되었는데 B 버전의 변경내역을 삭제하고 싶으면 A로 reset 해주면 master가 A 버전을 가리키게 되고, A 버전으로 돌아가게 된다.
하지만 git의 불변성에 의해 git은 어떤 버전도 수정 및 삭제를 하지 않는다. 따라서 삭제인 것 처럼 보여도 B 버전은 삭제되지 않고 남아는 있다.
이러한 덕분에 만약 A 버전으로 돌아왔어도 B 버전의 커밋ID를 통해 다시 B 버전으로 복구가 가능해진다.
8. .gitignore
작업 영역에서 여러 파일들을 작업 할 때, ID/PW 등과 같은 민감한 정보들이 들어 있는 파일들은 git이 자동으로 커밋하지 않도록 별도로 설정해 줄 필요가 있다. 특히 깃허브 등을 통해 저장소가 오픈소스로 공개될 경우 이는 보안적으로 매우 위험하다.
하지만 특정 파일만 add 및 commit 하지 않으면 git status로 상태 확인 시 계속해서 거슬리는 메세지가 뜰 것이다.
이를 위해 .gitignore를 사용할 수 있다.
.gitignore는 git이 무시해야 하는 파일들에 대한 정책 파일로
.gitignore 파일을 생성하고 해당 파일에 원치 않는 파일을 적어주면 git이 적힌 파일들은 add 및 커밋하지 않게 된다.
.gitignore 파일은 add와 커밋을 해 주어야 한다.
9. Github(깃허브)
로컬 저장소에서 작업한 파일들을 원격의 저장소에 안전하게 저장하거나 다른 사람들과 협업을 하기 위해 github를 사용할 수 있다.
Github(깃허브)는 이 원격 저장소를 빌려주는 사이트이며, 깃허브를 통해 제 3자와 같은 프로젝트를 협업 작업할 수도 있게 된다.
원격 저장소는 지역 저장소에 대해 알 필요가 없지만 지역 저장소에서는 원격 저장소가 어디 있는지 알 필요가 있다.
깃허브에서 생성한 저장소의 주소를 복사하고
git remote add origin '원격저장소url'
해당 명령어를 로컬 저장소에서 입력해주면 로컬 저장소에게 원격 저장소를 알려주게 된다.
origin은 원격 저장소의 주소를 가리키는 이름이며 다른 이름도 가능하지만 일반적으로 origin을 사용한다.
원격 저장소를 연동하면 origin/master 라는 새로운 브랜치가 생기는데 이는 원격 저장소에 어디까지 동기화 했는지를 기록하는 remote tracking 브랜치이다.
로컬 저장소에서 작업한 뒤 커밋을 해 주고, push를 해주면 연동된 원격 저장소로 변경 내역이 반영된다.
반대로 로컬 저장소에서 pull을 해주면 원격 저장소의 최신 내용을 로컬로 동기화 해준다.
원격 저장소를 그대로 로컬로 불러올 때 git clone 명령어를 사용할 수 있다.
git clone '원격저장소url' .
명령어를 입력해주면 현재 로컬 폴더로 원격 저장소를 복제한다.
로컬에서 원격 저장소의 최신 버전을 갖고 있지 않은 채로 push를 하게 되면 reject가 일어난다.
이는 협업 시 서로 다른 사람들이 같은 버전에서 작업하도록 하기 위한 것으로 두 로컬 저장소가 동시에 작업했다면 충돌과 상관없이 reject가 발생한다. (두 로컬 저장소가 서로 다른 파일만 작업해서 충돌이 없어도 reject)
따라서 항상 작업 전에 git pull을 해와서 원격의 내용과 병합된 새 커밋을 만들고 push를 해 주어야 한다.
'KT AIVLE School' 카테고리의 다른 글
(2주차 - 22.08.04) 웹크롤링2 (0) | 2022.08.04 |
---|---|
(2주차 - 22.08.03) 웹크롤링 1 (0) | 2022.08.03 |
(2주차 - 22.08.02) Python 라이브러리 활용 [데이터 분석] 2 (0) | 2022.08.02 |
(2주차 - 22.08.01) Python 라이브러리 활용 [데이터 분석] 1 (0) | 2022.08.02 |
(1주차 - 22.07.28~22.07.29) Python 프로그래밍 (0) | 2022.08.01 |