Junsik's Blog always work in progress

실전 Git 레시피

Git은 어렵습니다!

저희 팀은 Git으로 소스를 관리하고, GitHub로 코드 리뷰를 진행하고 있습니다. 또한, 저희 상황에 맞춘 변형된 Git Flow로 브랜치 관리를 하고 있죠.

하지만, 개발을 하다 보면 종종 꼬이거나(?) git이 예상했던 것과 다르게 동작할 때가 많습니다.

이 글에선 저희가 현재 사용하고 있는 환경에 관해 간략히 설명하고, 자주 마주치게 되는 상황에서 사용할 수 있는 git 명령어에 대해 알아보려고 합니다.

레시피 성격인 만큼 심도있게 설명하거나 모든 옵션을 언급하진 않으려 합니다.

로컬 환경 구축

upstream은 메인 repository입니다. origin은 메인 repository를 자기 계정으로 fork한 repository이죠. (fork는 GitHub의 fork 버튼을 사용해 간단히 실행할 수 있습니다.)

이제 fork된 repository를 로컬로 clone해봅시다. (개인 GitHub 페이지에서 URL을 복사할 수 있습니다.)

  $ git clone https://www.github.com/junsik-shim/some-project.git
  

some-project라는 폴더가 생성되고 프로젝트가 잘 받아졌을 겁니다. 해당 폴더로 들어가서 git remote를 실행해보면 제 fork가 origin으로 이미 설정되어 있음을 볼 수 있습니다.

이제 메인 repository를 upstream이란 이름으로 설정해야 합니다. (프로젝트 깃헙 페이지에서 URL을 복사할 수 있습니다.)

  $ git remote add upstream https://www.github.com/project-space/some-project.git
  

다시 한번 git remote를 실행해보면, 아래와 같이 설정되었음을 알 수 있습니다.

  $ git remote
  origin
  upstream
  

이제 환경 구축은 끝났습니다!

기본 루틴

이슈가 발생했을 때, 이 것을 처리하기 위해 pull 받고, 소스를 수정하고, 커밋하고, push 해서, PR(Pull Request)을 날리는 데 까지 필요한 과정을 보겠습니다.

upstream에서 최신 소스 받기

merge conflict를 피하기 위해서라도 로컬의 소스를 늘 최신으로 갖춰야겠죠. 보통 git pull 보단 git pull –rebase를 사용하는 게 히스토리 관리에 좋습니다.

  $ git pull --rebase upstream develop
  

새로운 브랜치 생성하기

두레이 이슈 번호를 가지고 새로운 브랜치를 생성합니다.

이슈 번호가 ‘qa/123’일 경우, 아래와 같이 실행합니다.

  $ git checkout -b qa/123
  Switched to a new branch 'qa/123'
  

수정한 파일 추가하고 커밋하기

수정한 파일 추가하는 건 쉘에선 너무 번거로우니, IDE나 Git Client에서 하는 것을 권해드립니다.

커밋을 fork된 repository로 올리기

  $ git push origin qa/123
  To https://www.github.com/junsik-shim/some-project.git
   * [new branch]       qa/123 -> qa/123
  

메인 repository로 Pull Request 날리기

GitHub의 본인의 fork repository에 가서 Compare & Pull Request 버튼을 눌러 코드 리뷰를 위한 PR을 날립니다.

발생할 수 있는 다양한 상황들

머리를 쥐어뜯게 만드는 상황들에 대한 해결법입니다. (물론 유일한 해결법은 아닙니다.)

소스 수정을 한참 하다보니 새로운 브랜치가 아닌 develop에서 작업을 하고 있었다!

아직 커밋을 하지 않은 상태이므로 그냥 새로운 브랜치를 만들면 수정하고 있는 사항들도 같이 옮겨갑니다.

  $ git checkout -b new-branch
  

소스 수정을 하고 커밋도 했는데, 새로운 브랜치가 아닌 develop에서 작업을 하고 있었다!

이런 경우에는, 현재 상태로 새로운 브랜치를 만들고, develop을 커밋하기 전의 상태로 되돌리면 됩니다.

  $ git checkout -b new-branch
  $ git checkout develop
  $ git reset --hard HEAD~1
  

만약 커밋이 여러개였다면 1이 아닌 그에 맞는 숫자를 넣어주면 되겠죠.

새로운 브랜치를 만들어야 하는데, develop에 아직 코드 리뷰를 통과하지 못한 커밋이 포함되어 있다!

기존 작업 때 새로운 브랜치에서 작업을 하지 않고 develop에 바로 커밋하여 PR을 날렸을 경우 발생할 수 있는 상황입니다. 해당 이슈의 코드 리뷰가 끝나지 않은 경우, 다른 이슈를 작업하기 위해 새로운 브랜치를 만들 때도 해당 커밋이 따라다니게 되는 문제입니다.

이런 경우에는, 해당 커밋이 생성되기 전의 상태로 새로운 브랜치를 만들면 됩니다.

  $ git checkout -b new-branch HEAD~1
  

마찬가지로 1 대신 원하는 숫자를 넣어주시면 됩니다.

뭔가 실행했더니 이상해져버렸다!

