/ #GITHUB

Git 튜토리얼

git 설치

기본적인 용어를 알아보았으니 실제로 명령어를 통해 커밋과 풀, 푸시를 해보도록 하겠습니다. IDE를 통해 쉽게 작업할 수 있지만 터미널상에서 명령어를 통해 커밋을 하겠습니다.

우선은 원격 컴퓨터에 로그인하고 git를 설치합니다.

$ sudo apt install git

커밋에 정보를 남기기 위해 config에 name과 mail을 입력합니다. email은 github에 입력된 email을 입력해야 잔디가 심어집니다.

$ git config --global user.name [userName]
$ git config --global user.email [userEmail]

커밋(commit)

우선 로컬 저장소를 생성해보도록 하겠습니다. 우선 작업 트리를 생성하고 이동합니다.

$ mkdir -p ~/git/example && cd ~/git/example

로컬 저장소는 init 명령어를 통해 생성할 수 있습니다.

init 명령어
$ git init

로컬 저장소가 생성되면 .git 폴더가 생성됩니다. 이제 임의의 파일을 하나 생성하고 임의의 내용을 작성합니다.

$ touch test.txt && echo "임의의 내용 작성 1" > test.txt

img02

커밋을 하기 위해서는 add 명령어를 통해 인덱스에 파일을 등록해야 합니다.

add 명령어
$ git add test.txt

인덱스에 등록된 파일을 확인하려면 status 명령어를 사용합니다.

status 명령어
$ git status

img03

이제 commit 명령어를 통해 변경 상태를 기록합니다. 이때 -m 옵션을 통해 커밋 메시지를 작성해야만 합니다.

-m 옵션
$ git commit -m "test.txt 파일 생성"

커밋 로그를 확인하고 싶다면 log 명령어를 입력해보세요.

log 명령어
$ git log

img04

스테이징과 커밋을 한번에 해결하려면 commit 명령어에 -am 옵션을 주면 됩니다.

-am 옵션
$ git commit -am "test.txt 파일 생성"

푸시(push)와 풀(pull)

이번에는 방금 생성한 로컬 저장소를 원격 저장소에 push 해보겠습니다. 그전에 ssh 인증키를 만들어서 github에 등록합시다.

$ ssh-keygen -t ed25519 -C "이메일 주소"

passphrase를 입력하라고 뜨면 암호를 입력해주거나 그냥 엔터를 눌러주시면 됩니다.

인증키가 생생되면 다음 명령어로 개인키를 확인합니다.

$ cat ~/.ssh/id_ed25519.pub

터미널에 뜬 개인키를 복사하고 github에서 Settings > SSH and GPG keys > New SSH Key로 이동하여 개인키를 등록해줍니다.

이제 github에 접속하여 원격 저장소를 하나 생성합니다.

저장소 주소를 복사한 후 remote add명령어를 통해 원격 저장소를 추가해줄 수 있습니다.

$ git remote add origin git@github.com:[userName]/[repository].git

원격 저장소가 추가되었는지 확인하기 위해서는 remote 명령어에 -v 옵션을 주면 됩니다.

$ git remote -v

이제 push 명령어를 통해 로컬 저장소의 변경 내역을 원격 저장소에 반영하도록 하겠습니다.

$ git push origin main

github의 원격 저장소에 접속하면 test.txt 파일이 저장된 것을 확인할 수 있습니다.

이번에는 원격 저장소를 clone을 통해 복사하여 example2 작업 트리에 로컬 저장소를 만들어보겠습니다.

우선 example2 작업 트리를 만들고 해당 폴더로 이동합니다.

$ mkdir -p ~/git/example2 && cd ~/git/example2

clone 명령어를 통해 원격 저장소를 복사합니다.

$ git clone git@github.com:[userName]/[repository].git .

test.txt 파일을 수정해봅시다.

$ echo "내용 추가" >> test.txt

img05

test.txt 파일을 스테이징 하여 커밋하고 원격 저장소에 푸시해줍시다.

$ git commit -am "내용 추가"
$ git push origin main

example 작업트리로 이동하여 test.txt 파일의 내용을 확인합시다.

$ cd ~/git/example && cat test.txt

아직 원격 저장소와 동기화되지 않았습니다. pull 명령어로 원격 저장소의 커밋 내역을 로컬 저장소에 합치고 test.txt 내용을 확인하면 동기화가 된 것을 알 수 있습니다.

$ git pull origin main && cat test.txt

브랜치(branch)

이제 브랜치를 만들어 보겠습니다. 브랜치는 branch 명령어를 통해서 추가할 수 있습니다.

브랜치 추가
$ git branch issue1

