Git 사용법

김무성·2022년 2월 10일
0

git

목록 보기
1/2

Git

개념

Git은 분산 버전 관리 시스템으로 리모트 서버에 있는 소스를 수정하려면 로컬 환경으로 소스를 클론(Clone)하는 과정이 필요하고 이후, Git은 로컬 환경의 파일을 추적하고 있다가 사용자가 소스를 수정하면 그 변경 사항을 감지한다.
사용자는 자신이 리모트 서버에 변경 사항을 반영하고 싶은 파일이나 소스 코드의 라인을 고른 뒤 리모트 서버에 업로드한다.

  • Push: 사용자가 자신이 변경한 로컬의 소스를 서버의 소스에 업로드하는 서버로 밀어올리는 행위
  • Pull, Fetch: 사용자가 서버의 소스를 자신의 클라이언트로 가져오는 행위

용어

Remote / Origin

  • Remote: 리모트 서버 자체를 의미
    리모트 서버라는 개념은 우리가 자주 사용하는 구글 드라이브나 N드라이브와 같은 클라우드 스토리지와 같이 전 세계 어딘가에 있는 서버에 우리의 소스를 저장하는 것

  • Github, Bitbucket, GitLab, etc...: 서버를 제공해주는 대표적인 업체
    Git이라는 시스템에 필요한 리모트 서버와 Git을 좀 더 편리하게 사용할 수 있는 기능들을 제공

  • Origin: 내가 사용하는 리모트 서버의 이름
    어떤 리모트 서버에 변경 사항을 업로드 할 것인지 정할 때, 반드시 하나의 리모트 서버만 사용할 수 있는 것이 아니기 때문에 내가 사용하는 리모트 서버의 이름을 정하기위해 사용하며 보통은 한 개의 리모트 서버만 운용하는 경우가 대다수이기 때문에 많은 사람들이 Remote와 Origin을 혼용해서 사용

Repository

  • 저장소, 리모트 서버 내에서 구분되는 프로젝트 단위
    구글 드라이브를 사용할 때도 하나의 디렉토리에 모든 파일을 다 때려넣지않고 몇 개의 디렉토리를 만들고 용도에 따라 파일을 나눠서 구분하는 것과 동일

  • 일반적으로 한 개의 레파지토리는 하나의 프로젝트를 의미하지만 경우에 따라서 레파지토리 하나에 여러 개의 프로젝트를 구성하기도 함

  • 레파지토리를 클론받을 때는 해당 레파지토리를 가리키는 URL이 필요하며 레파지토리의 이름은 URL의 맨 마지막에 .git 확장자를 가지는 방식으로 표현

    https://github.com/user/repository.git
    https://user@bitbucket.org/group-name/repository.git

Branch

  • 일종의 독립된 작업을 진행하기 위한 작업 공간의 개념
  • Git을 초기화하면 기본적으로 master 브랜치가 생기며 메인 브랜치 역할을 함
  • 브랜치는 어떤 브랜치에서 분리되고, 분리된 브랜치는 분리될 당시의 부모 브랜치 상태를 그대로 가짐
  • 개발자는 각각의 브랜치에서 개발을 진행한 뒤 나중에 다시 master 브랜치로 변경 사항을 합침

필수 명령어

혼자서만 프로젝트의 버전 관리를 한다면 단순히 리모트 서버의 레파지토리에서 소스를 받아와서 변경한 후 다시 리모트 서버로 업로드하는 과정만으로도 프로젝트를 진행하는데는 문제가 없지만 Git은 여럿이서 함께 소스를 수정하며 개발하는 협업 상황을 상정하고 만들었기 때문에 협업에서 발생할 수 있는 여러가지 곤란한 상황들을 타파하기 위한 많은 기능을 가지고 있다.
Git은 기본적으로 CLI(Command Line Tools)을 통해 사용하고 commit, fetch, branch와 같은 여러가지 명령어를 사용하여 이 기능들을 사용할 수 있게 해준다.

리모트 서버와 연동

