본문 바로가기
야놀자 테크스쿨/수업 요약 정리

[Git] Git 정리

by 노잼인간이라불립니다 2023. 7. 12.

0.  개요

 수업의 커리큘럼의 시작은 Git이었다. 개발자 중에 Git을 모르는 개발자는 없을 것이라고 생각한다. 그런데 내가 지금까지 Git이 왜 필요한지에 대해서 생각해본적이 있었던가? 왜 필요한지에 대해서 생각하지 않고, 무작정 다른사람도 사용하니까 따라서 사용했던 것 같다. 과거에 나를 반성하면서 Git이 세상에 나오게 된 이유는 뭘까? 이 질문으로 이 글을 시작할까 한다. (뭔가 불편하니까 나왔겟죠?)

 

1. Git이 나온 이유

 아마 Git 이전의 개발세계의 난이도는 매우 매우 난이도가 높았을 것이라고 생각한다. 물론 버전에 대한 이력관리는 하고 있었겠지만, 협업하여 개발 완료 후에 소스코드를 합치는 비용, 그리고 버전 관리를 하는 비용은 지금보다는 높았을 것이라고 생각한다.

 Git 없이 협업을 했던 시대를 생각해보자면, 많이 불편했을 것이라고 상상해 볼 수 있겠다. (그리고 만약 누군가의 하드디스크가 재해로 인해 날라갔다면?? 그 사람의 작업물은 훠이훠이 날아가버리고, 그동안의 노력은 제로가 되어버린다~!)

 그리고 Git이 없었다면?!

1. 변경내역을 확인하는 것이 좀 더 어려웠을것이고,(모든 수정이 잠수함 패치처럼 느껴졌을 것..)

2. 작업을 되돌리는 것도 어려웠을 것이고,(이건 이전 버전 파일을 가지고 있었다면 현재 파일을 지워버리고 이전 파일 복붙하면 가능했을듯?!) - 근데 깃도 원리는 동일하다. 각각의 버전마다 스냅샷이 다 있다.

3. 서로 협력해서 개발하여 최종 소스코드를 만들어 내는 것도 매우 어려웠을 것이다.(나였다면 소스코드 합치는 역할 맡기 싫을 듯...)

 

2. Git을 써야 하는 이유

 위에서 말한 불편한 점들을 Git이 모두 해결해준다.

 

 1) 변경이력을 확인하기가 매우 쉽다. - 커밋할 때 커밋 메시지와 함께 커밋이 되기 때문에 이 버전에서는 어떤 것이 수정되었는지 메시지를 읽으면서 자연스럽게 확인할 수 있다.

 2) 작업을 되돌리기 쉽다. - reset 이나 revert 명령어를 이용해서 이전 버전으로의 Rollback이 쉽다.

 3) 협업이 쉬워진다. - Git은 개발자들간에 협업이 잘 이루어 질 수 있게 끔 다양한 브랜치 전략을 권장한다.

 

이렇듯 Git은 전반적으로 프로젝트를 관리, 개발하는 비용을 줄일 수 있게 만들어 준다!

 

3. Git 명령어 정리

 개발자라면 Git을 써야하는 이유를 다들 공감했을 것이다. Git도 파면 끝도 없지만, 오늘은 Git을 사용하려면 적어도 이정도는 알아야 한다 정도로 Git에 대한 명령어 정리를 할 예정이다. (추후 시간이 된다면 나머지 것들도? 가능?)

 

1) GIT 설치 방법 (mac)

 일단 GIT을 사용하기 위해서는 설치를 해야한다. 맥에서는 brew를 이용해서 손쉽게 설치가 가능하다.

brew install git

 

2) global configuration 

 깃을 사용하기 위해선 git 설정파일에 유저정보와 이메일 정보를 입력해야 한다.

brew install git

// config 파일에 유저정보 입력
git config --global user.name "깃에서 나의 이름 넣기 (커밋할 때 저장 될 이름)"
git config --global user.email "깃에 가입한 내 이메일"

// config 정보 보기
git config --list

// config 파일에서 유저정보 삭제 
git config --global --unset user.name
git config --global --unset user.email

 

3) Git 명령어

 자 그럼 본격적으로 오늘 배운 Git 명령어들에 대해서 순서대로 정리해보고자 한다.

 

// 현재 디렉토리 기준으로 새로운 로컬 리포지토리 생성.
git init

