Search

Git in General

1. 용어정리

가. VCS

VCS(Version Control System): 파일의 버전을 관리하는 시스템으로 파일의 변경이력을 관리하여 작업 중 발생하는 다양한 경우에 대처하기 용이함

나. DVC

DVC(Distributed Version Control): 파일의 버전 관리를 사용자의 로컬환경과 원격서버에서 동시에 관리함으로써 오프라인 환경과 원격서버 이상 시에도 작업이 가능함

다. Git

VCS의 일종으로 DVC 기반으로 설계됨
→ 가장 대중적으로 사용되는 VCS
→ 무료이며 오픈 소스 소프트웨어임
→ 가볍고 빠르며 오프라인에서 작업 가능함
→ 실수 등에 의해 작업을 취소하는 것이 용이함
→ 쉽고 빠른 브랜치와 병합기능을 제공함

2. Git 기본정리

가. 작업의 흐름(Workflow)

working directory: 실제 작업 영역, 실존 파일이 들어 있음
→ untracked: git에서 파일의 존재를 추적하고 있는 상태
→ tracked: git에서 파일의 존재를 추적하고 있지 않은 상태
→ unmodified: 이전 version과 비교했을 때, 변경된 것이 없는 파일
→ modified: 이전 version과 비교했을 때, 변경된 것이 있는 파일
modified 상태의 파일만 add명령을 통해 staging area에 추가될 수 있음
unmodified 상태의 파일의 경우, 이전 버전과 같은 내용의 파일을 staging area에 추가할 필요가 없음
staging area: 준비 영역, version history에 등록될 준비
→ add 명령에 따라 working directory에서 staging area(index)로 이동
.git directory(local):
→ commit 명령에 따라 git version history에 등록됨
→ commit 되면 HEAD(현재 시점에서 바라보는 버전을 가리킴)가 해당 파일을 가리킴
참고) HEAD~1: 이전 commit, HEAD~2: 이전이전 commit을 가리킴
→ commit 명령 시 해당 파일에 대한 버전, 아이디, 날짜 등의 정보가 해쉬값으로 저장되어 버전 관리 가능
.git directory(remote):
→ push 명령에 따라 local repository가 remote repository에 저장됨
→ pull 명령에 따라 remote repository를 local repository에 반영함
예제) staging area
"hello, world" 텍스트를 포함한 a.txt, b.txt, c.txt 생성
→ working directory 내 위의 세 개의 파일이 untracked 상태
git add *
→ 세 개의 파일이 staging area로 이동
a.txt 파일 내 "hello, MJ" 텍스트를 추가함
→ "hello, world" 텍스트를 포함한 세 개의 파일(a.txt 포함) 모두 staging area에 존재
→ "hello, world hello, MJ" 텍스트를 포함한 a.txt 파일이 working directory에 tracked 상태로 존재
→ 각기 다른 내용의 텍스트를 포함한 a.txt 파일에 대해 버전 관리 중
git add *
→ 처음의 git add *와 같은 상태이나 a.txt의 파일은 추가된 텍스트를 포함하고 있음
git rm --cached *
→ 세 개의 파일 모두 working directory로 이동하고 untracked 상태임
예제) git directory(local)
예제) git directory(remote)

마. Commit

Commit의 단위는 하나의 모듈을 추가하나, 버그를 수정하는 식으로 의미를 부여할 수 있는 것이 좋다
버전 히스토리를 볼 때, 명확하게 작업의 흐름이 그려지는 commit이 생산성을 높인다
각 commit의 title에 맞는 변경사항만 추가되어야함
→ commit의 내용과 별개의 변경사항이 발생하면 코드리뷰 및 버전관리에 어려움이 생김
커밋 메시지의 내용은 주로 현재형 동사로 표현됨

바. SourceTree