clone

  • 리모트 서버의 레파지토리에서 클라이언트로 파일을 복붙하는 행위
  • 클론을 수행하기 위해 어떤 레파지토리에서 파일을 가져올 것인지에 대한 정보가 필요하며 이 정보는 URL로 표현
  • HTTPS 프로토콜이나 SSH 프로토콜을 사용하여 소스를 클론할 수 있으며 보통 HTTPS를 많이 사용

Github과 같은 리모트 서버 제공업체들은 레파지토리를 쉽게 클론할 수 있도록 눈에 잘 띄는 버튼을 만들어 놓고 해당 레파지토리의 URL을 제공하는 경우가 많으며 사용자는 URL을 복사한 후, Git의 clone 명령어를 사용해서 레파지토리를 클론하기만 하면 된다.

Example

$ cd ~/dev/evan # 원하는 작업 디렉토리로 이동
$ git clone https://github.com/evan-moon/test-repo.git

  1. 원하는 작업 디렉토리로 이동
  2. clone 명령어를 사용하여 레파지토리를 클론
  3. 현재 위치에 레파지토리 이름과 동일한 디렉토리가 생성되고 그 내부에 리모트 서버의 해당 레파지토리의 소스가 전부 복사됨
  4. 복사된 소스를 맘대로 수정하거나 파괴해도 리모트 서버에 업로드 하지 않으면 같은 리모트 서버를 보고 있는 다른 사람 영향 X

pull

  • 리모트 서버의 최신 소스를 가져와서 로컬 소스에 병합(Merge)해주는 명령어
    만약 내가 처음 소스를 클론한 후에 다른 사람이 리모트 서버를 상태를 갱신했더라도 리모트 서버가 나에게 그 변경된 사항을 알려주지는 않기 때문에 내가 직접 서버에 문의를 날려야 하는 것이다.
  • pull은 단순히 리모트 서버에서 로컬로 소스를 가져온다의 개념보다는 가져와서 합친다의 개념이기 때문에 브랜치끼리도 pull을 통해 소스를 합치는 것이 가능

$ git pull # 현재 내 로컬 브랜치와 같은 이름을 가진 리모트 서버 브랜치가 타겟
$ git pull origin master # origin 리모트 서버의 master 브랜치가 타겟

  • Pull Request - “내가 작업한 브랜치를 가져가서 합쳐줘~“라는 의미

fetch

  • 리모트 서버의 최신 이력을 내 클라이언트로 가져오되 병합은 하지 않는 명령어
    fetch 명령어를 사용하면 다른 사람들이 리모트 서버에 새로 업데이트한 모든 내역을 받아올 수 있으며 내역을 보고 내 로컬에 있는 버전이 리모트 서버에 있는 버전보다 이전 버전이라면 pull 명령어를 사용하여 내 컴퓨터의 소스 코드를 갱신하면 된다.

$ git fetch

  • 보통 로컬 소스와 리모트 소스의 변경 사항을 미리 비교해보고 싶을 때 fetch를 사용
    pull은 바로 리모트 서버의 최신 소스를 가져와서 내 로컬 소스에 합쳐버리기 때문에 조금 위험하다. ex) 지금 리모트 서버의 최신 소스가 버그가 있는 상태

#!/bin/bash

git fetch --all -p; git branch -vv | grep ": gone]" | awk '{ print $1 }' | xargs -n 1 git branch -d

위의 쉘은 fetch를 통해 리모트 서버의 최신 내용을 받아온 뒤, branch 명령어를 사용하여 리모트 서버에서는 삭제되었지만 로컬에는 남아있는 브랜치를 찾아서 싹 다 지워주는 스크립트이며 로컬에는 있지만 리모트에서 삭제된 브랜치는 브랜치 이름 뒤에 : gone이라는 문구가 붙어있기 때문에 구분이 가능하다.

$ git branch -vv

  • master fa0cec5 [origin/master] 마스터 브랜치에욤
    test 1f3578f [origin/test: gone] 리모트에선 죽은 브랜치
    test2 fa0cec5 로컬에서 만들어지고 리모트에 업데이트는 안된 브랜치

변경사항을 리모트 서버에 업데이트

