Git (Advanced - branch & merge, remote repository)

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는 결국 현재 위치를 알려주는 것

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)
      1. 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를 지워줌!
      2. recursive : 재귀적 방법
        • master가 분기된 후 새로운 commit을 한 상황. 즉, master와 분기된 branch의 공통 조상이 있는 상황!!
        • 이 상황에서는 재귀적으로 공통조상을 찾아 새로운 commit과 동시에 merge를 진행! (이 둘을 합쳤다 라는 정보를 가지고 있는 별도의 commit!)
        • !!! 즉, 새로운 commit이 생기는 것 !!!

  • 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)
    1. 원격 저장소에 있는 코드를 받아 오기 (clone) OR 원격 저장소에 Repository 생성 후 연결, 연결된 상태에서 작업
      • github에 새로운 repository를 생성
      • git clone “원격 저장소 주소” [‘저장하고 싶은 directory’]
        • git clone https://github.com/git/git.git . : ‘.’ 은 현재 경로
      • 후, git이 존재하는 파일로 경로 이동.
      • 받아 온 후 추가로 remote 할 필요 없이 자동으로 해당 경로를 origin이라는 별명으로 remote 함.
    2. 이미 작업이 이루어진 상태에서 원격 저장소에 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에 저장됨.
  • 백업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에 넣어주기만 하면 됨.
          1. .ssh/id_ras.pub 의 내용(인증키)를 복사
          2. 자신의 Github에서 ‘Settings’로 들어간 후 ‘SSH and GPG Keys’ 항목으로 이동
          3. New SSH key 를 통해 Key값에 자신의 id_ras.pub 내용을 입력.

        → 원격저장소인 Github에 자신의 ssh public key(id_ras.pub)를 등록한 것!

        → 즉, ssh private key(id_ras) 를 가지고 있는 컴퓨터는 별다른 인증없이 바로 원격저장소에 접속 가능!

      • 연결된 Github(원격 저장소) 사용하기
        • repository의 HTTPS 링크가 아닌 SSH 링크로 clone

          • 이제 add commit, push 를 해주면 잘 되는 것을 확인 가능

협업

  • 협업
    • 하나의 Github(원격저장소) repository를 git_home(집) 과 git_office(회사)에 clone하여 집에서 작업하던 것들을 회사에서 이어 작업할 수 있도록 협업을 진행하는 시나리오. [home : 집에 있는 컴퓨터, office : 회사에 있는 컴퓨터라고 가정하고 진행]
    • git clone을 통해 원격저장소 연결. (home, office 둘다.)
    • 방법
      1. 먼저 두 컴퓨터(directory)에 clone을 통해 github repository를 받아옴
      2. 집에서 코드를 작성해서 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)에 저장.
        • % 이런 식으로 다른 개발자들과도 협업을 할 수 있고, 나 자신이 이동해서 개발 진행 가능.
  • 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 : 태그를 포함해서!
    • 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는 masterdevelop.
  • 실제로 개발이 진행되는 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 까지 진행)