/* 
   - 작업 중이거나 변경된 파일들의 상태를 확인 가능 (스테이징 상태인지 등등)
     스테이징 : git에서는 commit이 이루어질때 워킹트리에서 로컬 리포지토리에
			바로 반영하는 것이 아니라, 인덱스 또는 스테이지라고 불리우는 곳에 저장된
			파일들만 로컬 리포지토리에 반영함. 이 과정에서 워킹트리에서 스테이지 영역으로 
			파일을 저장하는 것을 스테이징이라고 함.
*/
git status

// 워크트리 영역에 있는 파일(전체도 되고 일부도 됨)을 스테이지 영역에 파일 등록. (변경 또는 추가된 것들)
git add <file>  // 하나의 파일만
git add <file1> <file2> ...  // 등록하고 싶은 파일만
git add -A  // 변경, 추가된 모든 파일 등록.
git add --all  // -A와 동일
git add .  // -A과 동일함.

// 스테이지 영역에 있는 것을 로컬 리포지토리에 반영함.
git commit -m "commit message"
git commit <file>

// 로컬 리포지토리에서 변경된 사항들을 리모트 리포지토리에 반영함.
git push <원격저장소명> <local_branchname>:<remote_branchname>

// 리모트 리포지토리에서 변경된 내용을 로컬 리포지토리에 반영함.
git pull <원격저장소명> <branchname>

// 리모트 리포지토리에 저장되있는 프로젝트를 복사하여 로컬로 가져옴.
git clone <원격저장소URL> <local_name>

// 커밋 로그 확인 - 작성자, 날짜, 커밋메시지 등등
git log
git log -n  // n개 까지 역순으로 보여줌 
git log —oneline  // 커밋 내용을 축약해서 1라인씩 보여줌
git log —patch  // commit 간의 diff를 보여줌
git log --branches --decoreate  // branch의 최신 커밋위치를 알려 줌
git log --branches --decoreate --graph  // branch의 최신 커밋위치를 알려주고 브랜치를 그래프로 보여줌

// 이전 커밋으로 되돌리기 -> 아주 유용해보인다.
git reset HEAD // 가장 최근에 스테이징된 변경 내역을 언스테이징
git reset HEAD <file> // 해당 파일만 언스테이징
git reset --soft <commit> // 지정한 커밋으로 되돌아가면서 변경내용 유지
git reset --hard <commit> // 지정한 커밋으로 되돌아가지만 변경내용 삭제

// 변경된 내용을 commit하지는 않고, 임시로 스택에 저장가능. (가장 최근 스태시가 0번 스태시)
// 스태시는 스테이징된 상태의 변경 내용을 임시로 저장해 둠. 스테이징 되지 않은 내역은 스태시 불가
git stash
git stash -m “메시지”  // 어떤 작업을 했던 스태시인지 메시지를 남겨놓을 수 있다.(추천)
git stash list  // 저장된 스테시 리스트 보기 (주의: 숫자가 적을수록 최근 저장된 stash)
git stash drop <stash 이름> // 스태시 제거 stash 이름 ex) stash@{0}
git stash pop // 스택에 있는 stash를 꺼내어 현재 상황에 적용.
// 스택에 있는 최근 스태시를 현재 상황으로 적용(적용만하고 stash list의 stash는 지워지지 않음.)
git stash apply


/* 
- 마지막 커밋과 비교해서 변경 사항을 확인할 수 있다.
  (인텔리제이에서 마지막 커밋과 비교했을 때 변경된 사항을 보여주는 것도 이걸 이용해서 구현했을듯 하다)
*/
git diff // working directory랑 index랑 비교
git diff --staged  // index랑 repositroy랑 비교
git diff <commit> <commit>  // 앞 커밋과 비교했을 때 뒷 커밋은 뭐가 달라졌나 비교

// 현재 저장소의 모든 브랜치 출력
git branch 
git branch [-r | -a] [--list] [-v]
git branch -r  // 원격 브랜치만 표시
git branch -a // 모든 브랜치 표시
git branch --list  // 브랜치 이름만 출력
git branch -v  // 브랜치 이름과 마지막 커밋 메시지 출력
git branch -d <branchname>  // 브랜치 삭제


// 브랜치 전환, 커밋을 확인하기 위해 작업 디렉토리를 변경하는 명령어
git checkout [-b] <branchname> // 새로운 브랜치를 생성하고 해당 브랜치로 전환
git checkout <commit_hash>

// remote
git remote add <alias> <url>  // remote 저장소와 연결
git clone <url> <저장할 디렉토리> // remote 저장소로부터 클론
git remote  // remote 저장소 확인
git remote -v  //remote 저장소 url확인
git remote --help  // remote 관련 명령어 확인
git remote remove <alias>  // 원격저장소와 연결 해제