add

  • 원하는 변경사항만 골라담는 명령어
    택배를 보낼때, 상대한테 어떤 물건을 보낼지 정해야한다. 이때 add 명령어가 어떤 물건들을 포장할 것인지 고르는 과정을 담당한다.

$ git add . # 현재 디렉토리의 모든 변경사항을 스테이지에 올린다
$ git add ./src/components # components 디렉토리의 모든 변경사항을 스테이지에 올린다
$ git add ./src/components/Test.vue # 특정 파일의 변경사항만 스테이지에 올린다
$ git add -p # 변경된 사항을 하나하나 살펴보면서 스테이지에 올린다

  • 선택된 변경 사항들은 스테이지(Stage)라고 불리는 공간으로 이동
    스테이지는 박스와 같은 역할을 한다.

  • git add <경로> 명령어 - 해당 경로 안에 있는 변경 사항을 전부 스테이지로 이동
    이 과정이 불안한 사람은 -p 옵션을 줌으로써 변경 사항을 하나하나 확인하면서 스테이지에 올릴 수도 있다.

  • 스테이지에 담긴 변경 사항들은 git status 명령어를 사용하여 확인 가능
    status 명령어에 추가적으로 -v 옵션을 사용하면 어떤 파일의 어떤 부분이 변경되었는지도 함께 볼 수 있다.

$ git add ./soruce
$ git status

On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
(use "git reset HEAD ..." to unstage)

modified:   source/_drafts/git-tutorial.md

commit

  • 변경 사항들을 포장하는 명령어
    add를 사용하여 원하는 변경사항을 스테이지에 올렸다면 이제 스테이지에 있는 변경 사항들을 포장할 차례이고 이 포장하는 행위를 commit이라고 한다.
git commit -m "Add: test.py 파일 추가"
  • Git이 하나의 커밋을 하나의 버전으로 정의하므로 커밋은 Git에서 상당히 중요한 부분을 차지
    특정 버전으로 어플리케이션을 변경이라는 기준도 당연히 바로 이 커밋이 된다.

$ git log --graph

  • commit 20f1ea9 (HEAD -> master, origin/master, origin/HEAD)
    | Author: Evan Moon bboydart91@gmail.com
    |
    | 회원가입 기능 개발 끝!
    |
  • commit ca693fd
    | Author: Evan Moon bboydart91@gmail.com
    |
    | 회원가입 비밀번호 입력 폼 추가
    |
  • commit f9b6e2d
    | Author: Evan Moon bboydart91@gmail.com
    |
    | 회원가입 이메일 입력 폼 추가
    |
  • HEAD의 위치로 현재 커밋 상태를 확인할 수 있음
    위의 그래프 상에서 HEAD가 20f1ea9 회원가입 기능 개발 끝! 커밋에 위치해 있으므로 현재 필자의 어플리케이션은 회원가입 기능까지 개발이 완료된 상태라는 것을 알 수 있다.

  • 해쉬 값을 사용하여 어떠한 커밋으로든 자유자재로 이동 가능
    그래프를 보면 각각의 커밋들은 20f1ea9과 같은 고유한 해쉬 값을 가지고 있는데, 이 해쉬 값을 사용하여 어떠한 커밋으로든 자유자재로 이동할 수 있다. 예를 들면 회원가입 비밀번호 입력 폼 추가 커밋의 해쉬 값을 사용하여 git checkout ca693fd 명령어로 회원가입 비밀번호 입력 폼이 추가된 시점으로 이동할 수 있다는 것이다.

  • 커밋은 반드시 실행 가능한 단위로 설정해야 함
    특정 커밋으로 버전을 변경했을 때 어플리케이션이 제대로 실행되지 않고 에러가 발생하면 안된다.

  • 커밋에는 메세지를 담을 수 있음
    메세지는 커밋으로 인한 변경 사항이 무엇인지 직접적으로 표현해주는 유일한 수단이므로 좋은 커밋 메세지를 작성하기 위한 고민은 필수이며 커밋 메세지는 꼭 영어여야 할 필요는 없다.

  • 커밋은 아직 리모트 서버에 파일을 전송하는 것이 아니라 사용자의 클라이언트 내에서 수행되는 과정이므로 인터넷에 연결이 되어 있지 않아도 변경 사항을 커밋하는 것이 가능