Staging Area에 올라온 코드와 Working Directory에 있는 코드 사이에 일부 코드만 변경할 경우
→ Staging Area에 일부 코드만 올리고 싶은 경우
위와 같은 상황에서 보다 직관적으로 작업할 수 있음
다만, Git Command에 대한 학습을 충분히 한 후에 UI를 활용해서 작업 효율을 높이는 것을 권장
→ 트렌드는 빠르게 바뀌지만 기반 기술은 쉽게 바뀌지 않기 때문

사. Tag

version history의 주요 분기점에 대해 tagging하여 작업의 변경사항을 큰 틀에서 관리할 수 있다. 특히 Semantic versioning의 수단으로 많이 사용된다.
Semantic versioning: major, minor, fix의 카테고리에 따라 프로그램의 변경사항을 관리하는 방법
ex) v1.0.0 초기버전 → v1.0.1 초기버전의 일부 버그 수정 → v1.1.0 초기버전의 일부 기능 변경 → v2.0.0 전체적인 업데이트

아. Pull Reqeust

정의: 본인의 작업물에 대해서 상대방이 받아서 검토 및 병합을 요청
Pull Request 사용 이유
Push 권한이 없는 오픈소스 프로젝트에 기여하기 위한 목적으로 프로젝트 관리자에게 본인의 작업물을 검토 받기 위함
협업 시 파트너에게 개인의 작업물을 검토 받고 이상 없을 시 개인 작업물을 공동 작업물에 병합하기 위함

3. Branch

가. Why Branch

특정 제품 또는 서비스 개발을 병렬적으로 수행할 수 있기 때문에 작업 효율이 높음
ex) → v1.0.0 제품이 출시된 후, 1개의 버그가 발견되고 이와 별개로 1개의 기능이 추가되어야 한다. → 마스터 브랜치는 v1.0.0에 해당하는 commit을 바라보는 상황에서 이를 기준으로 2개의 branch 생성한다. 하나의 branch는 버그 수정과 관련된 것이고 다른 하나는 기능 추가와 관련된 것이다. → 버그 수정 branch에서 5개의 커밋을 수행하며 버그 수정을 완료하면 수정된 내용을 바탕으로 master branch에 commit한다. → 이 때, 조직에 따라 버그 수정 branch에서 쌓은 커밋을 처리하는 방법이 다르다. 엘리의 경우, 버그 수정 branch에서 생성한 5개의 커밋을 하나의 커밋으로 압축한 후, 이를 마스터 브랜치에 merge함.

나. Merge

Merge 동작 시 HEAD가 위치한 브랜치의 최신 커밋 위로 대상 브랜치의 커밋이 올라가는 방향으로 Git History 변경됨
fast-forward-merges: master branch가 바라보는 commit에서부터 파생된 branch(feature-a)를 master에 merge하고 해당 branch(feature-a)를 삭제하는 방법
→ 장점: merge 자체를 깔끔하게 진행 fast-forward-merges 가능한 구조의 경우, 엘리는 선호
→ 단점: version history에 merge에 대한 기록이 안남음
→ 이하 순차적으로 fast-forward-merges 진행
three-way merge: fast-forward-merge가 불가능한 경우, master branch의 모든 commit과 merging branch의 모든 commit을 포함하는 새로운 커밋을 생생하고 이 커밋을 마스터 브랜치가 참조함
→ fast-forward-merge가 불가능한 경우: 분기점 commit(merging branch가 master branch에서 떨어져 나온 commit) 다음에 master branch에 새로운 commit이 발생한 경우
예제) fast-forward-merges
a ← b← c ← d (HEAD → master) / d ← e ← f (feature-a)
→ git merge feature-a
a ← b← c ← d ← e ← f (HEAD → master, feature-a)
→ git branch -d feature-a
a ← b← c ← d ← e ← f (HEAD → master)
예제) no fast-forward-merges
a ← b← c ← d (HEAD → master) / d ← e ← f (feature-a)
→ git merge --no-ff feature-a
→ fast-forward 할 수 있는 구조이지만 일부러 메시지 남기는 경우에 해당함
→ commit message 작성 요구 발생 → merge 하는 이유에 대해 작성
a ← b← c ← d ← e ← f ← merge feature-a (HEAD → master) / d ← e ← f (feature-a)
→ git branch -d feature-a
a ← b← c ← d ← e ← f ← merge feature-a (HEAD → master)
에제) three-way-merges
a ← b← c ← d ← e ← f ← merge feature-a (HEAD → master) / d → g → h (feature-b)
→ git merge feature-b
→ commit message 작성 요구 발생 → merge 하는 이유에 대해 작성
a ← b← c ← d ← e ← f ← merge feature-a ← g ← h ← merge feature-b (HEAD → master) / d → g → h (feature-b)
→ → git branch -d feature-b
a ← b← c ← d ← e ← f ← merge feature-a ← g ← h ← merge feature-b (HEAD → master)