브랜치가 성생되었는지 확인하려면 다음 명령어를 입력하세요.

브랜치 생성 확인
$ git branch

img21

브랜치를 이동하려면 checkout 명령어를 사용합니다. git 2.23 부터는 checkout의 기능이 분리되어 switch 명령어를 통해서도 브랜치의 이동이 가능합니다.

$ git switch issue1

브랜치를 생성하고 바로 이동하려면 다음과 같이 -b 옵션 / -c 옵션을 붙이면 됩니다.

checkout의 경우
$ git checkout -b issue1
switch의 경우
$ git switch -c issue1

브랜치의 삭제는 -d 옵션을 사용합니다.

브랜치 삭제
$ git branch -d issue1

병합(merge)

이번에는 병합을 해보겠습니다.

issue1 브랜치로 이동하여 test.txt 파일을 임의로 수정하고 스테이징 후 커밋을 합니다.

img22

VSCode의 extension인 Git Graph를 통하여 커밋 로그를 그래프로 확인하면 브랜치가 하나로 이어져있는 것을 확인할 수 있습니다.

img23

이것은 현재 issue1 브랜치를 기준으로 main 브랜치는 fast-forward 상태에 있기 때문입니다.

img24

main 브랜치로 이동하고 merge 명령어로 하면 main 브랜치는 fast-forward merge가 됩니다.

fast-forward merge
$ git switch main
$ git merge issue1

img25

이번에는 non fast-forward merge를 해보겠습니다.

issue1 브랜치로 이동하여 test.txt 파일을 수정한 후 커밋합니다.

issue1 커밋
$ git switch issue1
$ echo -e "\nissue1 내용 추가" >> test.txt
$ git commit -am "test.txt issue1 내용 추가"

메인 브랜치로 이동 후 issue2 브랜치를 생성하여 이동 후, test.txt 파일을 수정한 후 커밋합니다.

issue2 커밋
$ git switch main
$ git switch -c issue2
$ echo "issue2 내용 추가" >> test.txt
$ git commit -am "test.txt issue2 내용 추가"

메인 브랜치로 이동하면 다음과 같이 fast-forward 상태가 됩니다.

img26

우선 issue1 브랜치를 fast-forward merge 합니다.

메인 브랜치가 fast-forward 상태이기때문에 그냥 merge 명령어만 사용하도 fast-forward merge가 되지만, --ff 옵션을 주어 명시적으로 fast-forward merge를 하겠습니다.

–ff 옵션을 통한 fast-forward merge
$ git switch main
$ git merge --ff issue1

img27

메인 브랜치의 참조 커밋이 변경되면서 non fast-forward 상태가 되었습니다. 이 상태에서는 자동으로 non fast-forward merge가 됩니다.

하지만 issue1과 issue2 양쪽에서 같은 파일을 수정했기 때문에 병합충돌이 발생합니다.

non fast-forward merge
$ git merge issue2

img28

test.txt 파일을 열어봅니다. 메인 브랜치인 HEAD의 변경 부분과 issue2 브랜치 변경부분을 다음과 같이 구분하여 파일이 수정되어 있습니다.

img29

HEAD(메인 브랜치)의 변경부분
<<<<<<< HEAD
브랜치 병합 테스트
issue1 내용 추가
=======
issue2 브랜치의 변경부분
=======
issue2 내용 추가
>>>>>>> issue2

코드를 수정하고 스테이징 후 다시 커밋을 보내면 커밋이 생성되면서 merge가 됩니다.

test.txt
임의의 내용 작성 1
내용 추가
브랜치 병합 테스트
issue1 내용 추가
issue2 내용 추가
병합충돌 해결
$ git commit -am "병합충돌 해결"

img30

--no-ff 옵션을 사용하면 fast-forward 상태일지라도 non fast-forward merge를 할 수 있습니다.

issue1 브랜치로 변경하여 메인 브랜치와 fast-forward merge를 하고, test.txt를 수정한 후 스테이징 및 커밋합니다.

issue1 새로운 커밋
$ git switch issue1
$ git merge main
$ echo "issue1 새로운 커밋" >> test.txt
$ git commit -am "issue1 새로운 커밋"

메인 브랜치로 이동하여 –no-ff 옵션을 통해 issue1을 non fast-forward merge 해봅시다.

–no-ff 옵션
$ git switch main
$ git merge --no-ff issue1

다음과 같이 커밋 메시지를 적기위한 에디터가 뜹니다. vim과 같은 명령어를 사용합니다.

img31

:wq를 입력하면 병합이 완료됩니다.

img32

또다른 병합 옵션으로 --squash가 있는데 이는 rebase를 알아야합니다.

리베이스(rebase)