push

  • 변경 사항들을 리모트 서버로 배송하는 명령어
    커밋을 통해 포장된 변경 사항들은 push 명령어를 사용하여 리모트 서버로 업로드 된다.
  • 푸쉬는 커밋된 변경 사항들을 실제 리모트 서버에 전송하는 것이기 때문에 반드시 네트워크에 연결이 되어있어야 함
  • 푸쉬할 때는 Git에게 “어떤 리모트 서버의 어떤 브랜치로 푸쉬할 것인지”도 함께 알려줘야 함
    푸쉬할 때 반드시 A 로컬 브랜치는 A 리모트 브랜치에만 푸쉬해야 한다는 룰은 없다.

$ git push origin master # origin 리모트 서버의 master 브랜치로 푸쉬해줘!

  • Git은 브랜치를 자동으로 추적할 수 있는 기능을 제공
    브랜치 이름이 길면 매번 입력한다는 것이 귀찮을 수 있으므로 --set-upstream 옵션을 사용하고 처음 한번만 브랜치 이름을 입력해주면 그 이후로는 git push 명령어만 입력해도 자동으로 처음 입력했던 브랜치로 변경 사항을 푸쉬할 수 있다.

$ git push --set-upstream origin master

버전 관리

용어와 개념

  • HEAD: 현재 나의 버전 상태를 의미
  • 브랜치(Branch): 작업 공간
  • 머지(Merge), 리베이스(Rebase): 브랜치를 합침

Merge Conflict

  • 논리적인 에러가 아니라 내 작업물과 다른 사람의 작업물이 충돌한 상황

Example

7월 25일 지각자 명단

나연
채영
사나
쯔위

  1. 철수와 영희는 사장님으로부터 지각한 사람의 명단을 만들어서 관리해 달라는 부탁을 받아 다음과 같은 텍스트 파일을 만들어서 지각자를 관리하기 시작
  2. 철수와 영희는 사장님으로부터 지각자를 관리할 수 있는 권력을 부여받았고, 매일 이 파일에 지각자를 입력
  3. 철수와 영희는 별로 사이가 안 좋기 때문에 이 둘은 서로 커뮤니케이션을 하지 않고 각자 맘대로 파일을 수정하는 방식으로 지각자 입력 작업을 진행
  4. 그러던 중 철수와 영희는 사무실에 심어놓은 자신들의 정보원으로부터 7월 25일의 세번째 지각자인 사나가 사실 지각이 아니였다는 정보를 입수
  5. 문제는 이 정보원들이 가져온 정보가 서로 다름
    철수의 정보원: 야 7월 25일에 지각한 사람 있잖아, 사나가 아니고 미나래!
    영희의 정보원: 영희야, 7월 25일에 사나가 지각한게 아니고 지효가 지각한거라는데?
  6. 이 둘은 자신의 정보원을 100% 신뢰하기 때문에 바로 각자 지각자 명단 파일을 수정하기 시작
From https://github.com/evan-moon/conflict-test
 * branch            test       -> FETCH_HEAD
Auto-merging 지각자.txt
CONFLICT (content): Merge conflict in 지각자.txt
Automatic merge failed; fix conflicts and then commit the result.
  • 이렇게 다른 사람과 내가 같은 부분을 수정하게 되면, Git은 어떤 것이 맞는 소스인지 알 방법이 없음
  • 이런 상황에서 Git은 어떤 부분이 충돌났는지 표시하여 사용자에게 알려주기만 하고 나머지는 사용자가 알아서 수정하라고 맡겨버리는데, 이런 상황이 바로 병합 충돌(Merge Conflict)
  • 같은 브랜치에서 작업한다는 것은 소스의 변경 사항을 계속 히스토리를 공유한다는 것으로 주기적으로 리모트 저장소로부터 상대방이 작업한 것을 Pull로 가져와서 내 로컬 브랜치에 병합해야하는데 이 과정에서 충돌이 발생할 가능성이 높음
7월 25일 지각자 명단