// commit
git commit // 커밋 vi가 뜸
git commit -m "커밋메시지" // 커밋 메시지 한줄로
git commit --amend // 커밋메시지 수정

// commit
git commit // 커밋 vi가 뜸
git commit -m "커밋메시지" // 커밋 메시지 한줄로
git commit --amend // 커밋메시지 수정

// tag 특정 버전 릴리스를 위해 사용 / checkout시 버전입력하면 해당버전 커밋으로 이동.
// tag에는 light tag와 annotated tag가 있다.
git tag 1.0.0 [-m "태그에 대한 설명"] [<branch명> || <commit hash값>]
git tag -a 1.0.0 -m "first release" master // 마스터 브랜치의 헤드에 대한 태그 생성

// 로컬저장소에서 저장한 태그를 원격저장소에 올리기
git push --tags

4) 명령어에 대한 좀 더 자세한 설명

   실시간 강의에서 배웠던 코드와 옵션들을 좀 더 정리해보자!

 

 - git log

  git log 명령어는 커밋했던 내역들을 가장 최신 부터 볼 수 있는 명령어이다.

  git log --oneline으로 하면 각 커밋별로 한 줄로 축약해서 볼 수 있으며, (이 때 축약되는 해시코드는 긴 해시코드와 동일한 식별자 역할)

  git log -p 또는 git log --patch 명령어는 각 커밋 간의 diff를 보여준다. (커밋 별로 파일에서 변경된 내용들을 보여줌.)

 

- git diff 

 git diff에는 3가지 비교가 있을 수 있다. 각 상황에 따른 명령어는 아래와 같다.

 1. git diff  : working directory랑 인덱스와 비교

 2. git diff --staged  : 스테이지랑 로컬 리포지토리와 비교.

 3. git diff <커밋> <커밋>  : 커밋과 커밋과의 비교. (앞 커밋에 비해 뒷 커밋은 뭐가 달라 졌는지 -> 순서 유의할 것)

 

- git stash

git stash는 스테이징 된 파일들을 임시저장.

 1. git stash -m "message" : stash로 임시저장을 하더라도 어떤 수정을 하고 있었는지 메시지로 남겨주는 것이 좋음.

 2. git stash list : stack에 담겨있는 stash들을 보여준다. 주의: 스태시 번호가 낮을 수록 최근 스태시 된 내용이다!

 3. git stash drop <stash 이름> : 해당 이름에 해당하는 스태시를 stack에서 제거

 4. git stash pop : 저장되어 있던 stash를 꺼내와 현재 상황에 적용함과 동시에 stash stack에서 제거.

 5. git stash apply : 가장 최근의 stash를 가져와서 현재 상황에 적용 (stack에 있는 stash는 제거되지 않음.)

 

- git revert  와 git reset

 1. git revert

  - 하나의 버전만 취소 --> 무조건 충돌난다 그래서 하나씩 되돌아 가야함.

  - 버전을 되돌린 새로운 버전을 새로 생성한다

  - 커밋을 한 단계씩 revert 해야 한다. 2단계를 한꺼번에 revert 하려고 하면 conflict 발생한다.

  - git revert <해시코드> <-- 해시코드는 취소할 커밋이다.

 

 2. git reset

  - 이전 버전으로 되돌린다. (아예 되돌아간다)

  - git reset <해시코드> <-- 들어가는 해시코드는 돌아갈 버전의 커밋 해시코드이다. (revert와 조금 다르니 주의할 것!!)

  - soft mixed hard 옵션이 있다. default 값은 mixed.

  - git reset --soft <해시코드> : 해당 버전으로 돌아가면서 변경사항이 스테이징 상태로 유지.

  - git reset <해시코드> : 해당 버전으로 돌아가면서 변경사항이 언스테이징 상태로 유지.

  - git reset --hard <해시코드> : 해당 버전으로 돌아가면서 변경사항이 삭제됨.

 

3. 정리

  reset과 revert는 언뜻 비슷해보이지만 들여다보면 미묘하게 다르다.

 

  1) revert는 하나의 커밋만 취소, reset은 몇 단계던 상관없이 해당하는 커밋으로 복귀 가능하다.

  2) revert는 새로운 버전을 생성하는 개념이라면, reset은 아예 돌아가는 개념이다.

 

 회사의 규정이나 협의를 통해 사용되는 것이 다르겠지만, 나 같은 경우에는 오류가 있던 버전도 계속해서 기록에 남겨두고 싶다면 revert, 그 기록이 없는게 더 깔끔하다고 판단되면 reset을 사용할 것 같다. (그것보다 아예 revert나 reset을 사용할 일이 없어야 한다!)

 

 

