git을 이용한 upstream과 origin 동기화 방법 및 conflict 최소화 전략

pengooseDev·2023년 3월 13일
5
post-thumbnail

항상 새로운 팀원이 생길 때마다 흐름을 말로 설명하거나 Notion에 정리해주는 것에 한계를 느꼈다. 블로그 글로 작성 후 링크를 전달해주는 것이 더욱 효율적이고, 누군가는 해당 글을 보고 flow를 익힐 수 있을 것 같아 글로 남기고자 한다.


0. 코드 가져오기

  1. uptream의 코드 fork 뜨기(fork뜬 저장소는 본인의 origin)
    (원본 저장소 관리자는 원본 코드가 origin이지만 직접 origin에 코드를 병합하진 않는다. 해당 사진은 개인 저장소처럼 그려두었지만 보통 organization을 만들어 organization의 소속으로 해두는 편! (팀장도 fork)

  2. git Clone 이후 로컬 환경에서 IDE로 열기

1. git이 remote하고있는 상태 확인

git remote -v

fork한 origin 저장소를 git clone을 할 경우, git은 upstream(원본코드)에 변화가 생길경우 이를 확인할 방법이 없다.

팀원들의 PR 및 merge로 upstream의 코드에 변동사항이 발생할 경우, 이를 동기화해야한다. 따라서 upstream을 remote해준다.

2. 동기화할 원본 코드에 repo 연결

git remote add {이름} {주소}
git remote add upstream {원본 저장소 url}

3. 작업환경(branch) 생성

git checkout -b feature/{작업자 이름}/{작업내용}

git checkout feature/pengooseDev/TicketBoard


코드 작성 중 원본(upstream) 저장소에 변경이 생긴 경우.

JeJe가 코드를 작성해서 코드의 버젼이 v1.1이 되었다고 가정하자.

우리는 두 가지를 생각해야한다. (git이랑 github이랑 다르다. git은 우리 컴퓨터의 저장소로, github은 온라인 저장소로 이해하면 편하다)
1. github의 코드
2. git(로컬. 즉, VSC로 작성한 코드)

부연설명(생성 순서)

  1. github/origin/v1.0
    • JeJe가 github에서 upstream을 fork한 시점에서 생성된다.
  2. git/origin/v1.0
    • JeJe가 git clone을 VSC에서 하는 순간 로컬에 파일로 생긴다.
  3. git/feature/v1.0
    • JeJe가 git으로 git checkout -b feature을 하는 순간 생긴다.
  4. git/feature/v1.1
    • JeJe가 VSC에서 새로운 코드를 작성하는 순간 생긴다.
  5. github/feature/v1.1
    • JeJe가 git에서 작성한 코드를 github의 feature branch로 push하면 생긴다.
  6. PR
    • JeJe의 github에서 feature branch에 존재하는 코드는 v1.1이다(현재 사진 상황)
      JeJe는 이 코드를 upstream의 branch중 하나로(develop이라 가정)PR을 날린다 가정하자.
      PR이 생성되고 merge되었을 때, upstream의 branch중 merge를 받은 branch(develop)만 v1.1인 상태이고 나머지 branch는 v1.0이다.

사실 엄밀히 말하면 JeJe가 생성한 PR이 merge된 시점에서 upstream의 develop branch에 존재하는 코드는 v1.1이 아니라 v1.1.1이다. PR이 merge되면서 merge되었다는 commit이 추가되기 때문이다.(git이 처음이라면 이해하지 않고 넘어가도 큰 문제가 되지 않는 부분)


upstream에 변경사항이 발생하면 팀원들은 반드시 빠르게 동기화한다. (중요)

그래야 conflict가 최소화된다.
upstream의 코드를 병합하는 과정에서 Conflict가 날 수 있다.
Conflict를 최소화하는 방법과 전략은 여러가지이다.(추후 설명)
일단 행동방식부터 확인하자.

Pengoose와 팀원이 할 일

Pengoose(또는 팀원)의 시점으로 바라보자.
다른 팀원(JeJe)이 날린 PR이 upstream에 merge되었다면?

Pengoose는 git(local)github의 코드를 upstream의 코드로 동기화를 시켜주어야 한다.
아래의 순서를 따르도록 한다.

1. 작성하던 코드를 일단 숨겨주자.

(현재 작업하고 있던 branch가 feature branch라고 가정한다)

git stash


2. main 브랜치로 전환하기

git checkout main

필자는 main branch를 동기화를 위해 코드를 받아오는 용도로만 사용한다.
작성한 코드가 main으로 push되는 일은 전혀 없다.

현재 코드의 상황은 아래와 같다.


3. 코드 받아오기

우선 upstream의 변경된 코드인 v1.1(주황색)을 git main으로 받아오자

git fetch upstream main

의미는 아래와 같다.
git(을 이용해) fetch(데이터를 가져올게요 어디서?) upstream(의) main(branch에서!)

main branch로 서술했지만 gitflow를 사용한다면 아마 develop branch에서 받아오게 될 것이다. PR이 merge된 브랜치에서 받아오면 된다.


4. merge (git 로컬 main branch 업데이트)

git merge upstream/main

의미는 아래와 같다.
git(을 이용해 현재 내가 있는 브랜치에) merge(코드를 병합할게요. 무슨코드를 병합하냐면 fetch해온 코드 중) upstream/main(upstream의 main브랜치에서 받아온 코드를요!)

즉, 3번에서 fetch해온 코드(upstream/main)을 현재 내가 있는 브랜치(main branch)에 병합하는 과정이다.
fetch를 upstream의 develop 브랜치에 했다면 git merge upstream/develop가 맞는 표현!

여기까지 진행했다면 현재 상황은 아래와 같다.


5. push (github main branch 업데이트)

다시 말하지만 git은 로컬이고 github은 인터넷 원격 저장소를 말한다. 둘이 다른거다!
git을 이용해 upstream github에 저장된 코드를 local에 가져오고 병합했다. 나의 github엔 적용이 안되어있으므로 이를 push하여 적용시켜주는 과정이다.

git push origin main

그 결과는 아래와 같다.


6. 작업 브랜치 이동

새로 업데이트 된 코드를 feature 브랜치에서 병합하여 기존에 작성하던 코드와 합친다고 가정하자.

git checkout feature


7. git의 feature branch 동기화

origin main에서 코드를 업데이트 해두었기 때문에 origin main에서 코드를 동기화하자. (물론, upstream에서 또 받아와도 되지만 귀찮다. main에 동일한 코드를 백업해두었으니 main에서 가져온다.)

git pull origin


8. github의 feature branch 동기화

5번과 동일한 논리이다. github feature branch 업데이트 해주자.

git push origin feature

뿌뿌뿌-🥳 코드를 upstream과 동기화하는 여정이 끝났다.
하지만 아직 한 발 남았다.
바로 conflict다.


9. 작업중이던 코드 꺼내오기

git stash pop

숨겨두었던 코드를 꺼내자. 여기서 conflict가 날 수 있다.
conflict는 언제 발생하느냐?

현재 코드와 새로운 코드가 동시에 변경사항이 있는 경우

쉽게 말해 코드 변경사항이 겹치면 conflict난다.
Conflict를 최소화하는 것도 전략이고 실력이다.
어떻게 관리할 것인가?


Conflict 최소화 전략

  1. upstream에 변경사항이 생길 경우, 팀장은 바로 브리핑하고 팀원들은 반드시 빠르게 동기화하기.
  2. ESLint 사용.
  3. Prettier 사용.
  4. Ticket 발급시 작업영역이 최대한 겹치지 않도록 발급.
  5. Router과 같은 Shared 컴포넌트에 코드 작성 시 자체적인 규칙 생성.

마치며

위의 코드를 복붙해서 쓰는 것보다, 위의 방식을 이해하고 상황에 따라 적용하는 것이 가장 좋다.

다들 햅삐햅삐 행복코딩!! 🥳


방-긋


+a 추가적으로 알아두면 좋은 요소들

rebase

해당 브랜치를 가져와 commit을 전부 클론을하고, 현재 브랜치에 추가한다.

1. 작업할 브랜치로 이동

git checkout origin pengooseDev

2. rebase

git rebase {가져올 브랜치}


cherry-pick

커밋이 쌓였을 때, 특정 커밋만 뽑아서 PR 날리기!

1. 브랜치 이동

git checkout main

2. PR날릴 브랜치 하나 따기

git checkout -b cherry

3. 체리 따오기

git cherry-pick {커밋id}

여러개 가져오고 싶은 경우

git cherry-pick {커밋id} {커밋id} {커밋id}

이런거도 가능.

4. push하기.

git push origin cherry

5. PR!


Docs(JP)

新しいチームメンバーが毎回加わるたびに、プロセスを口頭で説明したりNotionにまとめたりすることに限界を感じました。ブログ記事を書いてリンクを共有する方が効率的であり、誰かがその記事を読んでフローを学ぶことができると思い、文章に残すことにしました。


0. コードを取得する

  1. 上流のコードをフォークする(フォークしたリポジトリは自分のオリジン)
    (原本のリポジトリ管理者は原本のコードがオリジンだが、直接オリジンにコードをマージすることはない。この写真は個人のリポジトリのように描かれているが、通常はorganizationを作成し、organizationの所属として設定する方が一般的だ!(チームリーダーもフォーク)

  2. git Cloneの後、IDEでローカル環境で開く

1. gitがリモートしている状態を確認する

git remote -v

フォークしたオリジンリポジトリをgit cloneする場合、gitは上流(原本コード)に変更があった場合、これを確認する方法がない。

チームメンバーのPRやmergeにより上流のコードに変更が生じた場合、これを同期させる必要がある。そのため、upstreamをリモートする。

2. 同期する原本コードにrepoを接続する

git remote add {名前} {アドレス}
git remote add upstream {原本リポジトリURL}

3. 作業環境(ブランチ)を作成する

git checkout -b feature/{作業者名}/{作業内容}

git checkout feature/pengooseDev/TicketBoard


コードを書いている途中で原本(upstream)リポジトリに変更が生じた場合。

JeJeがコードを書いてバージョンがv1.1になったとしましょう。

私たちは二つのことを考えなければなりません。(gitとgithubは異なる。gitは私たちのコンピュータのリポジトリとして、githubはオンラインリポジトリとして理解すると便利だ)
1. githubのコード
2. git(ローカル。つまり、VSCで書いたコード)

補足説明(作成順序)

  1. github/origin/v1.0
    • JeJeがgithubでupstreamをフォークした時点で作成される。
  2. git/origin/v1.0
    • JeJeがVSCでgit cloneする瞬間にローカルにファイルとして作成される。
  3. git/feature/v1.0
    • JeJeがgitでgit checkout -b featureをする瞬間に作成される。
  4. git/feature/v1.1
    • JeJeがVSCで新しいコードを書く瞬間に作成される。
  5. github/feature/v1.1
    • JeJeがgitで書いたコードをgithubのfeatureブランチにpushすると作成される。
  6. PR
    • JeJeのgithubでfeatureブランチに存在するコードはv1.1である(現在の写真状況)
      JeJeはこのコードをupstreamのブランチの一つに(developと仮定)PRを送ると仮定しよう。
      PRが作成されmergeされた時、upstreamのブランチの中でmergeを受けたブランチ(develop)のみがv1.1の状態で、他のブランチはv1.0である。

実際には、JeJeが作成したPRがmergeされた時点でupstreamのdevelopブランチに存在するコードはv1.1ではなくv1.1.1である。PRがmergeされるとmergeされたことのcommitが追加されるためだ。(gitが初めてなら理解しなくても大きな問題ではない部分)


upstreamに変更が生じたら、チームメンバーは速やかに同期する必要がある。(重要)

そうすることでconflictが最小限になる。
upstreamのコードをマージする過程でConflictが生じる可能性がある。
Conflictを最小限にする方法と戦略は様々だ。(後ほど説明)
まずは行動様式から確認しよう。

Pengooseとチームメンバーがすべきこと

Pengoose(またはチームメンバー)の視点で見てみよう。
他のチームメンバー(JeJe)が送ったPRがupstreamにmergeされた場合?

Pengooseはgit(local)githubのコードをupstreamのコードと同期させる必要がある。
以下の順序に従う。

1. まずは作業中のコードを隠そう。

(現在作業中のブランチがfeatureブランチだと仮定)

git stash


2. mainブランチに切り替える

git checkout main

私はmainブランチを同期のためにコードを受け取る用途でのみ使用する。
書いたコードがmainにpushされることは全くない。

現在のコードの状況は以下の通りです。


3. コードを受け取る

まずはupstreamの変更されたコードv1.1(オレンジ色)をgit mainに受け取ろう

git fetch upstream main

意味は以下の通りです。
git(を使って) fetch(データを取ってくるよ、どこから?) upstream(の) main(ブランチから!)

mainブランチと述べましたが、gitflowを使用している場合は、おそらくdevelopブランチから受け取ることになるでしょう。PRがmergeされたブランチから受け取れば良いです。


4. merge (gitローカルmainブランチをアップデート)

git merge upstream/main

意味は以下の通りです。
git(を使って現在私がいるブランチに) merge(コードをマージします。どのコードをマージするかというと、fetchしてきたコードの中で) upstream/main(upstreamのmainブランチから受け取ったコードです!)

つまり、3番でfetchしたコード(upstream/main)を現在自分がいるブランチ(mainブランチ)にマージするプロセスです。
fetchをupstreamのdevelopブランチに行った場合、git merge upstream/developが正しい表現になります!

ここまで進めたら、現在の状況は以下のようになります。


5. push (githubのmainブランチを更新)

再び言いますが、gitはローカルを指し、githubはインターネットのリモートリポジトリを意味します。二つは異なります!
gitを使ってupstream githubに保存されているコードをローカルに持ってきてマージしました。私のgithubには適用されていないので、これをpushして適用させるプロセスです。

git push origin main

その結果は以下の通りです。


6. 作業ブランチに移動

新しく更新されたコードをfeatureブランチでマージして、以前に作成していたコードと統合することを想定します。

git checkout feature


7. gitのfeatureブランチを同期

origin mainからコードを更新したので、origin mainからコードを同期しましょう。(もちろん、upstreamから再び取得しても構いませんが、面倒です。mainに同じコードをバックアップとして保存しておいたので、mainから取得します。)

git pull origin


8. githubのfeatureブランチを同期

5番と同じ論理です。githubのfeatureブランチを更新しましょう。

git push origin feature

ぶぶぶ-🥳コードをupstreamと同期する旅が終わりました。
しかし、まだ一歩が残っています。
それはconflictです。


9. 作業中だったコードを取り出す

git stash pop

隠していたコードを取り出しましょう。ここでconflictが発生する可能性があります。
conflictはいつ発生するかというと、

現在のコードと新しいコードが同時に変更された場合

簡単に言えば、コードの変更が重なるとconflictが発生します。
Conflictを最小限に抑えることも「戦略」であり、「技術」です。
どのように管理するか?


Conflict最小化戦略

  1. upstreamに変更があった場合、チームリーダーはすぐにブリーフィングし、チームメンバーは必ず迅速に同期します。
  2. ESLintの使用。
  3. Prettierの使用。
  4. チケット発行時に作業領域ができるだけ重ならないように発行。
  5. RouterのようなSharedコンポーネントにコードを書く際は、自己規制のルールを作成。

終わりに

上記のコードをコピー&ペーストして使うよりも、上記の方法を理解し、状況に応じて適用することが最も良いです。

みなさん、ハッピーハッピーハッピーコーディング!! 🥳


ばんぐっと


+a 追加で知っておくと良い要素

rebase

該当ブランチを取得してコミットをすべてクローンし、現在のブランチに追加します。

1. 作業するブランチに移動

git checkout origin pengooseDev

2. rebase

git rebase {取得するブランチ}


cherry-pick

コミットが積み重なったとき、特定のコミットだけを選んでPRを送ります!

1. ブランチ移動

git checkout main

2. PRを送るブランチを一つ作る

git checkout -b cherry

3. チェリーピック

git cherry-pick {コミットID}

複数取得したい場合

git cherry-pick {コミットID} {コミットID} {コミットID}

このようにもできます。

4. pushする。

git push origin cherry

5. PR!

0개의 댓글