나연
채영
<<<<<<< HEAD
지효
=======
미나
>>>>>>> 35058b46325bb61112efd52f4019f907c561328d
쯔위
  • <<< HEAD와 ===사이에 들어있는 상단 부분이 현재 브랜치에서 내가 수정한 내용
    영희는 사나를 지효로 수정했기 때문에 해당 부분에 지효라는 이름이 들어가 있다.

  • ===부터 >>> 커밋 해쉬사이의 내용은 어떤 커밋에서 수정된 내용과 충돌이 발생했는지 알려줌
    이 예시에서는 철수가 사나를 미나로 수정한 부분이 될 것이다.

  • Git은 그냥 버전 관리만 해주므로 비즈니스 히스토리를 모르기 때문에 둘 중에 어떤 것이 맞는 소스인지도 모르고 사용자에게 선택을 맡기며 이 상황에서 영희는 다음 세가지 선택지를 가질 수 있음

철수의 변경 사항을 무시
자신의 변경 사항을 무시
두 변경 사항 모두 반영

여러개의 브랜치를 사용하는 이유

  • 기본적으로 Git은 혼자 만의 작업이 아닌 여러 명이 함께 작업하는 협업 상황을 상정하고 만들어짐
  • 여러 사람이 한번에 같은 어플리케이션의 코드를 수정하고 있는 상황에서 머지 컨플릭트가 자주 발생하므로 보통 사용자들은 브랜치로 주제에 맞는 작업 공간을 따로 나누어서 히스토리를 관리
    브랜치를 나누어도 결국 언젠가 소스를 병합해야 하기 때문에 컨플릭트가 발생할 확률은 있지만 적어도 작업 중간중간에 계속 해서 컨플릭트를 수정해야하는 일은 많이 줄일 수 있다.
  • 브랜치 전략: “어떻게 해야 효율적으로 여러 개의 브랜치를 관리할 수 있을까?”

전략적인 브랜치 관리, Git Flow

  • master와 develop 브랜치를 가지고 시작
  • master는 항상 운영되고 있는 소스의 상태를 가지고 있어야하며, 절대 master 브랜치에는 바로 커밋을 할 수 없음
  • develop 브랜치는 팀이 현재 개발을 진행하고 있는 브랜치이고 develop 브랜치에서 각자 개발을 맡고 있는 기능 별로 feature 브랜치를 생성해서 실제 개발을 진행

  • master 브랜치에 프로젝트의 시작을 의미하는 커밋이 찍힌 후 develop 브랜치가 생성, develop에서부터 기능 개발을 담당하는 브랜치들이 분기
  • 에반은 feature/add-typescript 브랜치를 생성한 후 어플리케이션에 타입스크립트를 붙히는 작업
  • 다니엘은 feature/social-login 브랜치를 생성한 후 소셜 로그인 연동 작업
  • 그 후 개발이 끝나는 대로 develop 브랜치에 해당 브랜치들을 차례로 머지하고 있는 모습
  • 다니엘이 에반보다 develop 브랜치에 머지한 시점이 늦기 때문에 만약 에반과 다니엘이 같은 부분을 변경했다면 이때 컨플릭트가 발생, 하지만 적어도 에반과 다니엘이 각자 기능을 개발하고 있을때는 컨플릭트가 발생하지 않기 때문에 좀 더 기능 개발에 집중 가능
  • 이렇게 개발을 진행하다가 배포를 해야할 시점이 오면 master로부터 release브랜치를 생성
    release/release-1.0.0과 같이 배포 버전을 브랜치 이름에 표기하는 네이밍 컨벤션, 이 release 브랜치는 온전히 배포 만을 위한 브랜치이기 때문에 해당 버전의 배포가 끝나면 버려진다.

  • 개발이 종료되고 1.0.0 버전을 배포하기 위해 release/release-1.0.0이라는 노란색 브랜치를 master로부터 하나 생성
  • 다음 버전에 배포될 기능들을 가지고 있는 develop 브랜치를 release/release-1.0.0 브랜치에 머지하고 스테이징 서버에 배포하는 등 최종 테스트를 한 후, 조직원 모두가 해당 버전의 배포에 동의한다면 master 브랜치에 해당 브랜치를 머지하고 버전명으로 태그를 담