5) 이후 추가 내용 (23/07/18 추가)

1. branch

 - branch 생성시 .git/refs/heads/master 이런 식으로 heads아래에 브랜치가 새로 생성된다.

 - .git/HEAD라는 파일이 있는데 그 안에는 현재 브랜치의 디렉토리 파일을 가리키고 있다. (refs/heads/master) 그리고 그 디렉토리 파일 안에는 그 브랜치의 최신 커밋객체의 해시값이 적혀있고, 해시값을 통해 objects아래에 있는 커밋객체를 확인할 수 있다.

 

2. stash

 - git diff는 워킹디렉토리와 index간의 차이를 나타내주는 명령어이다(전에는 워킹디렉토리와 리포지토리의 차이인 줄 알았다.)

 - git diff --staged는 스테이지와 리포지토리의 차이를 나타내주는 명령어이다.

 

3. reset

 - git reset --soft 옵션은 리포지토리의 내용만 해당 커밋의 내용으로 되돌린다. 그 이후 수정된 파일들은 스테이징 되어있는 상태다.

 - git reset --mixed 옵션은 인덱스 까지 해당 커밋의 내용으로 되돌린다. 그 이후 수정된 파일들은 워킹디렉토리에 남아 있다.

 - git reset --hard 옵션은 워킹디렉토리까지 해당 커밋의 내용으로 되돌린다. 그 이후 수정된 파일의 내용은 남아있지 않다.

 - git reset를 실행하게되면 .git/ORIG_HEAD에는 reset을 실행하기 전 최신 커밋객체를 저장 해 놓는다.

   그렇기 때문에 git reset --hard ORIG_HEAD 라는 명령어를 사용하면 reset하기 전으로 돌아갈 수 있다.

 

4. log

 - git reflog를 사용하면 HEAD를 어떻게 움직였는지 확인 할 수 있다.(checkout 확인)

 

5. merge

 - git에서 fast-forward merge가 아닌 일반 머지를 할 경우 conflict가 발생할 수 있다.

 - git이 merge를 할 수 있는 이유는 브랜치가 분기하기전 common파일, 메인 브랜치의 수정파일, 병합할 브랜치의 수정파일 총 3개를 가지고 3 way merge를 해 준다. 기존에 있던 파일에서 같은 부분을 수정하였다면 conflict가 발생할 수 있으나 그 외 다른 부분을 각자 수정하였다면 common 파일을 기준으로 삼아 수정된 파일을 병합해 준다.

 

6) 이후 추가 내용 (23/07/20 추가)

1. fetch vs pull

 1) git fetch를 입력하면?

  - 원격 저장소에 있는 최신 커밋 정보들을 가져온다.(커밋 객체, 트리 객체 등)

  - 현재 refs아래의 remote/fetch한 브랜치 명이 최신 커밋을 가리키게 된다.

 

  2) git pull을 입력하면?

  - 일단 fetch와 동일하게 수행된다.

  - 로컬 브랜치와 원격 브랜치가 같은 커밋을 가리키게 된다. (원격 브랜치와 병합된다.)

  - pull을 잘못했을 경우 되돌릴 수 있도록 ORIG_HEAD에 pull 이전에 가리키고 있던 커밋의 해시값이 저장된다.

  

 3) 정리

 --> fetch는 .git파일에 파일정보와 원격브랜치에 대한 정보만을 가져오고, pull은 그 내용을 현재 로컬 브랜치에 적용시킨다. (병합한다.)

 

 

2. merge vs rebase

 1) merge는 base로 하는 commit으로 부터 기준 브랜치와 pull 되는 브랜치간에 수정사항들을 비교해서 자동으로 merge 해준다. (3way merge) 같은 부분을 수정했을 경우 충돌이 나며 그 경우 개발자가 그 부분을 수정하고, add 및 commit 한다.

 