다. Conflict

Conflict: 두 개의 브랜치가 동일한 파일을 수정함에 따라 사용자에게 어떤 수정사항을 반영할지 확인요구
→ Conflict 발생: 두 개의 브랜치에서 동일한 파일을 수정할 때 발생
1. 개발용 브랜치(dev-pig, dev-live)는 farm이라는 모델을 공유함 2. 개발용 브랜치(dev-pig, dev-live)에서 특정 기능을 개발 완료하면 master 브랜치에 푸쉬함 3. dev-live에서 farm 모델에 대한 수정을 포함한 내용을 master에 푸쉬함 4. dev-pig에서 farm 모델에 대한 수정을 포함한 내용을 master로 푸쉬함 5. 4번 상황(dev-pig에서 머지) 시 Conflict 발생
Plain Text
복사
→ Conflict 미발생: 동일한 파일에 대해 수정이 발생하더라도 해당 수정 작업이 하나의 브랜치에서만 발생ㅇ한 경우 Conflict 미발생
1. 개발용 브랜치(dev-pig, dev-live)는 farm이라는 모델을 공유함 2. 개발용 브랜치(dev-pig, dev-live)에서 특정 기능을 개발 완료하면 master 브랜치에 푸쉬함 3. dev-live에서 farm 모델에 대한 수정을 포함한 내용을 master에 푸쉬함 4. dev-live에서 farm에 대한 수정한 부분을 dev-pig에 반영하기 위해 master의 변경사항에 대해 dev-pig에서 master를 머지함 5. 4번 상황(dev-pig에서 머지) 시 Conflict 미발생
Plain Text
복사
git으로 직접 해결: conflict 발생하면 두 개의 변경사항에 대해 직접 해당 파일을 수정해서 다시 merge 시도
main.txt라는 파일에 master branch와 feature branch가 동시에 수정하는 상황, HEAD→master
→ git merge feature
main.txt에 두 브랜치에서 변경하려는 부분에 대해 표기됨
두 브랜치의 변경사항 중 하나를 선택하여 해당 파일을 수정함
→ git add main.txt
→ git merge --continue
→ ff merge가 아니므로 merge commit message 작성
P4Merge 사용해서 해결
→ config에 merge tool로 등록
git config --global -e # 아래와 같이 config 설정 [merge] tool = p4merge [mergetool "p4merge"] path = "/Applications/p4merge.app/Contents/MacOS/p4merge"
Bash
복사
→ git mergetool
p4merge로 conflict 해결!
→ terminal 작업 재시작: ctl + c
→ git add .
→ git merge --continue

라. Rebase

