Git Bash 를 찾아서 실행하면 아래 그림과 같이 CLI 명령을 입력할 수 있는 창이 나옵니다.
$ 기호와 윗줄에 표시된 경로 등을 합쳐서 프롬프트 라고 합니다.
공부하기전 간단한 용어 설명
Git Bash 에서 사용할 기본 CLI 명령어, 즉 폴더를 만들거나 위치를 이동하는 방법등을 간단하게 알려 드리겠습니다.
더 자세히 공부하고 싶다면 '리눅스 명령어 공부하기' 와 같은 키워드로 검색하시면 됩니다.
pwd : 현재 폴더의 위치를 확인합니다.
ls : 현재 폴더의 파일 목록을 확인합니다.
cd : 홈 폴더로 이동합니다.
cd 폴더이름 : 특정 위치의 디렉터리로 이동합니다.
cd .. : 현재 폴더의 상위 폴더로 이동합니다.
mkdir 폴더이름 : 현재 폴더의 아래에 새로운 폴더를 만듭니다.
로컬 저장소가 있는 현재 폴더, 다시 말해서 일반적인 작업 폴더를 Git 용어로 뭐라고 할까요?
Git 에서는 작업 폴더를 워킹트리 라고 합니다.
워킹트리 : 일반적인 작업이 일어나는 곳입니다.
로컬 저장소 : .git 폴더, 커밋은 로컬 저장소에 저장됩니다.
원격 저장소 : GitHub 저장소, 로컬 저장소를 업로드하는 곳입니다.
Git 저장소 : 엄밀하게는 로컬 저장소를 의미하지만 넓은 의미로는 작업 폴더 (워킹트리 + 로컬 저장소 ) 를 의미합니다.
Git 명령어
- git add 파일명 : 파일들을 스테이지에 추가합니다.
- git commit : 스테이지에 있는 파일들을 커밋합니다.
- git push origin 브랜치 이름 : 현재 브랜치에서 새로 생성한 커밋들을 원격 저장소에 업로드 합니다.
- git pull : 원경 저장소의 변경 사항을 워킹트리에 반영합니다. git fetch + git merge
- git fetch origin 브랜치 이름 : 원격 저장소의 브랜치와 커밋들을 로컬 저장소와 동기화 합니다.
- git merge 브랜치 : 지정한 브랜치의 커밋들을 현재 브랜치 및 워킹트리에 반영합니다.
변경 내용을 git add 명령어로 스테이제 추가 할 수 있는데,
이렇게 파일을 스테이지에 올린다하여 스테이징 이라고 합니다. 내리는 것은 언스테이징 입니다.
log: 커밋 히스토리 확인하기
- git log : 현재 브랜치의 커밋 이력을 보는 명령어 입니다.
- git log -n : 전체 커밋 중에서 최신 n 개의 커밋만 살펴봅니다.
- git log --oneline : 커밋 메시지를 한 줄로 요약해서 보여줍니다. 생략하면 커밋 정보를 자세히 표시합니다.
- git log --graph : 커밋 옆에 브랜치의 흐름을 그래프로 보여줍니다.
- git --all : all 옵션을 지정해 주지 않으면 HEAD 와 관계없는 옵션은 보여주지 않습니다.
- git --decorate : 브랜치와 태그 등의 참조를 간결히 표시합니다.
아래는 git --oneline 을 입력했을 때 모습입니다.
이때 5362015 라고 보이는 16진수 7자리 숫자는 커밋 체크섬 혹은 커밋 아이디 라고도 합니다.
원경 저장소 만들고 커밋 업로드
Git 에서 저장소를 만든 다음 git remote add 로 원경 저장소를 연결하시면 됩니다.
- git remote add origin 원경 저장소 주소 : 원격 저장소를 등록합니다.
- git remote -v : 원격 저장소 목록을 살펴봅니다.
하지만 git push 를 하면 오류가 생깁니다.
오류 메시지를 읽어 보면 로컬 저장소의 [main] 브랜치와 연결된 원격 저장소의 브랜치가 없어서 발생한 오류라는 것을 알 수 있습니다.
오류 메시지에 업스트림 이라는 텍스트가 보이는데, 업스트림 브랜치는 로컬 저장소와 연결된 원격 저장소를 일컫는 단어 입니다.
따라서 git push origin main 이라고 직접적으로 mian branch 에 넣어주거나 업스트림을 지정해 주셔야 합니다.
git push -u origin main # 푸시와 동시에 업스트림 지정
git log --oneline -1 # 확인
저장소 클론하기
git clone 깃 주소 를 입력을 하시면 저장소를 복제하는 것과 똑같은 작업을 할 수 있습니다.
하지만 아래의 과정을 보시면 git clone 깃 주소로 클론하려다 실패하는 모습을 보실 수 있습니다.
명령어를 실행할 때 [새로운 폴더명] 옵션을 지정하지 않으면 클론한 프로젝트 이름과 같은 폴더를 만들게 되는데 이미 hello-git-cli 라는 폴더가 있기 때문에 실패한 것입니다. 따라서 새로운 폴더명을 입력해 주셔야 합니다.
재대로 클론한 폴더가 작동하는지 테스트 해보면 아래와 같이 하시면 됩니다.
주의할 점은 원래 hello-git-cli 로 다시 작업을 하실 때에는 git pull 로 변경 사항을 가져와야 합니다.
CLI 환경에서 브랜치 생성 및 조작하기
커밋과 브랜치의 관계
브랜치, 언제 사용하나요??
가장 대표적을 브랜치를 사용하는 경우는 새로운 기능을 추가할 때 사용합니다.
main 브랜치에는 정상적으로 동작하는 안정적인 버전의 프로젝트가 저장되어 있기 때문에 새로운 기능을 추가할 때는 main 브랜치의 최신 커밋으로부터 브랜치를 생성해서 개발합니다. 이때는 개발, 코드리뷰, 테스트까지 모두 완료해서 이상이 없으면 main 브랜치에 병합합니다.
또한 이 외에도 버그수정, 병합과 리베이스 테스트, 이전 코드 개션등의 이유로 브랜치를 사용합니다.
- git branch -v : 로컬 저장소의 브랜치 목록을 보는 명령어 입니다. -v 옵션을 사용하면 마지막 커밋도 함께 표시됩니다.
- git branch 브랜치 이름 커밋체크섬 : 새로운 브랜치를 생성합니다.
- git branch -rv : 원격 저장소에 있는 브랜치를 보고 싶을 때 사용합니다. v 옵션을 추가하면 커밋 요약도 볼 수 있습니다.
- git switch -c 브랜치 이름 : 브랜치를 새로 생성하고 동시에 변경까지 합니다.
- git merge 대상 브랜치 : 현재 브랜치와 대상 브랜치를 병합할 때 사용합니다. 병합 커밋이 새로 생기는 경우가 많습니다.
- git rebase 대상 브랜치 : 현재 브랜치와 커밋들을 대상 브랜치에 재배치 합니다.
- git branch -d 브랜치 이름 : 특정 브랜치를 삭제할 때 유용합니다. HEAD 브랜치나 병합이 되지 않은 브랜치는 삭제가 불가능합니다.
- git branch -D 브랜치 이름 : 브랜치를 강제로 삭제하는 명령입니다.
git branch 브랜치이름 커밋체크섬
커밋 체크섬 값을 주지 않으면 HEAD로 부터 브랜치를 생성하게 됩니다.
아래와 같은 명령어를 순서대로 입력을 하면 mybranch1 이라는 브랜치가 생성되는 것을 보실 수 있습니다.
HEAD 는 현재 작업 중인 브랜치를 가리킵니다.
브랜치는 커밋을 가리키므로 HEAD 도 커밋을 가리킵니다.
결국 HEAD 는 현재 작업 중인 브랜치의 최근 커밋을 가리킵니다.
switch: 브랜치 변경하기
체크아웃이라는 기능을 통해 브랜치를 이동할 수 있습니다. 그래서 이를 체크아웃한다 라고도 이야기 합니다.
CLI 환경에서 브랜치를 이동할 때 git checkout 명령어 대신 git switch 명령어를 사용해야 합니다.
HEAD 와 브랜치가 분리되는 Detached HEAD 상황이 될 수 있기 때문입니다.
git switch mybranch1 # mybranch1 으로 이동
git log --oneline --all # 이동했는지 확인
echo "third - my branch" >> file1.txt # 파일 내용 변경
git add file1.txt # 스테이지 올리기
git commit -m "mybranch1 변경 사항 수정" # 커밋 추가
git log --oneline --all # 잘 반영됐는지 확인
새로운 커밋을 생성하면
1. 새로운 커밋을 생성하면 그 커밋의 부모는 언제나 이전 HEAD 커밋입니다.
2. 커밋이 생성되면 HEAD 는 새로운 커밋으로 갱신됩니다.
3. HEAD 가 가리키는 브랜치도 HEAD 와 함께 새로운 커밋을 가리킵니다.
fast-forward merge: 빨리 감기 병합하기
브랜치를 병합하는 명령은 git merge 입니다. 별도로 옵션을 지정하지 않으면 기본적으로 빨리 감기 병합이 수행됩니다.
"빨리 감기 병합"은 Git의 병합 전략 중 하나인 "Fast-Forward Merge"를 뜻하는 표현입니다.
이 방식은 Git이 브랜치를 병합할 때, 분기점(branch)이 생기지 않고 단순히 브랜치의 HEAD 포인터를 앞으로 이동시키는 병합 방법을 의미합니다.
fast-forward 예시
- A 브랜치에서 B 브랜치를 만들고, B 브랜치에서만 작업을 진행했다고 가정해봅시다.
- B 브랜치에서 작업을 끝내고 다시 A 브랜치로 병합을 할 때, A 브랜치가 B 브랜치의 바로 직전 커밋에 있을 경우 "빨리 감기 병합"이 가능합니다. 즉, A 브랜치가 B 브랜치의 커밋을 단순히 따라가면서 그 상태로 "이동"하기만 하면 됩니다.
- 이때, Git은 새로운 병합 커밋을 만들지 않고, A 브랜치의 포인터를 B 브랜치의 마지막 커밋으로 업데이트합니다.
이렇게 명령어를 차례대로 입력을 하시면 아래와 같이 잘 병합된 것을 볼 수 있습니다.
그리고 git merge mybranch1 을 하시면 업데이트 중5e409cb .. 2ccf958 Fast-forward 라는 메시지도 확인하실 수 있습니다.
reset --hard: 브랜치 되돌리기
git reset 명령은 현재 브랜치를 특정 커밋으로 되돌릴 때 사용합니다.
이 중에서 많이 사용하는 git reset --hard 명령을 실행하면 현재 브랜치를 지정한 커밋을 옮긴 후 해당 커밋의 내용을 작업 폴더에도 반영합니다.
즉 현재 브랜치를 지정한 커밋으로 옮깁니다. 작업 폴더의 내용도 함께 반영됩니다.
git reset --hard 이동할 커밋 체크섬
그림과 같이 reset --hard HEAD~2 를 실행해서 HEAD 를 2단계 이전으로 되돌렸습니다.
rebase: 빨리 감기 병합 상황에서 리베이스하기
빨리 감기 병합이 가능한 상황에서 git rebase 명령을 사용하면 좋습니다.
git rebase 대상 브랜치 명령은 현재 브랜치에만 있는 새로운 커밋을 대상 브랜치 위로 재배치합니다.
그런데 현재 브랜치에 재배치할 커밋이 없는 경우 git rebase 명령은 아무런 동작을 하지 않습니다. 또한 빨리 감기 병합이 가능한 경우
git merge 명령처럼 동작하게 됩니다.
git rebase 는 아래에 정리한 내용을 보시면 이해하실 수 있습니다.
현재 브랜치에 재배치할 커밋이 없을 경우 아래처럼 아무런 동작을 하지 않는 모습을 보실 수 있습니다.
하지만 main 브랜치로 이동한 후에 rebase 를 하면 빨리 감기가 가능한 상황이기 때문에 git merge 처럼 동작하는 모습입니다.
tag: 배포 버전에 태그 달기
CLI 에서 커밋에 태그를 달아보도록 하겠습니다. 이를 태깅이라고 하며, 태그에는 사실 주석이 있는 태그와 간단한 태그 두종류가 있습니다.
주석이 있는 태그를 추천합니다.
git tag -a -m 간단한메시지 태그이름 브랜치또는체크섬
git push origin main
-a 로 주석이 있는 태그를 생성합니다. 또한 메시지와 태그 이름은 필수이며 브랜치 이름을 생략하면 HEAD 에 태그를 생성합니다.
이렇게 설정을 하시면 아래처럼 tag: v0.1 이라는 tag 가 생성되는 것을 보실 수 있습니다.
3-way 병합하기
갑작스레 버그를 발견한 상황을 생각해 보겠습니다. 보통 이 경우 하나 이상의 브랜치로 다른 기능을 개선을 하고 있을 것입니다.
이런 상황에서 버그 수정은 다음과 같은 단계로 이루어 집니다.
- 오류가 없는 버전(Tag 가 있는 커밋) 으로 롤백
- main 브랜치로부터 hotfix 브랜치 생성
- 빠르게 소스 코드를 수정하고 테스트 완료
- main 브랜치로 빨리 감기 병합 및 배포
- 개발 중인 브랜치에도 병합
버그가 발생한 상황에서는 원래 작업 중이던 브랜치도 main 브랜치로부터 시작했으므로 같은 버그를 가지고 있을 것입니다. 그래서 hotfix 브랜치의 내용은 main 브랜치와 개발 브랜치 모두에 병합되어야 합니다. 보통 main 브랜치의 병합은 빨리감기 이기 때문에 쉽게 되는 반면,
개발 중인 브랜치의 병합은 병합 커밋이 생성되고, 충돌이 일어날 가능성이 높습니다.
의도적으로 충돌 일으키기
git switch main # main branch 변경
git switch -c feature1 #feature1 브랜치 생성 및 변경
echo "기능 1 추가" >> file.txt # 파일 내용 수정
git add file1.txt
git commit -m "기능 1 추가"
git log --oneline --graph --all # 변경 사항 확인
# 다음 작업
git switch -c hotfix main
echo "some hotfix" >> file1.txt
git add file1.txt
git commit -m "hotfix"
git switch main
git merge hotfix
git push
이렇게 설정을 하시면 feature1 branch 와 main branch 의 file1.txt 내용이 다른 것을 확인하실 수 있습니다.
여기서 일부로 충돌을 발생해 보겠습니다.
git switch feature1
git merge main
git status
그럼 이렇게 충돌이 난 모습을 보실 수 있습니다.
해결 방법은 간단합니다. 직접 충돌된 파일에 들어가서 아래와 같이 나온 모습을 보고 수정을 하시면 됩니다.
여기서는 두 변경 사항을 모두 수락해도 좋으니 두 변경 사항을 모두 수락으로 바꾸시면 됩니다.
cat file1.txt # 충돌 파일 해결 확인
git add file1.txt
git status
git commit -m "충돌 해결"
git push
rebase
3-way 병합을 하면 병합 커밋이 생성되기 때문에 트리가 다소 지저분해진다는 단점이 있습니다.
이럴 때 트리를 깔끔하게 하고 싶다면 git rebase 명령을 사용할 수 있습니다.
리베이스는 어떤 경우에 주로 사용하나요?
rebase 명령어는 주로 로컬 브랜치를 깔끔하게 정리하고 싶을 때 사용합니다.
되도록이면 원격 저장소는 rebase 를 하지 않는 것이 좋습니다.
리베이스의 원리는 다음과 같습니다.
- HEAD 와 대상 브랜치의 공통 조상을 찾습니다.
- 공통 조상 이후에 생성된 커밋들을 대상 브랜치 뒤로 재배치 합니다.
임시 브랜치에서 테스트 작업하고 삭제하기
많은 입문자가 충돌 해결, 병합, 리베이스 등을 . 할때 막연히 걱정부터 합니다.
소스가 깨지거나 열심히한 작업의 내용이 사라지는 두려움, 그리고 Git 의 커밋 히스토리가 꼬일 것 같은 느낌이 동시에 들기 때문입니다.
이럴 때 걱정을 덜 수 있는 아주 쉬운 방법은 임시 브랜치를 활용하는 것입니다. 나중에 삭제하면 그만이니까요
git branch test feature1 # feature1 브랜치에서 임시로 test 브랜치 생성
# 이런 저런 test 작업중....
git branch -D test