핫픽스

  • develop 브랜치에서 브랜치를 분기하면 현재 버전의 기능 개발이 끝날 때까지 기다려야 함
  • 이런 경우 develop 브랜치에서 hotfix 브랜치를 분기하여 master로 머지 후 긴급 배포를 하게되면 develop 브랜치에 들어있는 아직 배포되지 말아야할 기능들까지 배포되기 때문에, 핫픽스는 예외적으로 master에서 분기해서 다시 master로 머지할 수 있음
  • 검정색 라인인 hotfix/fix-main-page는 master 브랜치로부터 갈라져나와서 한 개의 커밋이 찍히고 다시 master로 머지 됨
    이때는 정식 릴리즈가 아닌 핫픽스 릴리즈이므로 Sementic Version 룰에 따라 0.0.1 버전의 태그를 달아주었다.
  • hotfix/fix-main-page 브랜치가 머지되고 배포가 되었다는 것은 현재 운영 환경에서 돌아가고 있는 소스가 변경되었다는 것이므로 develop 브랜치에도 해당 변경 사항을 반영해줘야 함
    핫픽스 담당자는 배포를 하고 나면 develop 브랜치를 직접 최신화하는 것이 좋으며 동료들에게 “지금 핫픽스 배포가 끝났으니 각자 작업하시는 브랜치에서 develop 브랜치를 Pull하여 최신화 해주세요~“라고 알려주면 더더욱 좋을 것이다.
  • 같은 소스를 만지는 개발자가 많아질수록 어느 정도의 룰조차 없다면 원활한 협업이 진행되기는 힘들 수도 있기 때문에 대부분의 규모있는 조직에서는 각자의 상황에 맞는 브랜치 전략을 세워서 버전 관리를 진행 함

명령어

두 개의 브랜치 합치기

Merge

  • 제일 기본적인 브랜치 병합 기능
    합치려고 하는 대상 브랜치의 변경 사항을 타겟 브랜치에 모두 반영하면서 머지 커밋(Merge commit)을 남긴다.

$ git checkout master
$ git merge feature

Merge <--squash옵션>

  • 해당 브랜치의 커밋 전체를 통합한 커밋을 타겟 브랜치에 머지하는 옵션
    일반 머지는 머지가 되는 대상 브랜치의 모든 커밋이 남아있는 상태에서 타겟 브랜치로 합쳐지지만 머지 스쿼시는 대상 브랜치의 모든 커밋을 모아서 하나의 커밋으로 합치고 타겟 브랜치에 머지하는 방식이다.

    $ git checkout master
    $ git merge --squash feature

  • 스쿼시도 머지와 같이 독립된 하나의 개념으로 스쿼시는 커밋을 여러 개 합친다는 개념
    하단에 후술할 rebase 명령어와 함께 사용하여 현재 브랜치의 커밋을 합칠 때도 사용한다.

    $ git rebase -i HEAD~~

  • 위 명령어는 HEAD부터 HEAD의 ~~(전전) 커밋까지의 히스토리를 변경하겠다는 의미이다. 이 명령어를 입력하면 vim이 실행되고 아래와 같은 내용이 표시된다.

pick 9a54fd4 commit의 설명 추가
pick 0d4a808 pull의 설명을 추가
  
# Rebase 326fc9f..0d4a808 onto d286baa
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
  • 위의 텍스트에 표시된 커밋들의 맨 앞에 있는 pick 문자를 s또는 squash로 변경하면 두 개의 커밋이 합쳐짐

Rebase

  • 브랜치를 다른 브랜치로 합칠 수 있는 기능