리베이스(rebase)는 이름 그대로 커밋이 분기되는 base를 수정하는 작업입니다. 공통된 커밋으로부터 수정하려는 base 커밋까지의 차이점을 패치(patch)라는 공간에 임시로 저장하고 참조 커밋을 수정한 후 패치(patch)를 순차적으로 적용합니다.

방금 병합한 작업을 다음 상태로 되돌려 베이스가 수정되는 과정을 보겠습니다.

img27

커밋 되돌리기
$ git reset --hard HEAD~2
$ git switch issue1
$ git reset --hard HEAD~2

우선 issue2 브랜치로 이동하여 rebase 명령어로 메인 브랜치를 base로 만듭니다.

리베이스
$ git switch issue2
$ git rebase main

아까 상황과 마찬가지로 issue1과 issue2에서 동시에 같은 파일을 수정했기 때문에 패치를 만드는 과정에서 병합충돌이 발생했습니다.

img33

아까와 동일하게 test.txt 파일을 수정합니다.

test.txt
임의의 내용 작성 1
내용 추가
브랜치 병합 테스트
issue1 내용 추가
issue2 내용 추가

스테이징 한 후 병합과는 달리 커밋을 하지 않고 rebase 명령어에 --continue 옵션을 주어 리베이스를 계속 진행하도록 합니다.

리베이스 진행
$ git add test.txt
$ git rebase --continue

그러면 커밋 메시지를 입력하는 에디터가 뜹니다. :wq를 입력하여 커밋을 생성합니다.

img34

이제 메인 브랜치로 이동하여 fast-forward merge를 합니다. 병합과 비교해보면 issue2 브랜치에 있던 커밋이 main 브랜치로 베이스가 옮겨졌음을 확인할 수 있습니다.

리베이스의 경우

img35

병합의 경우

img30

중간에 리베이스를 취소하려면 –continue 옵션 대신에 –abort 옵션을 넣습니다.

리베이스 취소
$ git rebase --abort

리베이스를 사용하면 커밋 히스토리가 한줄로 깔끔해지지만 이미 공개된 저장소에 push한 커밋을 리베이스하면 히스토리가 꼬일 수 있으므로 주의해야합니다.

체리픽(cherry-pick)

체리픽(cherry-pick)은 리베이스와 유사하게 베이스를 변경하는 작업인데, 원하는 커밋만 pick해서 브랜치에 적용시키는 작업입니다.

issue1 브랜치로 이동하여 커밋을 2번 합니다.

issue1 브랜치에서 2번의 커밋 작업
$ git switch issue1
$ git merge main
$ git echo "issue1 체리픽1" >> test.txt
$ git commit -am "issue1 체리픽1"
$ git echo "issue1 체리픽2" >> test.txt
$ git commit -am "issue1 체리픽2"

로그를 통해 체리픽1 커밋의 해시를 확인합니다.

커밋 해시 확인
$ git log --oneline

img37

메인 브랜치로 이동하여 cherry-pick 명령어를 통해 체리픽 작업을 합니다.

cherry-pick
$ git switch main
$ git cherry-pick [커밋 해시]

마찬가지로 패치(patch) 과정에서 충돌이 발생합니다.

패치(patch) 충돌

img36

test.txt 파일을 충돌나지 않도록 수정합니다.

test.txt
임의의 내용 작성 1
내용 추가
브랜치 병합 테스트
issue1 내용 추가
issue2 내용 추가
issue1 체리픽1

저장하고 스테이징 후 --continue 옵션으로 체리픽을 계속 진행해주세요.

체리픽 진행
$ git add .
$ git cherry-pick --continue

다음과 같이 원하는 커밋만 브랜치에 가져왔습니다. 커밋을 새로 생성했기 때문에 커밋 해시가 다르다는 것을 볼 수 있습니다.

체리픽 결과

img38

중간에 체리픽을 취소하려면 --continue 옵션 대신에 --abort 옵션을 넣으면 됩니다.

체리픽 취소
$ git cherry-pick --abort

스쿼시 병합(squash merge)

체리픽과 유사한 작업이 병합의 --squash 옵션입니다. 스쿼시 옵션은 리베이스처럼 브랜치 베이스의 참조를 변경하지만, 리베이스가 베이스 이후로 브랜치 전체 커밋을 반영하는 것과 달리 스쿼시 병합은 브랜치의 커밋을 하나로 합쳐 체리픽처럼 새로운 하나의 커밋을 생성합니다.

체리픽 작업 이전으로 되돌리기
$ git reset --hard HEAD~
스쿼시 병합
$ git merge --squash issue1
test.txt 패치(patch) 충돌 해결
임의의 내용 작성 1
내용 추가
브랜치 병합 테스트
issue1 내용 추가
issue2 내용 추가
issue1 체리픽1
issue1 체리픽2
스쿼시 병합 진행
$ git commit -am "스쿼시 병합"

