Intro
앞서 [Git] 왜 Git을 사용할까?를 보고 왔다면 왜 Git을 사용해야하는지 이해했을 것입니다.
그리고 [Git] Git-Workflow, git 흐름을 살펴보자! 로 Git의 역할까지 파악하고,
[Git] Github 설치, 기본 세팅 및 간단 사용법로 기본적인 사용법까지 익혔다면?
Git 기본적인 사용에 있어서 문제될 것이 없을 것입니다.
그런데... Git을 사용하다가 실수를 저질러 다음과 같은 예기치 못한 상황이 발생한다면?
- 뭔가 단단히 잘못됐는데, 다 뒤엎고 예전으로 돌리고 싶어!
- 방금 커밋했는데 하나 깜빡한 걸 발견했어!
- 커밋 메세지를 잘못 썼어!
- 커밋 하나로 뭉칠레!
- 다른 브랜치에 커밋해야 하는 걸 실수로 master에 커밋해 버렸어!
- 실수로 이상한 브랜치에 커밋을 해버렸어!
- diff를 실행했는데 아무 것도 안 보이잖아?!
- 다섯 커밋 전에 한 커밋을 되돌려야 하잖아!
- 파일을 수정한 걸 되돌려야 하잖아!
- 아 몰라 됐어, 다 포기할레
실수를 되돌리거나 해결하는 방법을 찾는 건 너무너무 복잡합니다. 또한 Git의 공식 문서에서 문제에 대한 해결책을 찾으려면, 이미 그 해결책의 이름을 알고 있지 않고서는 불가능합니다. 마치 닭이 먼저냐 달걀이 먼저냐의 문제처럼...
최대한 구글링을 통해 찾아보는 것을 지향하되, 이 글에도 다양한 문제상황에 대한 해결책을 적어두었으니 도움될 것입니다!
예기치 못한 상황 및 해결책
1) push 전 commit에 추가로 수정된 파일 제외하기
상황: push 전에 git에 포함되지 않아야 하는 파일을 발견하여 .gitignore 파일에 적었으나 이미 commit 한 경우에 이를 그만 트레킹하도록 설정해주는 명령어이다.
git rm -r --cached file.folder
2) push 전 commit에 추가로 수정된 파일 포함하기
주의: 이 기능은 반드시 아직 로컬에만 있는 커밋에 사용해야 한다. 이미 공개 브랜치에 push된 커밋은 절대로 수정하면 안 된다!
상황: 이런, 방금 커밋했는데 하나 깜빡한 걸 발견했어!
# 새로 바뀐 파일들을 add 하고
git add . # 또는 각각의 파일들을 add
git commit --amend --no-edit
# 마지막 커밋에 바뀐 파일이 등록된다
# 주의: 절대로 원격 저장소에 push 된 커밋을 고치지 말 것
커밋을 하고나서 테스트/린터를 돌렸더니... 아 이런, 등호 뒤에 띄어쓰기를 깜빡했네, 같은 상황에서 내가 자주 사용하는 기능이다.
새로 커밋을 하고 rebase -i를 이용해서 두 커밋을 squash하는 것도 가능하지만, 이 방법이 만 배는 더 빠르다.
3) commit 메시지 수정: 이런, 커밋 메세지를 잘못 썼어!
# 에디터가 켜지고 커밋 메세지를 수정할 수 있다
git commit --amend
# 해당 메시지로 커밋 메세지를 수정할 수 있다.
git commit --amend -m "Phew! A better commit message this time"
커밋 메세지 포맷을 정확히 지켜야 한다면, 자주 쓰게 될 기능이다.
4) commit 메시지 수정: 이~~전의 커밋 메시지 수정
1. rebase 명령어
git rebase -i <commit-hash>
2. 합칠 commit log 선택
pick 8ffdf40 test1
pick 41626a1 test1
pick afd9381 test1
pick def890d test1
...
'1. rebase 명령어'를 입력하면 위와 같이 편집 창이 나오는데요. 위에서 수정할 commit의 앞에 존재하는 pick을 r(reward)로 변경하고 저장합니다. 이후에 나오는 창에서 원하는 commit message로 바꾸어 저장하면 새로운 commit 메시지로 변경된 것을 확인 가능합니다.
5) commit 메시지 수정: 커밋 하나로 뭉칠레!
1. rebase 명령어
git rebase -i HEAD~6 // 6 = 합치고자 하는 커밋의 수
위 명령어를 통해 rebase 하고자 하는 커밋 수를 입력합니다.
2. 합칠 commit log 선택
pick 8ffdf40 test1
pick 41626a1 test1
pick afd9381 test1
pick def890d test1
...
'1. rebase' 명령어를 입력하면 위와 같이 편집 창이 나오는데요, 위에서 합칠 commit의 가장 앞에 존재하는 pick을 s(squash)로 변경하고 저장합니다.
3. commit message 수정
# 커밋 4개가 섞인 결과 입니다.
# 1번째 커밋메시지 입니다:
test1
# 2번째 커밋메시지 입니다:
test2
# 3번째 커밋메시지 입니다:
test3
# 4번째 커밋메시지 입니다:
test4
위와 같이 작성된 메시지를 다음과 같이 수정합니다.
# 커밋 4개가 섞인 결과 입니다.
rebase test
# 1번째 커밋메시지 입니다:
#test1
# 2번째 커밋메시지 입니다:
#test2
# 3번째 커밋메시지 입니다:
#test3
# 4번째 커밋메시지 입니다:
#test4
4. 강제 git push
git push origin -f
6) 다른 브랜치로 commit 옮기기: 이런, 다른 브랜치에 커밋해야 하는 걸 실수로 master에 커밋해 버렸어!
주의: 이 기능은 반드시 아직 로컬에만 있는 커밋에 사용해야 한다. 이미 공개 브랜치에 push된 커밋은 절대로 수정하면 안 된다!
# 현재 master의 상태로 새로운 브랜치를 만든다
git branch some-new-branch-name
# master 브랜치의 마지막 커밋을 제거한다
git reset HEAD~ --hard
git checkout some-new-branch-name
# 이 브랜치에는 그 커밋이 남아있다 :)
만약 여러 개의 커밋을 했다면, HEAD~(하나의 커밋) 대신 git reset HEAD@{돌아갈-커밋-수}를 사용하면 된다.
7) 다른 브랜치로 commit 옮기기: 이런, 실수로 이상한 브랜치에 커밋을 해버렸어!
이건 stash와 cherry-pick을 이용하는 두가지 방법이 있다.
# 마지막 커밋을 취소하되, 변경된 사항은 남겨둔다
git reset HEAD~ --soft
git stash
# 올바른 브랜치로 이동
git checkout name-of-the-correct-branch
git stash pop
git add . # 또는 각각의 파일들을 add
git commit -m "commit message";
# save all tracked files
git stash
git stash save "your message"
# list your stashes
git stash list
# retrieve stash and delete
git stash apply stash@{1}
git stash drop stash@{1}
# ... or in 1 command
git stash pop stash@{1}
이 상황에서 cherry-pick을 사용하는 방법을 많은 사람들이 추천해주었다. 원하는 방법을 골라 사용하길!
git checkout name-of-the-correct-branch
# master의 마지막 커밋을 선택
git cherry-pick master
# master에서 해당 커밋을 제거
git checkout master
git reset HEAD~ --hard
8) add한 파일을 포함한 diff 확인: 이런, diff를 실행했는데 아무 것도 안 보이잖아?!
분명히 뭔가를 바꿨는데, diff의 결과가 아무 것도 나타나지 않는다면,
아마 add를 실행해서 파일을 staging 상태로 만들었을 가능성이 높다. 이 경우에는 특별한 옵션을 주어야 한다.
git diff --staged
9) revert를 사용하여 commit 되돌리기: 이런, 다섯 커밋 전에 한 커밋을 되돌려야 하잖아!
# 되돌려야 할 커밋을 찾는다
git log
# 방향키로 예전 커밋을 살펴보고
# 원하는 커밋을 찾으면 해당 커밋의 hash를 기억한다
git revert [saved hash]
# git이 해당 커밋을 되돌리는 새로운 커밋을 생성할 것이다
# 에디터 창이 나타나면, 새로 커밋 메세지를 입력하거나,
# 그냥 저장하고 종료한다
10) checkout으로 수정한 파일 되돌리기: 이런, 파일을 수정한 걸 되돌려야 하잖아!
# 해당 파일이 수정되기 전의 커밋 해시를 찾는다
git log
# 방향키로 예전 커밋을 살펴보고
# 커밋을 찾으면, 해시를 기록
git checkout [saved hash] -- path/to/file
# 예전 버전 파일로 바뀌어 있을 것이다
git commit -m "Wow, you don't have to copy-paste to undo"
이 방법을 알고 모르고 차이는 엄청 크다. 근데 솔직히 checkout --은 봐도 봐도 이상하다. 진짜 파일 하나 되돌리는데 이런 요상한 명령어를 써야할까요
11) 모든 브랜치의 로그 확인(reflog) 및 타임머신(HEAD@)
상황: 이런, 뭔가 단단히 잘못됐는데, 다 뒤엎고 예전으로 돌리고 싶어!
# git의 모든 브랜치에서 있었던 지금까지의 모든 기록을 볼 수 있다.
git reflog
# 각각 HEAD@{index} 형태로 index를 가지고 있으니, 잘못되기 전에 해당하는 index를 찾고 돌아갈 수 있다.
git reset HEAD@{index}
# 삭제한 브렌치의 커밋이더라도 되돌릴 수 있다.
git branch <branch-name> <deleted-branch-commit-hash>
이 기능은 실수로 지운 파일을 되돌리거나, 뭔가 잘못 수정한 걸 되돌리거나, 실수로 머지한 걸 되돌리거나... 그냥 잘 작동했던 때로 돌아가고 싶을 때 사용하면 된다.
12) 이전 commit된 상태로 파일 되돌리기
상황: 특정 파일을 이전에 commit 된 상태로 되돌리고 싶다면? (실수로 삭제한 파일도 되돌릴 수 있다!)
# 최신의 commit 된 상태로 되돌리기
git restore connect.js
# 특정 commit 상태로 되돌리기
git restore --source <commit-hash> <filename>
13) 최신에 push된 상태로 되돌리기
상황: 최신에 push 된 상태로 되돌리고 싶다면?
대부분 '해당 git 폴더는 지운 후 다시 clone하는 형태'로 진행했을 것이다. 근데.. 이건 git에서 바라는(?) 방식이 아니다...
원격 레포지토리 버전으로 초기화하고 싶다면, 아래 방법을 쓰면 된다.
(단, 이건 되돌릴 수 없는 명령어니 주의해야 한다!)
# origin에서 최신 상태를 받아온다
git fetch origin
git checkout master
git reset --hard origin/master
# 추적되고 있지 않은 파일/폴더를 모두 삭제한다
git clean -d --force
# 초기화하고 싶은 각 branch에 대해서 checkout/reset/clean을 반복
14) 트레킹하고 있지 않은 파일을 모두 삭제하기
상황: 트레킹하고 있지 않는 파일/폴더를 삭제하고 싶다면?
# 트레킹하고 있지 않은 파일 삭제
git clean -f
# 트레킹하고 있지 않은 폴더 삭제
git clean -d
# 트레킹하고 있지 않은 파일 및 폴더 삭제
git clean -df
참고
- Dangit, Git!?!: https://dangitgit.com/ko
- Oh Shit! Git?!: https://news.hada.io/topic?id=6846
- git cheat sheet advansed: https://dev.to/maxpou/git-cheat-sheet-advanced-3a17
- Turn around your Git mistakes in 17 ways: https://dev.to/smitterhane/turn-around-your-git-mistakes-in-17-ways-2mn1
'Study: DeveloperTools(DevTool) > DevTool: Git' 카테고리의 다른 글
[Git] git submodule 서브모듈 활용하기: 다운받기(clone), 변경하기(update), 추가하기(add), 삭제하기(deinit) + 특정 폴더를 submodule로 추출하는 방법 (1) | 2023.06.04 |
---|---|
[Git] Git branch 전략 (Git flow보다 중요한 것은.. Code Review) (0) | 2022.09.16 |
[Git] reset과 revert 알고 사용하기 (0) | 2022.07.20 |
[Git] git tag & 버전 넘버링 규칙: git은 버전관리도구이다!! (0) | 2022.07.06 |
[Git] Commit 메시지 가이드라인 (0) | 2022.07.04 |