우리가 늘 알고 있다고 생각하지만, 한번 더 확인하면 좋은 git 명령어 및 flow

2020-07-21 — Written by jslee
#git#command#git-flow

git을 사용하고 있지만 평소에 궁금했던 또는 사용하지 못했던 기능이 있었을까? 리마인드 한다는 마음으로 git에 대한 내용 용어 및 내용 정리. 순서는 무의미함

merge

$ git merge <branchname> # 현재 작업 중인 브랜치에 <branchname>의 브랜치를 끌어와 병합

git add

git에서 파일 기록을 추적하도록 추가

.gitignore

conflict

  • git이 해당 행이 어떤 의미를 지니고 있는지 알 수 없으므로 어떤 수정 사항을 선택할 것인지 개발자가 선택

    • 두 브랜치 내용 중에 하나를 선택
    • 두 수정 내역을 합침
CONFLICT (content): Merge conflict in .py Automatic merge failed: fix conflicts and then commit the result
  • $ git commit -m conflict resolved 로 conflict을 해결한 이후에 commit을 하자. (commit 할때는 항상 메시지가 고민)

git log

  • 어떤 작업 후에 커밋했는지 확인
  • git log 명령어 옵션

    • -p: 각 커밋에 적용된 실제 변경 내용을 확인
    • --word-diff: diff 명령의 실행 결과를 단어 단위 확인
    • --stat: 각 커밋에서 수정된 파일의 통계 정보를 확인
    • --name-only: 커밋 정보 중에서 수정된 파일의 목록만 확인
    • --relative-date: 상대적인 시간을 비교해서 확인 (1일전, 1주일전))
    • --graph: 브랜치 분기와 변합 내역을 그래프 형태로 확인 (브랜치 확인시 유용)

Github

  • github guides

    • github flow, github pages, git handbook 등에 대해서 설명

좋은 코드를 작성하는 방법

  • 다른 사람의 코드를 읽어보고
  • 코드를 많이 작성하고
  • 내 코드와 다른 사람의 코드를 비교해서 생각해보고

원격

git fetch

  • 로컬 저장소와 원격 저장소의 변경사항이 다를때 이를 비교 대조하고, git merge 명령어와 함께 최신 데이터를 반영하거나 충돌 문제 등을 해결
  • 내가 로컬에서 작업하고, 다른 협업자가 원격 저장소를 먼저 변경하면 push를 허용하지 않음, 이때 로컬 저장소의 커밋들을 원격 저장소와 맞춰야 하는데 이때 하는게 fetch
  • vs $ git pull

    • pull은 원격 저장소의 정보를 가져오면서 자동으로 로컬 브랜치에 병합까지 수행 (어떤 내용이 변경되었는지 알수 없음)
    • fetch를 통해 정보 가져오고 수동으로 병합하는 방법을 추천
  • fetch 하는 순서

    • push fail
    • fetch
    • branch -a
    • git merge origin/master
    • 로컬 master와 remote의 master를 병합
    • git diff <— pull을 했다면 병합을 자동으로 하기 때문에 어떤 변경이 있는지 모른다.
    • git commit
    • git push

고급

  • 명령어

    • git tag: 커밋을 참조하기 쉽도록 알기 쉬운 이름을 붙인다.
    • git commit --amend: 같은 브랜치 상에 있는 최종 커밋을 취소하고 새로운 내용을 추가하거나 설명을 덧붙인 커밋을 할 수 있다. (예: author 변경)
    • git revert: 이전에 작성한 커밋을 지운다. (특정 커밋의 내용을 지우는 새로운 커밋을 만들어 지운 내역을 모든 사람이 알 수 있도록)
    • git reset: 어떤 커밋을 버리고 이전의 특정 버전으로 다시 되돌릴 때 사용한다. git revert와 다른 점은 지운 커밋 내역을 남기지 않는다는 점
    • git checkout HEAD --filename: 아직 커밋하지 않은 변경 내역을 취소한다.
    • git rebase: git merge 처럼 병합할때 사용한다. 하지만 브랜치가 많을 경우 브랜치 이력을 확인하면서 병합
    • git rebase -i: 서로 다른 두개의 커밋 내역을 합친다.

tag

  • x,y,z

    • x는 기존과 호환 되지 않는 변경이 발생할때 증가
    • y는 기존과 호환 되며, 새로운 기능이 추가될 때 증가
    • z는 기존과 호환 되며, 버그 수정 등이 될 때 증가
# tag 추가하기
$ git tag <tagname>
$ git tag -a <tagname> # 최근 커밋에 tag +annotated
$ git tag -a <taganame> <checksum> # checksum에 tag +annotated

# 확인
$ git show <tagname>
$ git log --decorate -1

# 태그 리스트 확인
$ git tag -l

# tag와 커밋 checksum 확인
$ git show-ref --tags

# 특정 커밋에 태그를 붙이는 방법 (커밋 checksum 값 필요)
$ git log -2 # checksum의 값을 확인
$ git tag 0.9 <checksum>
$ git show-ref --tags # 태그 붙은것을 확인