Rebase: 분기점 commit 이후 마스터 브랜치에서 추가적인 commit이 발생한 경우, 분기점 commit을 마스터 브랜치의 최신 commit으로 변경하는 것
주의할 점: rebase에 따라 분기점 commit이 변경된 브랜치는 분기점 이후의 commit이 모두 변경된다.
→ 변경되는 commit의 내용은 이전과 같지만 commit hash value가 다른 commit으로 변경된다
→ 모든 commit은 불변성을 가지므로 수정되지 않고 같은 내용의 새로운 commit이 생성될 수 있다.
→ local repository에서 작업하는 경우나 remote repository지만 해당 브랜치를 혼자 작업하는 경우 유용함
→ 반면 다른 개발자와 함께 해당 브랜치를 공동작업하는 경우, 추후 master branch로 merge할 경우 conflict 발생 가능
rebase 예제
a ← b← c ← d ← e ← f (HEAD → master, feature-a) / d ← g ← h (feature-b)
→ git switch feature-b
a ← b← c ← d ← e ← f (master, feature-a) / d ← g ← h (HEAD → feature-b)
→ git rebase master
→ conflict 발생 시, 관련 파일 수정하고 'git add .' 'git rebase --continue'
a ← b← c ← d ← e ← f (master, feature-a) ← g' ← h' (HEAD → feature-b)
→ git switch master
a ← b← c ← d ← e ← f (HEAD → master, feature-a) ← g' ← h' (feature-b)
→ git merge feature-b
a ← b← c ← d ← e ← f (feature-a) ← g' ← h' (HEAD → master, feature-b)
rebase --onto: master branch에서 파생된 branch의 파생 branch에서 master branch의 최신 커밋으로 rebase
→ 특정 기능 개발에 지체될 경우, 그 기능의 UI등을 먼저 도입하는 상황 등에서 사용될 수 있음
→ 실무에서 자주 사용됨
rebase --onto 예제
a ← b← c (HEAD → master) / c ← d ← e ← f (feature-a) / e ← k ← t (feature-a-ui)
→ git rebase --onto master feature-a feature-a-ui
feature-a branch에서 파생된 feature-a-ui branch를 master branch의 최신 commit에 rebase
a ← b← c (master) / c ← d ← e ← f (feature-a) / c ← k ← t (HEAD ← feature-a-ui)
→ git checkout master
a ← b ← c (HEAD → master) / c ← d ← e ← f (feature-a) / c ← k ← t (feature-a-ui)
→ git merge feature-a-ui
a ← b ← c ← k ← t (HEAD → master, feature-a-ui) / c ← d ← e ← f (feature-a)

마. cherry pick

cherry pick: 커밋 내역 내 특정 커밋을 현재 브랜치로 가져오는 것
cherry pick 예제
a ← b ← c ← k ← t (HEAD → master, feature-a-ui) / c ← d ← e ← f (feature-a)
→ git cherry-pick f2123d
참고) f2123d: commit e에 해당하는 해쉬값
a ← b ← c ← k ← t (feature-a-ui) ← e (HEAD → master) / c ← d ← e ← f (feature-a)

4. Stash

가. Why Stash

커밋할 예정이 없는 일련의 작업에 대해 일시적으로 저장할 수 있는 기능
ex) 작업 중, 동료가 급하게 본인의 브랜치를 나의 워킹 디렉토리에서 확인할 것을 요청
ex) 버그 수정 중, 일련의 시도(bug fix1, bug fix2, 등등 커밋할 예정X)에 대해 저장하고 유사한 다른 시도를 이어서 작업할 경우

5. Undoing things

가. Rewriting history

Undoing의 의미는 version history에 수정을 가한다는 것
→ local repository에서 rewriting은 아무런 문제가 되지 않음
→ 다만, 이미 remote repo에 version이 올라가있고, 팀원과 공유된 상태라면 신중해서 작업해야 함

나. Undoing in local

