인프런 "모든 개발자의 실무를 위한 필수 기본기 클래스" 강의를 듣고 정리한 내용입니다.
상황별 Git 다루기
🟦 [log & reflog] 이전 commit 내역들과 변경사항 확인하기
git log
- commit 내역을 확인하는 가장 일반적인 방법
# 로그 간략하게 보기
$ git --oneline
- commit의 개수가 많을 경우 -n을 활용하여 최근 n개 commit만 볼 수 있다.
$ git log -n 10
- 시각적인 그래프 형태로 merge 된 것까지의 git commit history를 보려면 다음과 같이 입력한다.
$ git log --oneline --decorate --graph
git show
- 가장 최신 commit 정보 확인
- 특정 commit 정보 확인을 위해서는 git show {commit 해시}를 붙여준다.
$ git show c008c4785eeb14a395b4aa6cf9fa3b9e5896f5a4
- HEAD 포인터를 활용한 git show
-
HEAD는 커밋 내역에서 현재 commit(보통 가장 최신 커밋)을 가리키는 심볼릭 링크(포인터)다. 보통 명령어에 commit ID 대신 HEAD 포인터를 많이 활용한다. HEAD의 이전 commit들을 확인하고 싶을 땐 HEAD^ 혹은 HEAD~으로 포인팅이 가능하다. 만약 HEAD로부터 3개 전의 Commit에 접근하고 싶다면 HEAD^^^ 혹은 HEAD~3으로 표현할 수 있다.
-
# 현재로부터 두 번째 전에 있는 커밋을 기준으로 어떤 변경 사항들이 있는지 확인
$ git show HEAD^^
git reflog
$ git reset --hard HEAD^^
-
commit의 변경사항을 초기화한다. hard는 아예 commit을 삭제해 버리는 역할을 하는 강력한 명령어이다.
-
git log로 확인해 보면 전전 커밋 이후 변경 사항들은 삭제가 되어 있다.
-
이런 경우, 복구시킬 수 있는 방법이 $ git reflog 이다.
-
- 로컬 리포지토리에서 commit이 사라졌더라도 reflog를 통해 모든 변경 사항들을 확인할 수 있기 때문에, git reset 한 명령 내역까지 모두 확인할 수 있다. 따라서 git reset --hard 한 명령을 취소하고 싶으면 (명령 이전으로 돌아가고 싶으면) git reflog 에서 해당 명령 직전의 commit 해시 값을 참조하여 git reset --hard 하면 된다.
$ git reset c7591af --hard
🟦 [restore & reset] 변경사항, commit 초기화하기
git reset
- commit 시점을 초기화할 때 사용한다.
$ git reset --hard {커밋 ID}
-
특정 commit 시점으로 돌아갈 때, 해당 커밋 이후 만들어진 모든 작업물을 삭제한다.
-
현재 작업한 파일들을 모두 날리고 이전 커밋 상태로 완전히 돌아가고 싶을 때 사용하지만, 기존에 작성하던 변경사항들도 전부 날아가기 때문에 주의해야 한다.
$ git reset --mixed {커밋 ID}
-
특정 commit 시점으로 돌아갈 때, 해당 commit 이후 모든 작업물은 wokrspace 공간에 unstaged 상태로 남게 된다.
-
git reset의 기본 옵션으로, git reset 만 실행해도 같다.
$ git reset --soft {커밋 ID}
- 특정 커밋 시점으로 돌아갈 때, 해당 커밋 이후 모든 작업물은 index 공간에 staged 상태로 남게 된다.
git restore
$ git restore {파일 또는 파일 경로}
-
특정 파일의 변경사항을 제거하고 HEAD 기준으로 되돌리고 싶을 때 사용한다.
-
아직 stage(index)에 올라가지 않은 workspace 공간에 있는 변경 사항 파일을 되돌릴 때 사용한다.
-
git restore는 git reset --hard HEAD와 비슷한 결과를 낸다. 다만 restore는 새 파일의 변경사항을 되돌리지 않지만, reset은 새 파일의 변경사항도 되돌린다.
🟦 [revert] 이전 commit 변경사항 되돌리기
git revert {commit ID}
-
중간에 있는 커밋의 변경사항만 되돌리기 위해선 git reset은 한계가 있다.
-
git revert를 사용하면 기존 커밋들을 지우지 않고, 현재 커밋 위로 이전 커밋 내용을 다시 되돌리는 커밋을 만든다.
-
이전 커밋을 되돌려야 하는 상황일 때 rebase --interactive를 사용하여 커밋 내역을 조작할 수 있다. 하지만, rebase --interactive를 할 경우 remote repository에 push할때, force push를 해야 해서 불편하고 안전하지 않을 수 있는 문제 때문에 rebase 보다는 revert를 권장한다.
🟦 [stash] 변경 사항을 commit 하기보다는 임시 저장하고 싶은 경우
git stash
-
수정 내용을 임시 저장하는 명령어
-
다른 브랜치 개발 필요하여 현재 브랜치에서 다른 브랜치 간 전환 시에 기존 작업 내역들을 임시 저장을 해야 할 경우 사용된다. git stash 명령을 쓰면 현재 변경사항을 별도의 스택 공간에 빼두게 된다.
# 임시 저장시 메시지 남기기
$ git stash -m "OOO 변경 사항..."
# 임시 저장한 내역 git stash list로 확인
$ git stash list
# 임시저장한 것들을 스택에서 pop 하여 꺼내온다.
$ git stash pop
$ git stash apply
-
git stash pop과 비슷한 명령어로 git stash apply 가 있다. 이 둘의 차이점은 스태시 한 내역을 저장하는 스택 공간에서 내역을 제거하는지 여부이다. git stash pop의 경우 스택에서 작업 내역을 제거한다. 따라서 git stash list로 해당 작업이 빠져있는 것을 확인할 수 있다.
-
반면, git stash apply의 경우 스택에서 작업 내역을 빼지 않는다. 따라서 git stash list로 보면 여전히 스택에 남아있기 때문에 다른 곳에서 넣어둔 작업 내역을 재사용할 수 있다. (여러 곳에서 apply 가능하다는 의미)
🟦 [ammend commit & rebase] 이전에 쌓인 Commit들 변경하기
git commit --ammend
-
현재 작업 중인 commit(HEAD) 간단하게 수정할 때 사용한다. 기존 commit 메시지를 수정하여 덮어 씌운다.
-
commit을 한 후 추가적인 변경사항이 생겼거나 commit 메시지를 변경하고 싶을 때 많이 사용한다.
-
만약 commit 메시지만 수정하고 싶다면 변경사항 없이 바로 $git commit --amend를 사용한다.
-
commit 메시지 수정이 필요하지 않은 경우 --no-edit 옵션을 붙여 사용한다.
$ git commit --amend --no-edit
- 커밋이 쌓이는 것이 아닌 해당 커밋이 변경되어 있는 상태이기 때문에 $git commit --amend를 하고 origin 브랜치에 push를 할 경우, push 되지 않는다. 이런 상황에서는 다음과 같이 force 옵션을 주어 local에서 remote로 강제로 변경사항을 덮어 씌우도록 해준다.
git push origin $git push origin main -f
git rebase --interactive {Commit ID}
- rebase는 브랜치 병합 과정에서 주로 사용한다. 동시에 과거 커밋 히스토리를 변경할 수 있도록 --interactive (-i) 옵션을 이용하여 변경할 수도 있다. 커밋 히스토리 수정 범위는 현재 최신 커밋부터 {커밋 ID} 바로 위 커밋까지 적용된다.
$ git rebase --interactive 1dee32c # 혹은 HEAD^^ , HEAD~2 로도 표현할 수 있다.
-
입력하면 VIM 에디터를 확인할 수 있는데 대표적인 커맨드는 아래와 같다.
-
pick : 변경사항 없이 커밋으로 둔다. (default)
-
edit : 해당 커밋 내용을 변경할 것이며 커밋 메시지도 변경할 수 있게 한다.
-
drop : 해당 커밋을 제거한다.
-
-
코드 변경 마친 후 git commit --amend를 입력하면, 현재 최신 커밋(HEAD)에 덮어 씌우는 작업을 하게 된다.
-
최종 commit을 마친 후 모든 변경 사항 적용했다면 git rebase --continue로 다음 작업 대상으로 넘어가면 된다.
-
만약 해당 커밋의 변경 사항을 주지 않고 다음으로 넘어가고 싶다면 git rebase --skip을 사용한다.
-
만약 rebase 하는 과정에서 전체를 취소하고 싶을 수 있다. 이때 git rebase --abort를 사용한다.
-
중간에 있는 커밋을 변경하는 과정에서 상위 커밋과의 변경 사항이 충돌할 수도 있다. 그럴 때는 충돌 난 부분을 수정한 후 git add로 index에 충돌을 수정한 부분을 올린 후 git rebase --continue를 사용하면 된다.
🟦 [squash & rebase merge] 브랜치를 merge 할 때 merge commit 남기지 않기
git merge {브랜치 이름}
- 가장 기본적인 merge 방식으로, main 브랜치에 추가 commit이 없는 상태라면 feature 브랜치를 git merge 시 feature 브랜치의 모든 commit이 그대로 main 브랜치로 들어가게 되는데, 이를 fast-forward 방식이라고 한다. 이 경우 머지 커밋이 남지 않는다.
- 만약 main 브랜치에 새로운 커밋이 생겼을 경우, merge 하게 되면 머지 커밋이 생기게 된다.
-
merge conflict 가 발생하는 상황
-
한 파일의 같은 라인을 고쳤을 때
-
한 브랜치에서는 파일을 삭제하고 한 브랜치에서는 파일을 변경할 때
-
git merge {브랜치 이름} --squash
- 머지 커밋을 남기지 않으면서, 해당 브랜치에서 작업한 모든 내용을 하나의 commit으로 묶어버리는 방법이다.
git rebase {브랜치 이름}
-
머지 커밋을 남기지 않으면서 머지되는 브랜치의 모든 commit 내역을 그대로 가져오는 방법
-
git rebase는 별다른 커밋을 생성하지 않고 브랜치의 커밋 구조를 변경한다고 보면 된다. 코드를 보는 입장에서는 깔끔할 수 있지만, 브랜치의 병합 히스토리가 명시적으로 잘 남지 않아 히스토리를 추적할 때 불편할 수 있다.
🟦 [cherry-pick] 다른 브랜치에 있는 commit을 내 브랜치로 가져오기
git cherry-pick {commit ID}
- 다른 브랜치에 있는 특정 커밋을 본인 브랜치로 가져온다.
실전 충돌(Conflict) 다루기
❗ Merge 과정에서 충돌이 났다면?!
HEAD를 이전 Commit으로 변경한다. -d 옵션은 detach 뺀다는 의미이다.
git switch -d HEAD^
충돌이 발생하면 conflict marker를 통해 구분이 가능하다.
-
<<<<<<< HEAD와 ======= 사이에 있는 변경사항이 merge를 진행한 브랜치의 커밋 변경사항이다.
-
=======와 >>>>>>>{커밋 해시} 사이에 있는 변경사항은 merge할 타겟 브랜치의 커밋 변경사항이다.
-
충돌을 해결하기 위해서는 conflict marker를 지워주고 어떤 변경사항을 채택할지 결정하여 수정한다.
❗ 하나의 브랜치를 함께 사용하다가 충돌이 났다면?!
동시에 하나의 브랜치를 함께 사용하다 A가 remote repository에 먼저 commit을 올린 상황에서, B도 다른 commit을 만들어 remote repository에 git push를 해야 하는 경우, 바로 push 할 수 없기 때문에, git pull을 먼저 하는데 이런 경우에 merge conflict가 발생한다.
merge commit이 생기지 않고 git pull 하기 위해서는 pull --rebase를 사용하면 된다.
git pull 할 때, rebase 옵션을 주어 commit의 순서를 일정하게 보장하여 git push를 바로 진행할 수 있다. rebase 옵션을 통해 pull을 하면 remote branch의 commit을 git pull 한 후, local branch의 commit을 그 위로 쌓는 것이다. (rebase)
$ git pull origin {브랜치명} --rebase
'Devlog > Git' 카테고리의 다른 글
[Git] 좋은 Commit 메시지 작성하기 (0) | 2022.09.06 |
---|---|
[Git] Git 개념과 기본적인 명령어 (0) | 2022.07.05 |