Merge

  • 말 그대로 두 개의 브랜치를 하나로 합치는 기능으로 A 브랜치의 변경 사항 전부를 B 브랜치에 푸쉬하는 것과 동일하며 머지를 사용하여 브랜치를 합치게 되면 반드시 머지 커밋(Merge commit)이 남게 됨

    $ git checkout feature
    $ git merge master

  • 장점: 어느 시점에 어떤 브랜치가 머지 되었는 지 커밋을 통해 알기 쉬움
  • 단점: 불필요한 커밋이 생성
    단점은 작업 중인 브랜치가 별로 많지 않을 때는 나타나지 않지만 브랜치가 많아지면 나중엔 커밋 로그가 머지 커밋으로 뒤덮혀있는 광경을 볼 수 있게 된다.
    Rebase
  • 단순히 합치는 것이 아니라 말 그대로 브랜치의 베이스를 변경하는 것
    방금 전 예시의 feature 브랜치를 master로 리베이스하게 되면 마치 feature 브랜치의 변경 사항들이 master의 변경 사항이었던 것처럼 히스토리가 기록된다.

    $ git checkout feature
    $ git rebase master

  • 장점:깔끔한 커밋 히스토리를 만들어 준다는 것
    머지 커밋이 남지 않고 애초에 master에서 수정한 것 마냥 히스토리가 남기 때문에 깔끔하게 일자로 쭉 떨어지는 이쁜 히스토리를 볼 수 있다.

  • 단점: 커밋 끼워넣기 때문에 발생하는 문제
    feature 브랜치를 master로 리베이스했다고 가정하면 feature 브랜치를 생성한 이후에 master에 반영된 커밋들은 모두 맨 끝으로 이동하고 중간에 feature 브랜치의 커밋들을 끼워넣게 되며 사용자가 보고 있는 master의 상태는 feature의 변경 사항들이 반영되어 있는 히스토리를 가지고 있지만 다른 사람의 master는 아직 예전 master의 히스토리와 함께 일하고 있다는 것이다.

  • 머지는 머지 커밋을 발생시키며 히스토리가 미래로 나아가기 때문에 이런 문제가 발생할 확률이 적지만, 리베이스는 과거를 변경하는 것이기 때문에 문제가 생기기 쉬움

Cherry Pick

  • 다른 브랜치에서 어떤 하나의 커밋만 내 브랜치로 가져오는 기능
    체리픽이 하는 일을 보면 대상 브랜치의 커밋 하나를 가져와서 현재 브랜치에 병합하는 행위라고 느껴지지만 히스토리를 보면 병합되는 그림이 아니라 그냥 해당 커밋을 그대로 복사해와서 내 브랜치에 커밋되는 형태로 기록된다.

    $ git checkout master
    $ git cherry-pick 35058b4 # 가져올 커밋 해쉬

  • 체리픽을 사용할 때도 현재 브랜치의 소스와 충돌이 날 가능성이 있음

    Example

  1. A 브랜치에서 철수가 기능 개발 중
  2. B 브랜치에서 영희가 기능 개발 중
  3. 디자이너가 영희에게 리뷰 별점 아이콘과 디자인을 변경해달라고 요청
  4. 영희가 B 브랜치에서 디자이너의 요구 사항을 반영
  5. 근데 B 브랜치보다 A 브랜치가 먼저 배포되야 함
  6. 디자이너가 철수에게 A 브랜치에 왜 리뷰 별점 디자인 반영안됐냐고 물어봄
  7. 철수가 영희가 작업하고 있는 B 브랜치에서 리뷰 별점 아이콘이 수정된 커밋을 A 브랜치로 체리픽함으로써 이 상황을 쉽게 해결

작업하던 사항 임시 저장

Stash

  • 현재 작업 중인 변경 사항들을 잠시 스택에 저장할 수 있는 명령어
    이 명령어는 아직 마무리되지 않은 작업이 있는데 다른 브랜치로 체크아웃 해야하는 경우에 유용하게 사용할 수 있다.

    $ git stash # 현재 변경 사항들을 스택에 저장
    $ git stash list # 스태쉬 목록을 확인
    $ git stash apply # 가장 최근의 스태쉬를 다시 불러온다

  • 직접 스태쉬 이름을 지정 가능
    스태쉬의 이름을 지정하지 않으면 스택에 들어간 순서(First In Last out)대로만 스태쉬를 가져올 수 있다.

    $ git stash branch-name # 스태쉬 이름을 branch-name으로 지정하고 스택에 저장
    $ git stash apply branch-name # branch-name 이름을 가진 스태쉬를 불러온다