커밋 복원 및 최신 커밋 취소
git restore
→ git restore a.txt: working directory 내 a.txt 변경사항 복원(git status에 modified, deleted 등으로 표시된 것)
→ git restore --staged a.txt: staged area 내 a.txt 변경사항 복원
→ git restore --source=경로 a.txt: 특정 경로의 커밋으로부터 해당 복원
→ 경로는 특정 커밋의 해쉬값이나 HEAD로부터의 상대경로가 될 수 있음
git reset HEAD a.txt: HEAD가 위치한 커밋 버전으로 a.txt파일 상태 초기화
→ 이후 git restore 명령에 따라 working directory 내 파일 복원 완료
git restore 예제
'a.txt' 파일을 삭제할 것
a ← b ← c ← k ← t ← e (HEAD → master)
→ git show HEAD~1
→ git show HEAD~2
a.txt 파일은 k commit에서 생성되고 t commit에서 수정된 것 확인
→ git restore --source=HEAD~3 a.txt
→ git status
'deleted: a.txt' 확인
reflog: Reference Log
→ HEAD가 가리키고 있었던 커밋의 해쉬값을 기록하고 있음
→ git reset으로 특정 commit으로 초기화된 상태에서 삭제된 커밋으로 되돌아 갈 수 있음
reflog 예제
1.
git reflog로 돌아갈 커밋 해쉬값 확인
2.
git reset --hard 해쉬값
local history extension 활용하면 commit 되진 않았지만 reset으로 지워진 파일도 복원할 수 있음
커밋 메세지 변경
git rebase -i hash: interactive rebase 활용
→ rebase는 기본적으로 상위의 참조값을 전부 수정하므로 remote 환경에서 사용X
rebase -i 예제
1) commit c의 메시지를 edited로 바꿀 것
a ← b ← c ← k ← t ← e (HEAD → master)
→ git rebase -i ewr3343
→ ewr3343: commit b의 해쉬값
→ 커밋 b에서부터 rebase해야 c를 핸들링할 수 있음
→ 커밋 c에 대해 수정 옵션 r, reword 선택 및 적용
→ 메세지 수정
a ← b ← edited ← k ← t ← e (HEAD → master)
2) commit c의 메시지를 삭제할 것
a ← b ← c ← k ← t ← e (HEAD → master)
→ git rebase -i ewr3343
→ commit c에 대한 수정 옵션 d, drop 선택 및 적용
→ conflict 발생(commit c에서 추가한 파일에 대해 commit k에서 수정함)
a ← b(HEAD) ← c ← k ← t ← e (master)
→ commit c를 drop함에 따라 삭제된 파일을 stage area에 추가함
→ git add .
→ git rebase --continue
→ f commit 생성(drop된 커밋의 conflict을 해결한 내용을 commit 메세지로 연결)
a ← b ← k ← t ← e ← f (HEAD → master)
3) commit c의 메시지를 c-1과 c-2로 분할할 것
a ← b ← c ← k ← t ← e (HEAD → master)
→ git rebase -i ewr3343
→ commit c에 대한 옵션 e, edit 선택
a ← b(HEAD) ← c ← k ← t ← e (master)
→ git reset -mixed HEAD~1
→ git add c123.txt
→ git commit -m "c-1"
→ git add c567.txt
→ git commit -m "c-2"
a ← b ← c ← k ← t ← e (master) / c-1 ← c-2(HEAD)
→ git rebase --continue
a ← b ← c-1 ← c-2 ← k ← t ← e (master)
4) commit c, k, t를 합쳐서 commit C로 만들기
a ← b ← c ← k ← t ← e (HEAD → master)
→ git rebase -i ewr3343
→ or git rebase -i HEAD~4
→ ewr3343: commit b의 해쉬값
→ commit c에 옵션 pick 적용
→ commit k, t에 옵션 s, squash 적용
pick c
squash k
squash t
→ 통합 커밋 메시지 작성(C)
a ← b ← C ← e (HEAD → master)

다. Undoing in remote

Revert: 특정 commit을 취소할 때, 기존 version history를 변경하지 않고 revert commit을 추가
→ remote repository에 작업부분을 취소한 것을 반영하는데 사용됨
→ revert hash: 일반적으로 사용됨,
→ revert --no -commit hash
여러 Commit 동시 Revert: git revert <마지막 커밋>..<최신 커밋>
→ <마지막 커밋>의 내용은 revert되지 않고 그 전까지의 커밋까지 revert 됨
→ 코드 참고

6. GitHub

가. GitHub 프로젝트를 로컬로 가져오기