2) merge에서의 이야기 했던 base를 다시 rebase하는 것이 rebase이다. 아래의 그림을 통해 살펴보자.

  커밋이 아래의 그림과 같이 진행되었고, 브랜치는 각각 master와 rebase가 있다고 가정하자.

  여기에서 merge가 발생하게 되면 m2 commit을 base로 하여 m3와 r1,r2의 수정내용들이 반영된 새로운 버전이 생성되게 된다.(master에서 rebase를 땡길 경우 - 마스터 기준 병합)

  rebase 브랜치 기준으로 머지를 발생시켜도 m2를 기반으로 하여 r1,r2,m3의 수정사항들을 반영하여 새로운 버전이 생성되게 된다.

 그럼 rebase 브랜치에서 rebase를 사용하면 어떻게 될지 살펴보자. 먼저 rebase의 base는 m2 commit이다. 근데 rebase 브랜치에서 git rebase master라는 명령이 발생하면 m2였던 base는 m3가 되고 그 base로 부터 r1, r2가 이어진다.

 만약 m3, r1, r2 모두 같은 부분을 수정했다면 각 커밋마다 conflict가 발생하는데 개발자가 원하는데로 내용을 바꿔주고 git add ., git rebase --continue를 입력해주면 해당 커밋에 대한 rebase 작업이 끝난다.

 그리고 브랜치의 모양은 아래와 같은 일자형 모양이 된다.

3) 정리

 즉 rebase라는 것은 말 그대로 기존의 3way 병합을 위한 base를 최신커밋으로 옮기고, 분기된 브랜치는 그 브랜치를 기반으로 다시 base를 시작한다는 것을 의미한다.

 조금 위험한 명령어이기 때문에 자주 사용은 하지 않을 것 같지만, 이번 기회에 rebase에 대해서 조금이나마 이해 해서 기분은 좋다.

 

3. pull request를 이용한 협업

 1. 포크 - 하는이유 : 내 계정으로 포크 떠 와야 push가 가능. 

 2. 포크한 저장소를 클론 (원본 클론 해봤자 어짜피 푸시안되서 소용없다.) 

 3. 브랜치 생성 후 생성된 브랜치에서 작업

 4. 푸시 - git push origin <생성한브랜치 명> 

 5.풀 리퀘스트 보내기

4. git flow

 개인적으로 git flow는 실제로 프로젝트 때 적용해보거나, 회사에 들어가서 실무를 통해서 그 규칙에 맞게 배우는 것이 좀 더 많이 깨달을 수 있는 기회일 것이라고 생각한다. 그러나 그 전에 일단 배경지식을 습득해 놓아야 하기 때문에 이렇게 몇 줄 끄적여 본다.

 

 1) git flow의 브랜치

 - master: 사용자와 직접 맞 닿아 있는 브랜치. 그렇기 때문에 release 버전이 배포 되었을 때 또는 버그 픽스시에만 머지가 발생한다.

 - develop: 개발이 일어날 때 매우 빈번하게 머지 또는 분기될 브랜치. 모든 개발은 이 브랜치 기준으로 작성된다.

 - feature: 기능개발이 필요할 때 회사 규칙에 따라 다르겠지만 feature/login 또는 feature-memeber 등과 같이 기능 개발할 때 develop 브랜치로부터 분기 시켜서 개발.

 - release: develop에서 배포 할만한 프로덕트가 만들어졌을 경우, master에 반영하기전에 qa를 해보기 위해서 release브랜치에서 테스트.

 - hotfix: 버그가 발생했을 경우 master로부터 발생시켜 빠르게 버그 수정후 develop과 master에 push.

 

2)git flow의 대략적인 흐름.

 먼저 master 브랜치에서 처음 커밋이 이루어지고, 앞으로 개발이 이루어질 develop브랜치가 탄생할 것이다. 앞으로 master 브랜치는 실제 qa가 끝난 release 브랜치로부터 버전을 땡겨올 것이다.

 그리고 develop 브랜치를 중심으로 개발이 이루어 질 예정이다. 기능개발이 필요하다면 feature브랜치를 새로 만들어서 기능 개발 후 merge, 기능개발이 모두 이루어져 테스트를 해야한다면 release 브랜치로 push.

 마지막으로는 hotfix같은 경우 master에서 배포된 버전에서 버그가 발생했을 때 빠르게 버그를 수정하고 develop 브랜치와 master 브랜치에 푸시한다.

 

3. 정리

 이렇게 git flow에 대해서 대략적인 흐름에 대해서 알아 보았다. git flow에 대해서 살펴보니 git 브랜치에 따라 각각의 역할이 있고, branch간의 상호작용을 통해 소프트웨어 개발에 있어서 생길 수 있는 복잡한 문제들을 풀어내는 하나의 해결법이라고 생각했다.

 git branch 전략은 프로젝트의 복잡도에 따라 결정되는 것이 맞지만, 앞으로 있을 혐업 프로젝트에서 한번 적용해 보고 싶은 마음이 있다. 만약에 다른 브랜치 전략을 사용하더라도, 다음 회사에서는 git flow를 이용한 프로젝트 관리를 한번 경험해보고 싶다.