이미 커밋한 내용 되돌리기

Reset

  • 지정한 커밋 당시로 돌아가는 명령어
    리셋을 사용하게되면 지정한 커밋 이후의 히스토리는 모두 사라지게 된다.

Example

* 19061e7 - 맛없는 식당을 찾은 죄로 여자친구한테 이별 통보를 받았다.
|
* e50aff9 - 여자친구가 맛이 없다고 한다.
|
* 2d57c29 - 알리오 올리오를 주문했다.
|
* c04f8f6 - 찾아본 식당에 방문했다.
|
* 7d9d953 - 여자친구와 함께 갈 좋은 식당을 찾았다!
  1. 여자친구와 함께 방문할 좋은 식당을 찾아서 기대감을 안고 알리오 올리오를 주문했지만 맛이 없어서 결과적으로 여친한테 차였다.
  2. 너무 슬픈 나머지 기억을 지우고 싶어서 알리오 올리오를 주문하기 전으로 돌아가려고 한다.
  3. 돌아가고 싶은 커밋을 지정하고 reset하면 해당 커밋 이후의 히스토리는 모두 삭제하고 과거로 돌아갈 수 있다.
$ git reset --hard c04f8f6

# 식당을 방문했을 때로 돌아갔다!
* c04f8f6 - 찾아본 식당에 방문했다.
|
* 7d9d953 - 여자친구와 함께 갈 좋은 식당을 찾았다!

reset 명령어의 3가지 옵션

  • hard: 지정한 커밋 이후의 히스토리가 삭제되고 삭제된 내용들은 그대로 사라진다.
  • soft: 지정한 커밋 이후의 히스토리가 삭제되고 삭제된 내용들은 스테이지로 이동한다.(add한 상태로 변경)
  • mixed: 지정한 커밋 이후의 히스토리가 삭제되고 삭제된 내용들은 스테이지에 올라가지 않은 상태가 된다.(다시 add 해줘야 함)
    옵션을 지정하지 않고 reset 명령어를 사용하면 mixed 옵션으로 작동하며 만약 이미 되돌리고자 하는 히스토리가 리모트 저장소에 푸쉬까지 된 상태라면 리셋 후 히스토리를 푸쉬할 때 --force 옵션을 사용해야한다.

Revert

  • 특정 커밋의 변경 사항을 되돌리는 명령어
    이때 해당 커밋을 되돌린다고 해서 히스토리에서 그 커밋을 삭제하는 것이 아니라, 되돌리고자 하는 커밋의 내용을 반전하고 다시 커밋하는 것이기 때문에 하나의 커밋이 추가되는 형태로 히스토리가 남는다.

$ git revert 35058b4 # 특정 커밋을 되돌린다
$ git revert 35058b4..c04f8f6 # 커밋의 범위를 지정하여 되돌린다
$ git revert HEAD # 현재 헤드가 위치한 커밋을 되돌린다

  • 추가된 사항은 제거하고 제거된 사항은 다시 추가
    만약 35058b4 커밋에서 A.js의 2번 라인에 a라는 글자가 추가되었다고 하면 git revert 35058b4를 사용했을때 A.js의 2번 라인에서 a를 다시 삭제하는 것이다.

Example

* 35058b4 - Revert 맛없는 식당을 찾은 죄로 여자친구한테 이별 통보를 받았다. # 여친한테 차인 히스토리만 리벗하자
|
* 19061e7 - 맛없는 식당을 찾은 죄로 여자친구한테 이별 통보를 받았다.
|
* e50aff9 - 여자친구가 맛이 없다고 한다.
|
* 2d57c29 - 알리오 올리오를 주문했다.
|
* c04f8f6 - 찾아본 식당에 방문했다.
|
* 7d9d953 - 여자친구와 함께 갈 좋은 식당을 찾았다!

위의 예시에서 여자친구한테 차인 커밋을 다시 리벗했지만 히스토리 상에는 흑역사가 고스란히 남아있다.

출처

profile
graph data scientist

0개의 댓글