clone: remote repository를 로컬로 복사함
→ 기본 명령: git clone URI
→ remote repository에서 하나의 브랜치만 복사하고 싶을 때
git clone -b {본인_아이디} --single-branch https://github.com/{본인_아이디}/{저장소 아이디} ex) git clone -b javajigi --single-branch https://github.com/javajigi/java-racingcar
Plain Text
복사
다른 remote repository를 local에 연결
→ 기본 명령
git remote add {저장소_별칭} base_저장소_url ex) git remote add upstream https://github.com/code-squad/java-racingcar.git
Plain Text
복사
→ base_repository의 특정 브랜치(본인_아이디)를 연결
git remote add -t {본인_아이디} {저장소_별칭} base_저장소_url ex) git remote add -t javajigi upstream https://github.com/code-squad/java-racingcar.git
Plain Text
복사
remote repository 확인
→ git remote -v: remote repository 정보 확인
→ git remote show: remote repository 별칭 확인
→ git remote show 별칭: 해당 remote repository에 대한 상세정보 확인
특정 remote repository를 로컬로 가져오기
git fetch upstream {본인_아이디} ex) git fetch upstream javajigi
Plain Text
복사
fetch한 repository를 local에 반영
git rebase upstream/본인_아이디 ex) git rebase upstream/javajigi
Plain Text
복사

나. 로컬 환경과 remote 환경의 계정 동기화

Github에 로그인한 계정과 git local 환경의 사용자 이름과 이메일 계정이 다른 경우, 일치시켜야 함
→ Githut에 커밋이 올라올 때 사용자명이 표기되므로 일치시켜야 다른 팀원이 혼란스럽지 않음
→ 수정명령: git config --global -e
→ [user] name = ~~ email = ~~
→ 수정확인: git config --global user.name

다. SSH key로 인증하기

서버에 public key, 클라이언트에 private key 적용해서 비밀번호 없이 인증할 수 있는 수단
remote repository에 push할 때, 비밀번호 입력을 생략할 수 있음
관련 절차는 GitHub 관련 페이지 참조

라. 로컬에서 완료한 프로젝트를 새로운 원격 저장소에 저장하기

GitHub에 새로운 저장소 만들기
로컬 환경과 리모트 환경 연결
→ git remote add {remote 환경 별칭} {remote 환경 URI}
git remote add test https://www.github.com/MJbae/git_test.git
로컬 환경의 파일을 리모트 환경으로 업로드
→ git push {remote 환경 별칭} {local 환경 브랜치}
git push origin main

마. fetch vs pull

fetch와 pull의 차이는 merge 기능의 유무
fetch: 리모트 환경의 커밋을 로컬 환경의 version history에 추가하지만 merge는 하지 않는다
→ HEAD는 리모트 환경에서 추가된 커밋이 아니라 로컬 환경의 커밋을 가리킴
pull: 리모트 환경의 커밋을 로컬 환경에 추가하면서 자동으로 merge한다
→ HEAD는 리모트 환경에서 추가된 최신 커밋을 가리키고 있음

바. fetch 상세

(Sync 작업) 리모트 환경의 작업에 대해 파악하고 싶은 때, fetch를 사용하여 로컬 환경의 작업과 비교할 수 있다
리모트 환경의 최신 커밋을 로컬 환경의 version history에 포함시킴
→ git fetch {리모트 환경 별칭}
git fetch test-origin
리모트 환경의 특정 브랜치의 커밋만 로컬 환경으로 가져옴
→ git fetch {리모트 환경 별칭} {리모트 환경 브랜치}
git fetch test-origin master

사. pull 상세

리모트 환경의 작업을 로컬 환경에 반영하고 싶을 때, pull을 사용하여 작업내용을 덮어쓴다
리모트 환경의 특정 브랜치의 커밋을 로컬 환경에 반영함
→ git pull {리모트 환경 별칭} {리모트 환경 브랜치}
git pull test-origin master

7. Merge Conflict