pull을 잘못 받거나 revert를 잘못 했거나.. 아니면 뭘 했는지도 모르겠는데 뭔가 이상해졌을 때에는 reset을 해주면 됩니다.

  $ git reflog
  544ebb9 HEAD@{0}: pull upstream monkey3: Fast-forward
  e94fc27 HEAD@{1}: checkout: moving from monkey3 to test
  e94fc27 HEAD@{2}: checkout: moving from analyticsqa/246 to monkey3
  f19873 HEAD@{3}: commit: 차트 로딩 추가 (analyticsqa/246)
  e94fc27 HEAD@{4}: checkout: moving from monkey3 to analyticsqa/246
  

첫번째 있는 pull을 취소하고 싶다면, 그 하단에 있는 HEAD@{1}로 이동하면 됩니다.

  $ git reset --hard HEAD@{1}
  

이것 또한 1대신 원하는 위치를 넣으면 됩니다.

메인 repository에 Push를 하고 나니 뭔가 이상해진 걸 깨달았다!

이미 public으로 나간 커밋은 reset을 하면 안 됩니다. 이런 경우에는 해당 커밋을 revert 하고 다시 push를 해야 합니다.

먼저 git log를 하여 revert 하고 싶은 커밋의 해시(예: 028c6298eb025ff8e5ccfbba399501a7e8e50af8)를 복사한 후, 다음과 같이 실행합니다.

  $ git revert 028c6298eb025ff8e5ccfbba399501a7e8e50af8
  

그러면 커밋 메시지를 수정할 수 있는 창이 뜨고, 종료하면 revert가 수행됩니다.

merge 커밋을 revert 하고 싶다!

merge 커밋을 revert 하기 위해선 -m 플래그를 추가해줘야 합니다. -m 뒤에는 원하는 부모의 번호를 넣어줘야 하는데.. 보통 1을 넣어주시면 됩니다. (브랜치 merge 순서에 따라 달라집니다.)

  $ git revert -m 1 028c6298eb025ff8e5ccfbba399501a7e8e50af8
  

꼬였던 로컬 브랜치를 다 정리했고, origin에 있는 같은 브랜치를 이것으로 덮어씌우고 싶다!

강제로 덮어씌우고 싶을 땐, -f를 붙여주면 됩니다. (메인 repository에는 절대로 하면 안 됩니다. 이미 못하게 막혀있겠지만요..)

  $ git push -f origin some-branch
  

로컬에만 있어야 하는 파일이 이미 push까지 되어버렸다!

IDE 설정 파일과 같이 로컬에만 존재해야 하는 파일이 메인 repository까지 실수로 들어갔을 때가 있습니다. 이럴 때는 git에서는 지우면서 로컬에 있는 파일은 지우지 않아야 합니다.

  $ git rm --cached some-filename
  

이미 커밋을 했는데 커밋 메세지를 수정하고 싶다!

커밋 메세지를 수정하고 싶을 때는 amend를 사용하면 됩니다.

  $ git commit --amend
  

엄밀히 말하면 기존의 커밋을 수정하는 것이 아니고 새로운 커밋을 만들어냅니다. 만약 이미 해당 커밋이 push가 되어 있는 상황이라면 다른 사람들의 히스토리를 꼬이게 만들 수 있으므로 되도록 지양해야 합니다.

브랜치 이름을 바꾸고 싶다!

브랜치 이름을 바꾸고 싶을 땐, 아래와 같이 실행하면 됩니다.

  $ git branch -m old-name new-name
  

비슷한 여러 커밋을 정리하고 싶다!

같은 주제로 작업한 커밋이 여러개라면 깔끔한 히스토리 관리를 위해 커밋들을 squash하는 게 바람직합니다.

  $ git log --oneline
  971e62d a 수정
  fc8812c a 수정
  a1b2ae7 a 수정
  16cfc94 Merge pull request #249 from changuk-lee/renewal_monkey3
  3d19554 Merge remote-tracking branch 'origin/renewal_monkey3' into renewal_monkey3
  ...
  

3개의 ‘a 수정’을 하나로 묶어봅시다.

  $ git rebase -i HEAD~3
  

이제 텍스트 에디터가 실행되고, 각 커밋에 대해 원하는 작업을 할 수 있습니다.

  pick 971e62d a 수정
  pick fc8812c a 수정
  pick a1b2ae7 a 수정
  ...
  

두번째와 세번째 라인의 pick을 s(squash)로 바꿔봅시다.

  pick 971e62d a 수정
  s fc8812c a 수정
  s a1b2ae7 a 수정
  ...
  

이제 저장하고 종료하고 나면 커밋 메세지를 수정하는 화면이 나옵니다.

  # This is a combination of 3 commits.
  # The first commit's message is:
  a 수정

  # This is the 2nd commit message:

  a 수정

  # This is the 3rd commit message:

  a 수정
  

원하는 커밋 메세지를 넣은 후 저장/종료 후, git log를 해보시면 3개의 커밋이 하나의 커밋으로 합쳐진 것을 볼 수 있습니다.

Squash하고 싶은데 순서가 얽혀있다!

커밋 순서가 아래와 같은 경우에, 두 개의 ‘a 수정’ 커밋을 묶고 싶다면 어떻게 해야 할까요.

  $ git log --oneline
  ae5ba65 a 수정
  3f30dc1 b 수정
  b341e53 a 수정
  ...
  

일반 squash할 때와 똑같지만, 커밋의 순서를 바꿔주면 됩니다.

  pick b341e53 a 수정
  pick 3f30dc1 b 수정
  pick ae5ba65 a 수정
  

에서,

  pick b341e53 a 수정
  s ae5ba65 a 수정
  pick 3f30dc1 b 수정
  

이렇게 바꿔주면 됩니다.