ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Git Branching의 가치
    주제탐구 2019. 1. 13. 16:47

    git에 익숙하지 않으면 작업 내역을 커밋하는 데 애를 먹는 경우가 있다.


    동료 개발자들과 커밋을 작업 단위로 쪼개자고 합의를 본 상태다. 나 또한 1000줄짜리 거대한 커밋을 보는 건 원치 않기 때문이다.

    그런데 작업하다 보니 여기저기 손을 대면서 중복 코드도 없애고, 상속 구조도 바꾸고, 오타도 수정하고...

    여러 가지 작업을 하고 말았다. 이제 이 작업들을 나눠서 커밋해야 한다.


    git은 보니까 마지막 커밋만 --amend로 수정할 수 있던데... 잘못 커밋하면 되돌리지 못하는 것 같다.

    파일 하나하나를 신중히 나눠 커밋하다 보니 커밋 정리만 1시간을 하는 자신을 발견하게 되었다.


    남들도 이렇게 힘들게 작업하나 싶다. 다들 깔끔하게 커밋하던데 나만 힘든 것 같고 막...


    쓰앵님 우리 예서가 커밋 정리하다 우는데 어떡하죠?


    이렇게 공들여 작업하면 버전 관리가 짐처럼 느껴지게 된다.

    솔직히 프로그래밍하는 것도 어려운데 (소근) 커밋 한 번 할 때마다 힘을 쏟아야 한다니...


    그런 면에서 개발자를 위한 git 튜토리얼이라면, commit log diff만 알려주고 끝내면 안 된다고 생각한다.

    코드 짜면서 실수를 많이 하는 초심자일수록 merge branch rebase cherry-pick의 가치가 빛을 발한다.

    입사하고 나서도 몰랐던 요 Branching의 가치에 대해서 언급하고자 한다. 잃어버린 업무 시간을 추억하면서... 또르르...

    우리 로컬에서는 편하게 커밋해요

    코드 받아라 얍 (출처: https://xkcd.com/1296/)


    git 커밋하는 법에 대해서 검색하면 보통 이런 이미지를 볼 수 있을 것이다. 뭔가 git 커밋 메시지를 대충 쓰지 말라고 하는 것 같다.

    원격 저장소 작업 내역을 보아도 이렇게 커밋하는 사람은 없으니까.


    또 만약 회사에서 이슈 트래킹 도구를 쓴다면, 이슈 티켓 번호를 언급하는 것으로 커밋 메시지를 대체할 것이다.

    그러면 아마 커밋 메시지가 더 깔끔해 보일 것이다. 전부 비슷하게 생겼으니까.


    근데 이거 다 원격 저장소 얘기다.

    로컬 저장소와 원격 저장소는 다르다

    작업 내역을 원격 저장소로 전송할 때 보통 이런 명령어들을 쓴다.


    1
    2
    $ git push origin master
    $ git push upstream develop
    cs


    이는 'master'나 'develop'이라는 한 브랜치에 내 작업 내역을 push하는 명령어다.


    로컬에 'local/refactoring'이든, 'haaaaaaands'든, 'afjiofejiog'든... 어떤 브랜치를 만들어도 저 명령어로 푸시되지는 않는다.

    손이 미끄러져서 'git push origin afjiofejiog'을 치지 않는다면 말이다.


    원격 저장소와의 연동은 마치 바탕 화면에 공유 폴더를 하나 만드는 것과 같다.

    공유 폴더를 제외한 나머지 바탕 화면은 내 마음대로 관리해도 되는 것처럼, 로컬 저장소에서 브랜치를 관리하는 것은 각자의 몫이다.

    그러니 자신이 필요한 관심사에 따라서 'refactoring', 'typo', 'debug', ... 편한 방식대로 만들어서 효율적으로 사용하면 된다.

    내 컴퓨터 안 로컬 저장소에서 git flow를 따르는 등, 타인을 의식할 필요가 없는 것이다.

    (그렇다고 'AAAAAA' 같은 메시지로 커밋하면서 어질러 둔다면... 푸시하기 전에 좀 힘들겠지만...)


    그런데 git branch만 알고 있으면, 나중에 master나 develop에 어떻게 커밋을 옮겨올지 몰라 branch를 거의 안 쓰게 된다.

    그래서 커밋을 옮겨오는 방법을 살짝 소개만 해 보려고 한다.

    다른 브랜치의 커밋을 반영하는 merge

    가령 블로그 글을 쓰면서 git으로 버전 관리를 하는 상황을 가정해 보자.
    chapter1이라는 브랜치를 만들고, 한 문단씩 커밋하면서 글을 쓰는데 자꾸 이전 문단의 오타가 눈에 밟힌다.
    문단 추가하는 작업 내역이랑 오타 수정하는 작업 내역을 분리하고 싶어서, 오타 수정할 때마다 별도의 브랜치를 만들었다.

    칠지도...?

    아마 이런 모습이 될 것이다. 이제 오타 수정 내역을 chapter1에 합치면 될 것 같다.


    다른 브랜치의 수정 사항을 현재 브랜치에 반영하고 싶다면 merge를 사용할 수 있다.


    1
    $ git merge typo1 typo2 typo3
    cs


    커밋 메시지를 적절히 수정하고 커밋하면,

    와웅

    모든 커밋이 하나로 합쳐지는 것을 볼 수 있다.

    그런데 커밋 히스토리에 뭔가 절지동물 같은 것이 만들어졌다. master에는 저 상태로 커밋하고 푸시하면 안 될 것 같다...


    그래서 chapter1의 수정 내역을 master에 두 개의 커밋으로 깔끔하게 반영해 보려고 한다. "1장 추가"와 "1장 오타 수정"으로 :)

    커밋의 순서를 바꾸고 합치는 rebase

    우선 "1문단 추가"부터 "4문단 추가"까지, 4개의 커밋을 "1장 추가"로 합치려고 한다.
    git은 이전 커밋들의 순서를 바꾸거나, 특정 커밋을 빼거나, 커밋을 합칠 때 사용할 수 있는 rebase -i 명령을 제공한다.

    master가 "4문단 추가"(d82ddfb)를 가리키게 하고 커밋 4개를 합쳐보자.

    1
    2
    3
    $ git checkout master
    $ git reset --hard d82ddfb
    $ git rebase -i HEAD~4
    cs

    꺄 이게 뭐지?

    복잡해 보이는 화면이지만 주석이 반 이상을 차지하고 있다. 익숙한 커밋 메시지도 보인다.


    각 줄이 하나의 커밋을 나타낸다. 위아래로 복사&붙여넣기하면 커밋의 순서를 바꿀 수 있고, 버리고 싶은 커밋이 있다면 그 줄을 삭제하면 된다.

    아래 Commands를 통해 더 정교한 작업을 할 수 있는데, 우리가 찾는 건 해당 커밋을 이전 커밋과 합치는 squash다.


    첫 번째 커밋 아래 모든 커밋을 squash하고 에디터를 빠져나오면...

    git이 모든 커밋 메시지를 합치려고 할 것이다. 전부 지워주고 "1장 추가"라고만 쓴 뒤 다시 빠져나오면...


    깔끔한것...

    새로운 커밋이 생겼고, 4개 커밋의 수정 내용이 전부 반영된 것을 볼 수 있다.

    rebase -i에는 이것 말고도 예전 커밋을 수정하거나 쪼개는 유용한 기능들이 있는데, Pro Git에서 이를 잘 설명하고 있다.

    개별 커밋만 쏙 빼오기 위한 cherry-pick

    그나저나 "1장 오타 수정"(bb852aa)도 가져와야 하는데... merge를 사용하면 저 절지동물을 원격 저장소로 내보내게 된다.
    수정 내용만 쏙 가져오는 방법이 없을까?

    git cherry-pick을 통해 복잡한 구조를 격리시키고 개별 커밋만 쏙 빼올 수 있다.
    가져와 보면...

    1
    $ git cherry-pick bb852aa
    cs


    -m 옵션이 설정되지 않았다는 에러가 난다. 왜지?

    git에서 개별 커밋의 변경 내용은 부모 커밋에 상대적인 특성을 가지고 있다.
    "1장 오타 수정"은 부모 커밋을 여러 개 둔 merge 커밋이므로, 각각의 부모 커밋과 비교해 보면 변경된 내용이 다 다를 것이다.
    아마 "1문단 오타 수정"의 입장에서 보면 "1장 오타 수정"은 2·3·4문단을 추가해주는 커밋이고,
    "3문단 오타 수정"의 입장에서 보면 "1장 오타 수정"은 4문단을 추가해주는 커밋이 될 것이다.
    cherry-pick으로서는 어떤 변경 내용을 가져올지 알 수 없기 때문에 에러를 내는 것이다.

    "1장 추가"에는 "4문단 추가"의 변경 내용이 이미 반영되어 있으므로, "4문단 추가" 쪽을 부모 커밋으로 골라주면 될 것 같다.
    잠깐 chapter1으로 돌아가서 커밋 히스토리를 보면...

    1
    2
    $ git checkout chapter1
    $ git log
    cs

    Merge가 네 개


    "4문단 추가"가 Merge 열의 왼쪽에서 첫 번째에 있는 것을 확인할 수 있다. -m 옵션 뒤에 몇 번째인지를 숫자로 적어주면 될 것 같다.


    1
    2
    $ git checkout master
    $ git cherry-pick bb852aa -1
    cs


    깔-끔


    1
    $ git push origin master
    cs


    깃허브도 깔-끔


    깔끔하게 수정 내용만 가져올 수 있었다. 절지동물은 내 컴퓨터에만 두는 것으로 😊


    사실 cherry-pick은 한 개뿐 아니라 여러 개의 커밋을 원하는 순서대로 빼오는 데 사용할 수 있다.
    즉 하나의 브랜치 위에 있는 커밋만 cherry-pick하면 rebase -i와 비슷하게 쓰는 셈이 된다.

    실제로 cherry-pick을 사용하지 않고 rebase -i만으로도 위 작업을 할 수 있다.
    어느 쪽이든 기존 커밋과의 연결고리를 끊어버리므로 주의해서 사용할 필요가 있다.
    (약간 흑마법 느낌이 있는 것 같다...)

    한 방에 끝낼 수 있다

    유의할 점이 있다

    No newline at end of file

    이 모든 작업에서는 충돌이 발생할 가능성이 있다.
    커밋을 합치거나 앞뒤로 옮기는 작업에서 충돌이 발생하면, 작업의 단계마다 수동으로 충돌을 해결해 주어야 한다.
    그런 난감한 상황을 줄이기 위해서 꼭 지켜줘야 하는 것이 있다.

    줄 단위로 변경 사항을 관리하는 git에서는 파일 끝에 줄바꿈을 꼭! 해두는 게 좋다.
    줄바꿈은 줄 끝에 개행 문자('\n')를 붙여 이루어지기 때문에, 만약 커밋하고 나서 파일 끝에 줄바꿈을 하고 내용을 덧붙이면
    이전 내용의 마지막 줄까지 같이 수정하게 된다.

    이렇게 내용을 덧붙여 가는 식으로 작업한다면 개별 커밋의 순서를 뒤바꾸거나, 분기한 브랜치를 합칠 때 전부 충돌이 나게 된다.
    이걸 수동으로 수정하면서 merge rebase cherry-pick하다 보면... 참 힘들다...

    그러므로 Redundant Code라고 생각되어도 꼭 파일 끝에는 개행 문자를 붙여두자.

    이거 다 로컬 저장소 얘기다

    알다시피 원격 저장소에 푸시한 커밋은 rebase나 cherry-pick으로 순서를 조정할 수 없다.
    (그러려면 원격 저장소를 버려야 한다. 징긋)

    푸시하기 전에 실수한 게 없는지 확인하는 습관을 들여야겠다.

    관심사를 분리하는 것이 중요하다

    우리들 대다수는 두뇌 용량에 한계가 있어 깨끗하고 체계적인 소프트웨어보다 돌아가는 소프트웨어에 초점을 맞춘다. 전적으로 올바른 태도다. 관심사를 분리하는 작업은 프로그램만이 아니라 프로그래밍 활동에서도 마찬가지로 중요하다.

    - 로버트 C. 마틴, <클린 코드>


    작업을 하다 보면 알게 모르게 두 가지 일을 동시에 하면서 골머리를 앓는 경우가 있다.

    버전 관리에서는 "작업 내역을 저장해야지"와 "커밋 히스토리를 깔끔하게 만들어야지"라는 두 관심사가 섞이게 된다.


    동시에 성취하려 할 필요가 없이, 간단하게 하나씩만 집중하면 덜 머리 아프게 일할 수 있다고 생각한다.
    브랜치를 나누고 합치고 커밋을 떼오는 이런 명령어들이 분명 두 관심사의 분리에 도움을 줄 수 있을 것이다.


    그리고 이 글보다 Git Branching을 훨씬 더 잘 알려주는 인터랙티브 튜토리얼이 있으니 꼭 참고해 보셨으면 좋겠다. 🤗


    로컬에는 편하게 커밋하자... 저 타자 치는 고양이처럼...



Designed by Tistory.