서로 다른 환경에서 같은 파일을 수정할 때, 어떤 수정사항을 반영할 지 알 수 없으면 Merge Conflict 발생
로컬환경과 리모트환경에서 같은 파일을 각각 다르게 수정할 때, 어떤 수정을 반영할 지 알 수 없음
서로 다른 브랜치에서 같은 파일을 각각 다르게 수정할 때, 같은 이유로
예제 1, 리모트 환경과 로컬 환경 차이에 따른 conflict 발생
1) 리모트 환경에서 a.txt 파일에 대해 수정함
2) 로컬 환경에서 a.txt 파일에 대해 수정하고 commit 명령
3) 로컬 환경에서 pull 명령
4) merge conflict 발생
예제 2: branch merge에 따른 conflict 발생
1) main branch에서 a.txt 파일 수정함
2) feature branch에서 a.txt 파일 수정함
3) main branch에서 feature branch merge
→ git merge feature
4) conflict 발생
예제 3: rebase에 따른 coflict 발생
본문의 '3-라.Rebase' 참고

나. 해결방법 1) Merge by manual operation

충돌난 부분을 직접 조정, merge commit 발생
서로 다른 환경에서 발생한 각각의 수정사항에 대해 반영할 부분을 직접 조정한다
예제
1) 리모트 환경의 a.txt 파일의 모든 내용 삭제
2) 로컬 환경의 a.txt 파일에 "12345" 추가
→ echo 12345 >> a.txt
3) 로컬 환경에서 commit 명령
→ git commit -am "Merge Test"
4) 리모트 환경의 최신 커밋을 로컬 환경에 반영
→ git pull origin-test master
5) conflict 발생
6) 로컬 환경에서 vim 편집기로 해당 파일의 충돌 부분 수정
→ vi a.txt
7) merge 작업 재시작
→ git add .
→ git merge --continue
→ or mege --continue 대신 git commit -m "~~" 사용 가능
→ or merge 작업 재시작 전체를 git commit -am "~~" 대체 가능
8) Merge에 따른 Update commit message 발생
→ 로컬 환경과 리모트 환경에 자동으로 반영되는 commit 발생
→ 자동 발생 커밋의 내용: Update a.txt
9) Mege commit 작성
→ 로컬 환경에만 반영됨

다. 해결방법 2) Merge by merge tool

본문의 '3-다.Conflict' 참고하여 merge tool 활용
해결방법 1과 다른 점은 vim 편집기 대신에 merge tool 활용한다는 것

라. 해결방법 3) Rebase

리모트 환경의 커밋을 로컬에 가져오고, 로컬 환경의 커밋을 그 위에 씌우는 방식
git pull --rebase 활용
장점: version history가 깔끔해진다
예제: 리모튼 환경과 로컬 환경에서 같은 파일에 대해 수정한 경우, rebase로 conflict 해결하는 방법
1) rebase 방식으로 원격 커밋을 로컬에 반영
→ git pull --rebase
2) conflict 발생
→ vim or mergetool 활용해서 해결
3) rebase 계속 진행
→ git rebase --continue
4) commit message 작성
5) 리모트 환경에 반영
→ git push test-origin main

8. 노하우

가. rm, mv < git rm, git mv

bash 명령으로 파일 삭제(rm) 및 파일명 변경(mv)을 하면 staging area에 따로 변경사항을 반영해야 함(add 명령으로)
git rm, git mv 명령으로 같은 작업을 수행하면 staging area에 자동으로 변경사항이 반영됨

나. 누가 언제 작업했지?

git blame 명령으로 누가 언제 작업했는 지 알 수 있음
git blame을 활용한 IDE extension 활용(ex. VSCode GitLens)

다. 문제의 Commit을 빠르게 찾아내는 방법