git commit —amend

  • 마지막 커밋 메시지를 수정하는 방법
$ git commit --amend
  • 마지막 커밋과 커밋하지 않은 상태에 있는 변경 내역이 서로 합쳐진 새 커밋 생성
  • 아무런 변경 내역을 만들지 않고 명령어를 실행하면 커밋 메시지만 변경하는 효과
  • 최종 커밋을 수정하는 것이 아니라 최종 커밋을 대체하는 새로운 커밋을 생성

git revert

  • 공개된 커밋 내역을 수정 (되도록이면 안하는게)
  • 커밋으로 발생한 변경 내역의 반대 커밋을 한다. (추가한 코드 제거, 제거한 코드 추가)

    • 되돌리는 것이 아니라 되돌리는 것 같은 효과를 내는 것
$ git revert <commit checksum>

# 최근 변경 이력 확인
$ git log -5
  • git revert를 실행한 시점부터 대상 커밋(checksum으로 넣는 커밋)까지 변경 내역을 거꾸로 적용하는 새 커밋을 생성

git reset

  • 이전 작업 결과를 저장한 상태로 되돌리기
  • 어떤 특정 커밋을 사용하지 않게 되어 다시 되돌릴 때 사용
  • vs revert

    • revert 이전 커밋을 남겨두는 명령어
    • reset 이전 커밋을 남기지 않고 새로운 커밋을 남긴다는 차이
  • 커밋인 HEAD의 위치, 인덱스(실제 커밋 전 변경 내역을 담는 준비 영역, git add 명령시에 이 영역으로 이동), 작업하는 저장소 디렉터리 등도 함께 되돌릴지 선택하기 위한 모드를 지정가능

    • hard: 완전히 되돌림
    • mixed: 인덱스의 상태를 되돌림. (default)
    • soft: 커밋만 되돌림
  • 명령어 옵션

    • ^ 혹은 ~: ~은 커밋 내역 하나를 의미, 표시한 수 만큼 되돌린다.
    • ORIG_HEAD: 지운 커밋 내역을 보관, 해당 명령을 통해 git reset명령으로 지운 커밋을 되돌릴 수 있다.
$ git reset --soft HEAD~~~
$ git reset --soft ORIG_HEAD # 원래 상태로 되돌리기

$ git reset --hard HEAD~~~
$ git reset --hard ORIG_HEAD

git checkout HEAD —filename

  • 특정 파일을 최종 커밋 시점으로 되돌리기
  • 파일 하나를 대상으로 변경 내역을 통째로 원래대로 (변경 직전의 최종 커밋 시점으로) 되돌릴 때 사용할 수 있는 명령어
$ git checksum HEAD --<filename>
  • vs git reset

    • reset: hard모드가 아니라면 저장소 디렉토리 파일 내용은 명령을 실행한 시점 그대로 남는다. git reset 명령으로 되돌린 다음에 필요한 부분만 수정 작업하고 다시 커밋 가능, 커밋된 변경 내역이 파일에 그대로 남는다.
    • checkout: 파일을 완전하게 대상 커밋의 시점으로 돌린다. 이후 커밋된 변경 내역이 사라진다.

git rebase

  • rebase(=re 다시, base를 정한다.)
  • 브랜치 이력을 확인하면서 병합
  • 푸시하기 전에는 로컬 저장소에 있던 커밋을 깔끔하게 정리해서 푸시
  • 명령어 옵션

    • git rebase --continue: 충돌 상태를 해결한 후 계속 작업을 진행
    • git rebase --skip: 병합 대상 브랜치의 내용으로 강제 병합을 실행, 즉 여기서 명령을 실행하면 master 브랜치를 강제로 병합한 상태가 된다. 또한 해당 브랜치에서는 다시 git rebase 명령을 실행할 수 없다.
    • git rebase --abort: 명령을 취소
  • (hotfix) rebase (onto) master

    • 현재 작업중인 브랜치의 base를 master로 다시 설정
  • git rebase 명령을 실행하면 무조건 fast-forward가 가능하지만, 이런 경우 병합 커밋을 남기는게 좋다. (master의 커밋 뒤에 rebase되는 커밋들이 붙음)

    • 병합 흔적을 명시적으로 커밋 그래프에 남기는게 좋다.
    • $ git checkout master; git merge hotfix --no--ff 명령을 실행

git rebase -i

  • i, interactive

    • 상호 작용하면서 rebase할 커밋을 선택
  • 커맷 내역 합치기는 방법
$ git rebase -i HEAD~~~
  • 원칙

    • 남기는 커밋 메시지 앞에는 pick
    • 없애는 커밋 메시지 앞에는 fixup
    • checksum은 그대로
    • 커밋 메시지를 새롭게 수정못함
  • 여러개의 커밋 중에서 필요한 것을 고른 후에 새롭게 커밋

