Git의 혁신 (branch & merge)
branch
- branch : 작업을 진행하다 필요에 의해 작업을 분기하는 현상
- 보통 어떤 이슈가 발생했을 때, 그 이슈를 처리하기 위해 branch를 사용하기도 하고, 어떤 실험이 필요할 때 branch를 사용하기도 함
- 새로운 branch (new_feature): 용도에 맞게 분기되어 변형된 작업물
- 기존 branch (master): 분기된 작업물의 근간이 되는 작업물의 변형
- % 만약 분기되지 않고 하나의 작업물이 쭉 변형되는 것 → 하나의 branch만을 가지고 있는 것
- branch 만들고 사용해보기
-
master : 근간이 되는 branch. git을 사용하는 그 순간부터 주어지는 기본 branch
git branch “브랜치 이름”
: 새로운 branch 생성-
git branch
: 현재 존재하고 있는 branch들 확인- 현재 사용하고 있는 branch에 * 표시가 있음.
-
git checkout ‘브랜치 이름’
: 다른 branch로 이동하기 (git checkout -b new_feature
: branch 생성 및 이동을 한번에 하기 )- 이때, 존재하는 파일을 확인하거나 commit log들을 확인하면 master와 동일. 즉, 현재 branch를 이동했을 뿐이지, 이동해서 어떠한 작업도 해주지 않았기 때문에 master와 동일한 상태. 즉, 분기가 시작되는 시점인 것.
- 만약 new_feature에서 파일을 추가하거나 수정하거나 삭제한 후 branch를 master로 checkout한 후 master branch의 commit log, 파일들을 확인한다면, new_feature에서 했던 활동들이 반영이 안되는 것을 확인할 수 있음 → 이게 바로 분기되어 작업을 진행하는 것. → branch의 이점!
- 추가로
git checkout ‘commit주소’
를 명령한다면, 해당 commit의 상태로 이동하는 것이 가능! (reset이 아님!) → 그 commit 시점의 코드들을 확인 및 현재 commit과 비교할 수 있음. (코드리뷰에서 유용한 기능)
-
- branch 정보 확인
-
git log --branches --decorate
: 모든 branch들의 commit 확인- 현재 branch의 최신 commit은 HEAD → 를 통해 표시. (즉, 현 시점의 위치를 알려주는 것)
- 오른쪽 decorate을 통해 어떤 branch에서 commit이 이루어졌는지 확인 가능 (branch의 최신 commit을 표시)
git log --branches --decorate --graph
: branch들의 흐름을 graph로 보이게 끔 해주는 기능-
이는 분기가 일어나고 각 branch가 뻗어 나갔을 때 더 시각적으로 확인이 가능
-
-
--oneline
-
-
-
git log master..new_feature
: 두 branch의 commit 차이 확인- B..A : A에는 있고 B에는 없는 commit 차이
- git log -p B..A : 더 자세한 차이 확인 가능 (변경 사항 확인 가능)
- git diff B..A : diff 와 동일하지만, branch를 대상으로 진행하는 것
-
- stash : 해당 branch에서 작업이 끝나지 않은 상황에서 다른 branch로 이동하고 싶을 때 (즉, commit하기도 애매하고 commit을 안하기에는 이동이 불가능할 때) stash를 통해 작업을 숨겨놓고 이동이 가능!
-
branch에서 commit하지 않고 다른 branch로 넘어가게 되면 이전 branch에서 commit하지 않은 그 파일이 다른 branch에 영향을 미치게 됨.
다른 branch에서 f4라는 파일을 생성하고 commit하지 않은 상태에서 master로 이동한 상황. → f4.txt 가 master에게 까지 영향을 미침!
-
이런 상황에서 stash를 사용해야 되는 것!
- 여기서 주의할 점은 stash는 적어도 버전관리를 하는 상태인 놈들, 즉 add를 한번 했던 애들에 대해서만 적용이 가능함!
git stash [save]
: 현재 작업중인 것을 일단 저장해주고 다른 branch에 영향을 미치지 않도록 숨겨줌.git stash apply
: 숨겨놓은 작업을 다시 사용할 수 있도록 하는 것- 하지만, apply 해준다 해도 해당 stash는 계속 남아 있음. (git stash list를 통해 stash 목록을 확인할 수 있음) → 명시적으로 삭제가 필요
git stash drop
: 가장 최신 statsh를 삭제해 주는 것.git stash pop
: apply 와 drop을 동시에 진행해 주는 것.
-
- branch의 원리
- HEAD : git 을 처음 생성하면 반드시 생기는 파일
- 이 head는 master라는 파일을 가리킴
- 이 master라는 파일은 가장 최신 commit을 가리킴
- 즉 HEAD를 통해 가장 최신 commit이 무엇인지 알려주는 것
- 여기서 새로운 branch를 생성하게 되면 ref/heads/new_feature 라는 새로운 파일이 생김.
- 즉, branch들은 이 refs/heads 하위로 생성 되는 것.
- 여기서 만약 새로운 branch로 이동하게 된다면 HEAD 파일은 해당 branch 파일을 가리키게 되고, 그 branch파일은 해당 branch에서 이루어진 가장 최신 commit을 가리키는 것
- 이 HEAD를 통해서 내가 현재 가리키고 있는 branch의 위치를 알 수 있는 것! (이 HEAD가 있기에 위의 senario가 가능한 것!)
- 또한 graph를 통해 branch들의 commit log들을 확인할 때, HEAD는 결국 현재 위치를 알려주는 것
- HEAD : git 을 처음 생성하면 반드시 생기는 파일
merge
- merge : 분기된 branch들을 병합시키는 것
- master에 병합하기. (master가 기준이 되는 것)
- new_feature의 내용이 master에 병합되는 것!
- merge를 진행하면 각 branch가 통합되는 하나의 새로운 commit이 생기게 됨. 즉, 이 commit은 2개의 parent를 가지게 되는 것!
-
new_feature에 병합하기 (new_feature가 기준이 되는 것)
- master와 완전히 동일한 내용을 담도록 설정.
git branch -d new_feature
: 필요 없는 new_feature branch 삭제하기- merge가 발생했을 때 만날 수 있는 경우들 (Branch & Merge Senario)
- fast-forward : 빨리 감기
- master에서 다른 branch로 분기가 된 후 master는 어떠한 commit도 이루어지지 않은 상태에서 master에 해당 branch를 merge시킬 때, master가 해당 branch가 가리키는 commit으로 빨리감겨지기 때문에 fast-forward 라고 지칭.
- !!! 별도의 commit을 생성하지 않음 !!! (기존의 case에서는 merge를 하면 별도의 commit이 생성되었었음)
- master가 가리키는 commit을 바꾸기만 해서 빨리감기, fast-forward라고 지칭
- 이제 hofix는 쓸모없는 branch이므로 hotfix를 지워줌!
- recursive : 재귀적 방법
- master가 분기된 후 새로운 commit을 한 상황. 즉, master와 분기된 branch의 공통 조상이 있는 상황!!
- 이 상황에서는 재귀적으로 공통조상을 찾아 새로운 commit과 동시에 merge를 진행! (이 둘을 합쳤다 라는 정보를 가지고 있는 별도의 commit!)
-
!!! 즉, 새로운 commit이 생기는 것 !!!
- fast-forward : 빨리 감기
- master에 병합하기. (master가 기준이 되는 것)
- conflict -> branch 충돌 해결 (merge 할 때의 conflict)
- 두 branch이 동일한 파일이 없는 상태에서 병합을 하게 되면 충돌은 일어나지 않음.
- 하지만 두 branch에 같은 파일이 있는 상태라면 충돌이 일어날 수 있음
- 예를 들어 두 branch가 공통으로 가지고 있는 common.txt가 있는 상태에서 분기가 되었다고 가정했을 때, 분기 된 후 하나의 branch에서만 해당 common.txt 를 수정하고 다시 병합한다면 (즉, 두 branch들 중에서 하나의 branch에서만 수정이 진행되고 merge한다면) , 충돌은 발생하지 않고 해당 파일에 대한 변경사항을 자동으로 적용하여 merge 해줌.
-
하지만, 두 branch 모두에서 common.txt를 수정(both modified)했다 하고 병합을 진행하면. 충돌 발생!
→ 이렇게 되면 내가 직접 자동 병합된 부분을 수정해주고 따로 commit을 진행해 주어야됨!
→ 두 branch 모두에서 변경되었기에 conflict 발생! → 자동 파일 merge 실패! → 직접 변경사항을 처리 및 수정하고 add, commit 해주어야 됨!
→ 또한, 충돌이 발생하면 해결되기 전까지 해당 branch 옆에 merging이라는 표시가 뜸! (병합 중!)
→ common.txt 파일의 conflict가 난 상태. ==== 라는 구분자를 기준으로 위의 HEAD까지가 현재 branch(병합의 기준)에서 수정한 부분, 구분자 기준 아래 exp 까지가 외부 branch(병합을 하는)에서 수정한 부분. 즉 이를 통해 충돌이 난 부분을 알려주고, 이 부분에 대해서 직접 처리하고 사용자에게 맡기는 것.
→ 수정을 한 후 add, commit을 진행하면 conflict는 해결!
→ 이런 게 가능한 이유는, 병합이 진행 될 때의 index파일은 둘의 공통이였던 원본 파일(1, Base), 병합이 진행되는 branch의 변경된 파일(2, Local), 병합을 하는 branch의 변경된 파일(3, Remote) 모두를 저장하고 있기에 이렇게 자동 병합 및 conflict 인지가 가능한 것! (3 way merge 기법)
→ 이런 merge와 conflict를 중점적으로 다뤄주는 Tool들도 있음
-
2 way merge VS 3 way merge
- reset
- reset의 원리 또한 HEAD와 관련됨.
- reset은 이전의 commit으로 돌아가며 그 commit의 이후는 숨겨버리는 기능, HEAD는 현재 branch의 최신 commit이 어디인지 가리키는 파일.
- 즉, reset을 진행하면 HEAD가 현재 branch의 최신 commit을 reset에서 설정한 commit으로 가리키게 끔 변경하여 돌아가는 것!
-
또한, 이런 reset이 진행 되었을 때, 해당 commit으로 돌아가고, 그 이후의 commit은 삭제되는 것이 아닌 숨겨지는 것. 그렇기에 reset을 취소할 수 있는 것! 그 취소된 commit 중 가장 최신 commit이 저장된 것이 바로 ORIG_HEAD (origin head, 원래의 head!) . 이를 이용해서 reset을 취소할 수 있음! (병합 또한 ORIG_HEAD로 병합 이전으로 복구가 가능!)
-
reset 취소하기! → 똑같이 reset을 이용해서 ORIG_HEAD로 복구하면 됨!
-
혹은 현재 branch에서 했던 모든 작업들이 담긴 log를 통해서도 복구가 가능!!
→ git reflog 를 통해 확인 가능
원격 저장소
- 원격 저장소 (Remote Repository)
- 현재 사용 중인 local 저장소가 아닌 따로 연결되어 있는 다른 저장소
- 진행 중인 작업을 백업하거나 다른 사람들과 협업을 하기 위해 사용
- 협업은 다른 개발자들과 할 수 있는 것이지만, 사실 나 혼자서도 협업이 가능 → 집에서 개발을 진행하다가 회사에 가서 회사 컴퓨터로 이어서 개발을 진행하는 상황 등
백업
- 백업 → 원격 저장소(Github) 연결 (clone with HTTPS)
- 원격 저장소에 있는 코드를 받아 오기 (clone) OR 원격 저장소에 Repository 생성 후 연결, 연결된 상태에서 작업
- github에 새로운 repository를 생성
git clone “원격 저장소 주소” [‘저장하고 싶은 directory’]
- git clone https://github.com/git/git.git . : ‘.’ 은 현재 경로
- 후, git이 존재하는 파일로 경로 이동.
- 받아 온 후 추가로 remote 할 필요 없이 자동으로 해당 경로를 origin이라는 별명으로 remote 함.
- 이미 작업이 이루어진 상태에서 원격 저장소에 Repository 생성 후 연결
- github에 새로운 repository를 생성
- remote를 통해 현재 local 저장소에 원격저장소 연결하기
git remote add origin ‘원격저장소 주소’
- remote add : 원격저장소를 연결하겠다.
- orgin ‘원격저장소 주소’ : 해당 주소에. 근데 주소가 기니 앞으로는 이 주소를 origin 이라고 부르겠다.
- remote를 통해 하나의 local 저장소에 여러 원격 저장소에 연결 가능
- remove를 통해 연결 해제
git remote remove origin
- remote remove : 원격저장소를 제거하겠다. (연결 해제하겠다)
- origin : 해당 별명을 가지고 있는 원격저장소를!
- push를 통해 local 저장소의 변경사항을 원격저장소에 저장하기 (백업)
git push -u origin master
- push : 넣겠다
- -u origin master : origin이라는 별명을 가지고 있는 원격저장소의 master branch에 (-u 는 가장 처음 push를 진행할 때, 혹은 추후에 다른 branch나 다른 원격저장소에 push할 때 처음에만 사용하면 됨)
- 추후에는 git push 만 적어주어도 해당 origin 저장소의 master branch에 저장됨.
- 원격 저장소에 있는 코드를 받아 오기 (clone) OR 원격 저장소에 Repository 생성 후 연결, 연결된 상태에서 작업
- 백업 → ssh를 통한 원격 저장소 연결 (clone with SSH)
- secure shell(ssh)
- 연결(remote), push, pull 을 할 때 따로 로그인을 하지 않아도 된다는 장점이 있음. 즉, 자동으로 로그인해주는 것.
- HTTPs 와 아예 다른 방법이라기 보다는 대등한 관계의 서로 다른 통신이라고 생각하면 됨.
- 연결 방법
-
사용자 경로에 ssh-keygen 을 통해 다른 컴퓨터(원격 저장소)로 접속할 수 있는 하나의 비밀번호가 생성됨. (기계적으로 굉장히 복잡한 비밀번호)
git checkout -b new_feature
: branch 생성 및 이동을 한번에 하기-
자신의 사용자(User) 경로에 .ssh 에 해당 비밀번호 파일이 생성됨
- id_rsa : 기밀의 정보가 들어 있는 것 → 나의 컴퓨터에 저장하는 것
- id_rsa.pub : 기밀이 아닌 공개된 정보가 들어 있는 것 → 원격 저장소에 저장하여, 나의 컴퓨터와 접속할 때 인증되도록 하는 것.
- 즉, 내가 원하는 원격저장소의 적당한 파일에 해당 id_rsa.pub를 저장해두면, 따로 로그인없이 접속이 가능한 것
- Github(원격저장소)에 ssh로 연결하기
- 방금 생성된 id_rsa.pub(public key)의 내용(인증키)을 Github에 넣어주기만 하면 됨.
- .ssh/id_ras.pub 의 내용(인증키)를 복사
- 자신의 Github에서 ‘Settings’로 들어간 후 ‘SSH and GPG Keys’ 항목으로 이동
- New SSH key 를 통해 Key값에 자신의 id_ras.pub 내용을 입력.
→ 원격저장소인 Github에 자신의 ssh public key(id_ras.pub)를 등록한 것!
→ 즉, ssh private key(id_ras) 를 가지고 있는 컴퓨터는 별다른 인증없이 바로 원격저장소에 접속 가능!
- 방금 생성된 id_rsa.pub(public key)의 내용(인증키)을 Github에 넣어주기만 하면 됨.
- 연결된 Github(원격 저장소) 사용하기
-
repository의 HTTPS 링크가 아닌 SSH 링크로 clone
- 이제 add commit, push 를 해주면 잘 되는 것을 확인 가능
-
-
- secure shell(ssh)
협업
- 협업
- 하나의 Github(원격저장소) repository를 git_home(집) 과 git_office(회사)에 clone하여 집에서 작업하던 것들을 회사에서 이어 작업할 수 있도록 협업을 진행하는 시나리오. [home : 집에 있는 컴퓨터, office : 회사에 있는 컴퓨터라고 가정하고 진행]
- git clone을 통해 원격저장소 연결. (home, office 둘다.)
- 방법
- 먼저 두 컴퓨터(directory)에 clone을 통해 github repository를 받아옴
- 집에서 코드를 작성해서 repo에 올린 후 이 코드를 받아 office에서 이어 받아서 개발하는 상황
- home
- 파일을 하나 생성하여 해당 home local repo에 올린 후 (add, commit)
- 이를 push를 통해 원격저장소(github)에 올려줌 [clone 으로 원격저장소를 연결했다면, 자동으로 origin이라는 별명으로 remote되기에 이를 그냥 push 하면 됨 (master라는 branch에 자동 연결)]
- office
- clone을 한 상태이기에 pull을 통해 현재 원격 repository에 변경되어 있는 파일들을 가져옴. (home을 통해 변경하고 push된 파일들)
- 가져온 후 이를 수정한 후 local repository에 add 및 commit 후 다시 push하여 원격 저장소(Github)에 저장.
- % 이런 식으로 다른 개발자들과도 협업을 할 수 있고, 나 자신이 이동해서 개발 진행 가능.
- home
- pull VS fetch
- pull : 원격저장소의 현상태를 그대로 다운로드 받고, 원격저장소의 최신 commit이 어느 위치에 있는지 기록하며, 지역저장소까지 그 최신 commit의 위치로 설정함. (즉, 지역 저장소는 pull을 하면 원격저장소와 동일한 상태가 되는 것) → 즉, pull은 원격저장소의 branch(origin/master)를 그대로 받아와 병합까지 진행한 것!
- fetch : 원격저장소의 현상태를 그대로 다운로드 받고, 원격저장소의 최신 commit이 어느 위치에 있는지 기록은 하지만, 지역저장소에는 영향을 주지 않는 것. (즉, 지역 저장소는 fecth를 해도 이전과 동일한 상태) → 즉, fetch는 원격저장소의 branch(origin/master) 를 받아와 병합하지 않은 상태로 둔 것!
- 이는, 원격저장소의 내용과 지역저장소의 내용의 차이점을 비교할 수 있음. → git diff HEAD origin/master ( HEAD : 지역 저장소의 현 commit, origin/master : 원격 저장소의 현 commit)
- 차이를 확인한 후 그냥 받아와도 되겠다 판단이 들면 원격저장소의 내용을 현 branch에 병합시키면 됨→ git merge origin/master
- Tag
- branch와 유사하면서 다른 기능
- branch는 항상 그 branch에 해당 하는 최신 commit을 가리킴. 즉, 언제나 다른 commit (최신 commit)을 가리킴
- tag는 tag 한 시점의 commit을 가리킴. 즉, 한번 tag를 지정하면 언제나 똑같은 commit을 가리킴 (release의 version개념)
- 그래서 보통 사용자들에게 배포할 때 사용
- Tag 달기
git tag 1.0.0 main → light version
- tag : 태그를 달겠다.
- 1.0.0 : 해당 이름으로
- main : 현재 main branch가 가리키고 있는 commit에. (commit의 주소를 직접 지정하여 이전 commit에도 tag를 달 수 있음)
그럼 이런식으로 해당 commit에 tag가 붙게됨.
git tag -a 1.0.1 -m ‘bug fix’ main
→ annotation version- tag : tag를 달겠다.
- -a : annotation version 으로! → 해당 태그에 대한 설명을 주석으로 달 수 있음.
- -m : 주석의 내용은
- ‘message’ : 해당 message로 주석을 달겠다
- main : main branch가 가리키는 commit에
- Tag 원격 저장소에 올리기
- 기본적인 push를 하면 올라가지 않고 –tag를 붙여줘야지 올라감
git push -u origin main --tag
- push : 원격저장소에 올리겠다.
- -u origin main : origin repository의 main branch에
- –tag : 태그를 포함해서!
- 기본적인 push를 하면 올라가지 않고 –tag를 붙여줘야지 올라감
- Tag 삭제하기
git tag -d 1.1.1
- -d : 삭제한다
- 1.1.1 : 해당 이름을 가진 tag를
- Rebase
-
merge와의 차이
% m을 f로 병합한다 가정
merge같은 경우 새로운 commit을 생성하고 둘을 병합.
rebase같은 경우 base(m과 f의 공통 조상인 commit)를 기준으로 그 base이후의 f에서 이루어진 commit을 따로 임시저장소(patch)에 저장 후 이들을 없애주고 f의 위치를 m으로 이동시킴.
그 후 patch에 있는 f의 commit들을 현 branch에 하나 하나 병합 시켜주며 f를 해당 commit으로 이동시켜줌. 이걸 patch에 있는 모든 commit에 대해 진행
결론적으로, merge와 rebase의 f가 가리키는 최신 commit은 같은 commit. (즉, 둘 다 같은 내용을 가지고 있다는 것)
차이점은 merge는 history가 병렬적으로 나열되어 있어서 history 확인이 까다롭지만, 사용하는 것이 쉽고 간결하고 rebase는 history가 직렬적으로 있어 history 확인이 용이하지만, 사용하는 것이 까다롭다는 점
- 사용
-
git rebase master
(checkout rb) → rb에 master 병합 (using rb) -
git merge rb
(checkout master) - fast-foward를 통해 master를 rb의 위치로 오게끔 하기
-
- 충돌 해결하기
- merge는 위에서 봤듯이 충돌이 발생하면 그 지점에서 생긴 통합적인 충돌만 해결하면 됐었음. (병렬적으로 진행하기에)
- 하지만 rebase는 직렬적으로 진행하기에, 병합 과정에서의 그 직렬의 history에 발생하는 모든 conflict를 처리해줘야됨. 또한, 이 모든 순차적인 conflict를 처리해줘야지 마지막으로 병합 되는 것을 확인할 수 있음.
- % rebase 같은 경우 까다롭기에 공유하는 repository에서는 가급적이면 그냥 merge를 사용하는 것이 좋음
-
총 정리
% 지금까지 배운 모든 개념을 한번에 정리한 그림. 굉장히 유용하고 직관적임
GIt Flow
- 가장 중요한 branch는 master와 develop.
- 실제로 개발이 진행되는 branch는 develop branch
- 특정한 기능(좀 큰 기능)을 추가하거나 수정할 때는 별도의 feature라는 branch를 생성하고 그 branch에서 개발 진행. 기능에 대해 개발하고 그 기능을 적용하기 위해 develop branch에 병합.
- 버그나 작은 기능의 수정 등은 develop에서 진행.
- 작업을 마무리하고 사용자들에게 배포하는 시점이 오면 (웹 서비스라 하면, 그 서버에 해당 작업을 반영하는 순간의 직전) release라는 branch를 만들어 그 branch를 통해 release 진행.
- release 후 여러가지 버그 수정, 문서 업데이트 등은 그 release branch 안에서 진행. 이 작업들은 develop에 반영하기 위해 지속적으로 develop에 merge 시킴 (나중에 한번에 merge하면 conflict 가 자주 발생하므로)
- 실제로 배포를 하려고 할 때는 해당 release의 작업을 master에 병합하여 실제 배포를 진행. (Tag도 함께 사용.→ 기록)
- 즉, master는 사용자에게 제공되었던 그 버전들만을 모아놓는 branch임
- 사용자에게 버전을 제공하고 있는 도중 긴급하고 사소한 버그에 대해선 hotfix branch에서 빠르게 처리하고 다시 master로 release 진행.
-
GitFlow를 진행한 결과 log (원격저장소에서 pull request, merge 까지 진행)