IDE debug tool로도 문제의 원인을 찾아내지 못했을 때 유용하게 사용할 수 있음
git bisect 명령으로 수많은 commit에 대해 이진탐색을 적용하여 문제의 커밋을 빠르게 찾을 수 있음
ex) 1. 1 ~ 100번 커밋 중에 문제의 커밋이 있다고 가정함.
→ 1번 커밋에서 문제 발생 확인, 100번 커밋에서 문제 없음 확인
→ 1번 커밋에 대해 git bisect bad, 100번 커밋에 대해 git bisect good
2. 이진탐색을 통해 중간중간의 커밋을 로컬에 적용하여 실행해보고 이상유무 체크 반복
→ 50번 커밋 good, 25번 커밋 bad, 33번 커밋 good, 18번 커밋 bad, 19번 커밋 good
3. 18번 커밋은 드러나지 않은 문제점을 가진 커밋으로 판단할 수 있음

라. Tig

터미널 UI interface tool, 커밋의 주요 정보를 쉽게 볼 수 있음
커밋내용과 커밋에 따른 변경사항과 수정된 파일등을 쉽게 수정할 수 있음
/ 활용하여 문자열 검색할 수 있음
vim 활용하여 파일 수정 가능

마. 가능하다면 터미널 기반으로 Git 사용 권장

대규모 프로젝트에서 그래픽 기반의 툴을 사용하면 툴의 속도가 매우 느려지는 경우 다수
그래픽의 버튼을 누르면 어떤 옵션으로 git 명령이 입력되는 지 알 수 없음
대부분의 작업은 터미널과 Tig을 주로 이용
소스트리의 경우, 로컬 환경에서 작업할 때, 많은 파일에 대해 일부만 stage area에 올리거나 commit할 때 사용함
conflict이 발생하는 경우, merge tool을 활용

바. (코드스쿼드) remote repository의 커밋을 local로 가져와서 반영하기

상황설명
1) 로컬환경에서 branch feature-a 작업 완료 및 branch feature-b 작업 중
2) 리모트환경 별칭 origin의 branch feature-a에 로컬환경의 같은 브랜치의 작업을 모두 push함
3) 리모트환경 별칭 origin의 branch main에 feature-a의 커밋이 반영 X
4) 리모트환경 별칭 origin은 리모트 환경 upstream에서 fork한 저장소임
→ local, origin, upstream의 주요 브랜치는 모두 service 브랜치임
5) 리모트환경 별칭 origin의 branch feature-a에서 리모트환경 별칭 upstream의 service에 PR 전송
6) 리모트환경 upstream 내 service branch에서 PR 확인 및 merge 승인
→ origin의 feature-a가 upstream의 service에 반영됨
→ upstream 내 service에 merge commit 생성
로컬에서 작업 시작
사전 작업) upstream을 remote에 추가
→ git remote add -t {본인_아이디} {저장소 별칭} {저장소 url}
→ git remote add -t MJbae
1) git switch service
→ 로컬환경의 service 브랜치로 전환
2) git fetch upstream service
→ origin의 feature-a의 커밋이 upstream의 service에 반영되어 있는 상태
→ 원격환경 별칭 upstream의 브랜치 service의 커밋에 대한 버전 히스토리를 로컬 환경으로 가져옴
3) git switch feature-a
→ 로컬의 feature-a 브랜치로 전환
4) git rebase upstream/service
→ upstream의 service에서 가져온 버전 히스토리의 최신 커밋 위에 로컬의 feature-a의 커밋을 쌓아올림
→ upstream의 service로 PR 날린 후에 변경된 사항이 있으면 로컬의 feature-a와 충돌이 날 것
→ vim or merge tool로 충돌 부분 직접 수정
→ 작업 완료되면 git add 로 충돌 파일을 staged area에 올림
→ git rebase --continue
→ rebase를 하게되면 그 위의 모든 커밋의 해쉬값이 바뀜, 다시 말해 커밋이 변경됨. 단, 내용은 변경되지 않음
→ rebase는 반드시 로컬 환경의 브랜치에서 나만 작업할 때만 사용함. 다시 말해, 공동으로 사용하는 브랜치에 rebase 사용 금지
5) git switch service
6) git push origin service
→ service에 대한 리모트 작업내용을 원격저장소 service에 최신으로 업데이트

Reference

Dream Coding Alley, Git 마스터 과정