reset과 revert

커밋 상태를 과거로 되돌리는 명령어에는 resetrevert가 있습니다.

reset은 헤드 자체를 과거로 옮겨 특정 커밋으로 되돌리는 작업을 하며, revert는 과거로 되돌린다는 이력을 남기고서 특정 커밋을 없던 것으로 하는 작업을 합니다.

reset

reset의 옵션에는 --soft, --mixed, --hard가 있습니다.

--soft : 커밋을 하기 전단계인 스테이징 상태로 돌려놓습니다.
--mixed : 기본값으로 적용되며, 파일을 스테이징 이전단계로 돌려놓습니다.(파일이 작업 트리에 존재)
--hard : 추적 상태인 파일을 작업 트리에서 제거합니다. 즉, 파일을 수정하기 전으로 돌려놓습니다.

명령어의 형태는 다음과 같습니다.

$ git reset [옵션(선택)] [커밋 해시]

커밋 해시 대신에 헤드의 상대적인 수치로도 reset을 수행할 수 있습니다. 틸트(~숫자)의 경우 틸트로부터 숫자만큼 reset을 수행하며, 캐럿(^)의 경우 헤드로부터 한단계 아래 커밋으로 reset을 수행합니다.

예를 들어 아래 코드의 경우 2칸 이전의 커밋을 파일 수정 이전으로 되돌립니다.

reset
$ git reset --hard HEAD~2

revert

revert는 하나의 커밋 상태로 되돌리며, 상태를 되돌릴때마다 revert 상태를 남기는 커밋을 생성합니다. 다음과 같이 여러 커밋에 대하여 revert 작업이 가능합니다.

revert
$ git revert HEAD^ HEAD^^

스태쉬(stash)

스태쉬(stash)는 추적 상태에 있거나 스테이징 상태에 있는 파일을 임시로 저장하는 작업입니다. 추적 상태에 있거나 스테이징 상태인 파일이 있다면 다른 브랜치로 이동하지 못합니다. 이때 스태쉬를 통해 파일을 저장하고 다른 브랜치로 이동할 수 있습니다.

다음 명령어를 통해 새로운 스태쉬를 생성할 수 있습니다.

스태쉬 생성
$ git stash

또는

$ git stash save

스태쉬 스택의 확인은 다음 명령어를 통해 확인할 수 있습니다. 리스트에서 스태쉬 이름을 확인할 수 있습니다.

ex) stash@{0}: ….

스태쉬 목록
$ git stash list

스태쉬의 적용은 apply 명령어를 통해 적용할 수 있습니다. 스태쉬를 생성한 브랜치 뿐만 아니라 다른 브랜치에도 적용가능합니다.

스태쉬 목록
$ git switch issue2
$ git stash apply stash@{0}

옵션 없이 적용하면 스테이징 상태에 있던 파일도 추적 상태로 적용되지만, --index 옵션을 준다면 스테이징 상태에 있던 파일은 스테이징 상태 그대로 적용됩니다.

drop 명령을 사용하면 스태쉬를 스택에서 삭제하며, pop 명령을 사용하면 스태쉬를 적용하고 스택에서 삭제합니다.

스태쉬 삭제
$ git stash drop stash@{0}

-m –keep-index

원격 브랜치의 생성과 삭제

원격 브랜치를 생성하려면 로컬 브랜치가 존재해야하며, push 명령어를 사용하여 생성할 수 있습니다.

origin/issue1 원격 브랜치 생성
$ git push origin issue1

원격 브랜치를 가져오려면 switch 또는 checkout 명령어에 --track 옵션을 붙입니다.

origin/issue1 원격 브랜치 가져오기
$ git switch origin/issue1

또는 

$ git checkout origin/issue1

원격 브랜치를 삭제하려면 push 명령어에 --delete 옵션을 붙입니다.

origin/issue1 원격 브랜치 삭제
$ git push --delete origin issue1

원격 저장소 동기화

원격 저장소를 동기화 하려면 fetch 명령어를 사용합니다.

origin 원격 저장소 동기화
$ git fetch origin

pull 명령어의 경우, fetch 작업과 merge 작업을 수행합니다. fetch를 하면 원격 저장소로부터 최신이력을 가져와 이름없는 브랜치에 반영합니다. 이 브랜치는 FETCH_HEAD라는 이름으로 참조할 수 있습니다. 메인 브랜치를 병합하고 싶다면 브랜치를 merge하거나 pull을 수행하면 됩니다.