commit

  • 커밋은 어떤 상황에서 해야 할까? 커밋은 프로젝트에서 의미가 있는 최소한의 단위를 말하고, 의미를 가질 수 있게 되는 시기라면 언제든지 커밋을 권장한다.
  • 커밋 단위

    • 여러 파일을 수정하더라도 그 의도는 단 하나여야 한다.
    • 파일을 하나만 수정하더라도 두 개 이상의 의도가 있다면 하지 말아야 한다. (버그와 새기능 추가를 동시에 하지 않아야 한다.)
  • 커밋 메시지 작성 규칙
  • 메시지 작성 방식

[category] - [simple message]
[detailed description]
  • category

    • fix: 잘못된 부분 수정
    • add: 기능 추가
    • mod: 코드 수정
    • rm : 기능 삭제
  • message

    • 커밋에 대한 간단한 한 줄 설명을 작성 (약 70자)
  • detailed description

    • 커밋의 자세한 내용을 기술 (왜 커밋 했는지, 버그 수정의 경우 원래 어떤 문제가 있었는지, 사용중인 이슈 트래커가 있다면 해당 이슈의 하이퍼링크를 포함)

branch

  • 작업 흐름의 내용, 어떤 목적으로 브랜치를 만들었는지 바로 알수 있도록
  • 이름 작성 규칙 #
  • 이름 작성 규칙 (카테고리(=디렉토리))

    • new: 새 기능 추가가 목적인 브랜치
    • test: 무언가를 테스트하는 브랜치(새 라이브러리, 배포 환경, 실험 등)
    • bug: 버그 수정이 목적인 브랜치
new/feat-foo
new/feat-bar
but/critical-thing
test/awesome-new-library

프로젝트 유형별 협업 흐름

  • 어떤 브랜칭 생성 규칙을 선택할 것인가
  • (데스크톱 앱)git-flow, (웹)github-flow, (모바일 앱)gitlab-flow

git-flow

  • develop 브랜치를 중심으로 feature 브랜치들을 통해 기능 추가, release 브랜치를 통해 배포 준비와 코드의 버그 수정하며, master로 배포하고, hotfix로 배포된 버전의 버그를 수정해 master와 develop 브랜치에 반영하는 것을 반복
  • 브랜치 종류

    • develop: 하나만 존재, 여기서 모든 개발이 시작 (이 브랜치는 오직 병합만 커밋)
    • feature: 여러개 존재 가능, develop 브랜치를 기반으로 새로운 기능 개발이나 버그 수정, 각각의 브랜치는 하나의 기능(의도)만을 맡는다.
    • release: develop 브랜치에서 갈라져 나와 배포를 준비하는 브랜치 (오로지 버그만 수정), 배포본의 완성도를 높이는 브랜치
    • master: 실제 배포되는 버전이 있는 브랜치 (release, hotfix 브랜치와만 관계, 오직 병합만 커밋)
    • hotfix: 현재 배포 중인 코드에 버그가 있어 급히 수정하고 내용은 master와 develop 브랜치에만 반영
  • 특징

    • 작업 흐름은 주기적으로 작업 결과를 배포하는 프로젝트의 경우 적합
    • 매우 견고한 코드를 생산하면서 배포 간격이 충분히 긴 프로그램이나 솔루션을 다루는 프로젝트에 적합
    • 빠른 개발과 배포에는 알맞지 않음 (하루에도 몇번씩 작업 결과를 배포해야 하는 웹 서비스 등에는 알맞지 않다.)

github-flow

  • git-flow의 단점을 해결하고자 github에서 사용하는 github-flow
  • 브랜치 종류

    • master: 언제나 배포할 수 있는 상태로 유지되는 브랜치 (보통 하나만 존재, 오직 병합 커밋만 할 수 있다.)
    • feature: 여러개 존재가능, 새 기능을 추가하거나, 버그를 수정하거나, 그 외의 모든 코드 수정을 담당하는 브랜치 그룹. 단 한번에 하나의 의도만을 구현하는 브랜치 그룹이며 그에 따라 이름 짓기가 중요한 브랜치 그룹
  • 특징

    • 빠른 기능 추가와 수정이 필요한 분야에 적합
    • 웹 애플리케이션 등이 이 작업 흐름을 적용하기에 적합
    • 간단하고 기능의 추가 완료와 배포가 바로 연동되는 가볍고 작은 빠른 작업 흐름

gitlab-flow

  • git-flow와 github-flow의 중간에 위치한 gitlab-flow
  • 작업흐름들은 github-flow를 따르지만 배포 과정을 gitlab에서 개선한 작업 흐름

    • github-flow의 부족한 점인 안정성과 배포 시기 조절 가능성을 추가 브랜치를 두어 보강하는 전략
  • 브랜치 종류

    • production: master 브랜치에서 production 브랜치로 병합, git-flow에서 release 브랜치와 비슷한 역할. gitlab-flow에서의 production 브랜치는 오직 배포만을 담당
  • 특징

    • 사용자의 의도대로 배포할 수 없는 환경
    • 스토어에 앱을 배포하는 형태일때 적합 (배포 시기를 정할 수 없음)
    • production 브랜치에 배포하고 외부 배포 승인을 대기
@doubly