git/git

git 활용 step 3rd-#1 / fork로 협업하기

부엉이사장 2025. 1. 6. 19:59
Introduction
드디어 깃헙으로 협업하기 포스팅이다.
이전에도 말했지만, 난 협업경험이 없다. 때문에 포스팅이 잘못됐다면 지적해주면 매우 감사드리겠음.

팀프로젝트가 있는데 협업경험이 없다고? 라고 생각할 수도 있는데, 정확히는 팀프로젝트 두번 다 개발적으로 협업하는게 어려운 상황이었다.

첫번째 팀프로젝트는 네트워크인프라 교육과정에서 파이널 프로젝트였기때문에, 팀원 총 다섯분 중, 인프라팀 세분, 개발팀 두 명중에 나는 개발팀이었다. 애초에 개발팀인원자체가 적었어서 개발적으로 협업이 애매했다.
더해서 개발팀원 한분과 같이 협업을 했는데, 서로 쓰는 언어가 달랐음..
또한 네트워크 인프라에서는 msa가 대세기때문에 우리팀도 eks쿠버네티스 환경에서 msa구조의 서비스를 만들었다. 때문에 서로 같은 레포지토리를 코드를 짤 수 있는 설계 자체가 아니었다.
이때 우리팀은 organization 으로 프로젝트 협업을 하였다. 즉 개발자별로 담당하는 레포지토리가 있었고 레포지토리당 혼자서만 건드는 협업구조라, 내가 기대한 개발팀에서의 협업이란 구조가 이루어지지 않았다.

두번째 단어장프로젝트는 팀원 세 명이서 진행한 프로젝트인데, 여기또한 팀원 두 분은 인프라 담당이었고, 개발은 전체적인 코드를 나 혼자 담당하였다. 당연히 개발자 레포지토리가 따로 있었고 난 개발 레포지토리만 손댔었다.

물론 인프라팀의 cicd파일 수정을 위해 잠깐씩 pull과 merge해야하는 경우가 있었지만 이건 뭐 너무 단순한거라..

지금까지 혼자 코딩공부를 해왔기에, 깃으로 협업할 수 있는 기회가 없었음..
취직을 준비하는 현재 상황으로써는 협업을 알아두고 가야한다고 판단해서 깃으로 협업하기 포스팅 시리즈를 쓴거다.
혼자놀기의 진수를 볼 수 있음. (쫌 쪽팔림)

2025년의 시작을 앞서, 나는 puppy world프로젝트를 진행하기로 하였다.

강아지를 위한 어플리케이션 개발인데, 강아지 개발자인 muzzi씨와 dori씨를 팀원으로 모셨고, 내가 팀장직을 하게 되었다.

각자는 github레포지토리가 존재하는 형태에서 협업을 할 것이다.

 

협업 하는 방식이 여러가지가 있는데 이번 포스팅에선 fork로 협업하는 방식으로 해보겠다.

 

 

환경셋팅

 

일단 각기 다른 개발자가 협업을 하는 환경을 만드려고 컴퓨터를 여러대 쓰는 환경이 필요했따.

그래서 muzzi가 쓰는 컴퓨터인 우분투이미지로 만든 muzzi computer vm과, dori가 쓰는 컴퓨터인 dori computer vm을 만들었다.

각 vm ssh연결해놨음

그리고 크롬창을 서로 다른 구글계정으로 로그인하고 각 깃허브 계정을 생성하였고, muzzi worker계정으로 접속한 github, dori worker계정으로 접속한 github를 만들었다.

 

우분투환경인 무찌와 도리 계정에서 전부 본견의 깃허브 계정에 로그인되어 자기 레포에 push와 pull을 할 수 있는 상태임

난(nurd worker) 그냥 내 컴퓨터에서 접속한걸로 가정을 했음.

 

 

fork 협업방식?

 

대략적인 구성도를 보면, fork협업방식은 먼저 팀장이 puppy-project라는 깃허브 레포지토리를 본인 깃허브 계정에 만든다.

그리고 무찌랑 도리 개발자가 팀장의 fork-puppy-project레포지토리를 통째로 그대로 복사해서 본견의 각 깃허브 계정에 복사해서 가져온다.(fork하는게 깃허브 홈페이지에 따로있음)

 

각 개발자는 복제해온 레포지토리를 로컬로 clone후, 각각 코드를 알아서 수정하고 고친다음에, 본견 계정의 레포지토리에 push한 후, 이 레포지토리를 통쨰로 팀장의 레포지토리에 pull request를 보낸다.

그럼 팀장은 팀원들이 수정한 코드들이 잘 동작하는지 확인하고, 이 pull request로 코드의 변화를 팀장 계정레포에 merge할지 말지 결정을 하는거다.

 

이게 대략적인 fork협업방식의 구조임

 

 

해보자

먼저 nurdworker 내 계정에 레포지토리를 하나 만들었다. fork-puppy-project라는 레포지토리에 app.js파일을 만들고 그 안에 app.js파일이 들어있다.

코드내용은 이렇다.

 

# muzzi worker계정으로 nurdworker깃허브 fork-puppy-project를 fork하기!

 

현재 시점은 muzzi worker이다. 무찌는 팀장님이 puppy project를 포크해가라고 명하셨다.

사진은 muzzi worker계정으로 로그인한 깃허브 웹이다. 이제 nurdworker의 fork-puppy-project레포지토리를 가야한다.

팀장인 nurd worker의 fork-puppy-project레포지토리에 muzzi worker계정으로 접속했다. 오른쪽 상단에 무찌계정으로 로그인된게 보인다.

레포지토리 페이지에서 Fork저 버튼을 클릭하샘

 

이런 페이지가 뜨는데, 즉 muzzi워커 깃허브에 팀장님의 fork-puppy-project레포지토리를 통쨰로 가져간다는 얘기다.

메인브랜치만 가져간다고 했음.

create fork버튼 클릭.

잠시후 잘 가져왔음.

 

이제 무찌는 무찌계정의 fork puppy 레포지토리를 자기 컴퓨터에 clone을 해서 가져온다. 팀장님꺼 가져오는거아님!!

잘 가져왔다.

 

 

# 무찌가 코딩을 한다

무찌가 app.js파일에 간식달라는 코드를 추가했다.

그리고 muzzi worker계정 본견의 레포지토리에 push를 했음

무찌계정으로 들어가보니 잘 push가 된걸 확인 할 수 있다.

 

# 코드가 너무 맘에드는데? 팀장님한테 자랑하자 - pull request

pull request를 보내려면 pull request탭에서 new pull request버튼을 클릭한다.

무찌계정으로 로그인했고 원래 레포지토리는 팀장님의 nurdworker계정 fork-puppy-project레포지토리이다.

팀장님의 main브랜치에 pull request를 보내는거다.

muzzi worker계정의 fork-puppy-project레포지토리 main브랜치의 커밋상태를 pull request를 하자.

아래엔 어떤 부분이 원본이랑 다른지 비교할 수 있음.

 

이렇게 대충 메시지 형식으로 팀장님한테 pull request할때 추가할 수 있음.

기존 레포지토리랑 충돌이 없이 잘 보냈다고 뜸.

 

 

# 이제 팀장님인 nurd worker계정에서 pull request가 온지 확인하자

지금부터는 팀장인 nurd worker시점이다.

사진은 nurd worker계정의 깃허브 계정이다.

fork-puppy-project레포지토리에 저렇게 pull request탭에 뭔가 생겼고, 메세지가 하나와있다.

 

메세지를 보니 무찌가 저렇게 메세지를 보낸게 확인된다.

무찌가 main브랜치로 push했다(건방진녀석..)

커밋으로 어떻게 수정했는지 확인해볼까? 

음 나름 합격이야

그래도 이 코드를 테스트 해봐야겠지?

 

# 무찌가 보낸 코드 테스트

git branch muzzi-code-test
git fetch origin pull/1/head:muzzi-code-test
git switch muzzi-code-test

팀장은 puppy project의 로컬 vs코드를 키고 해당 명령어를 입력한다.

fetch를 난 처음보는데, 밑에 더 포스팅할거다.

만약 pull을 하면 현재 팀장의 브랜치에 무찌의 pull request 코드내용이 병합되어버리겠지?(사실 pull request코드는 pull로 가져올수가 없음) 때문에 fetch를 쓰는거다..

muzzi-code-test라는 임시 브랜치를 생성해주고, fetch명령어를 통해서 해당 pull request의 제일 마지막 커밋을 해당 브랜치에 가져온다는거다.

git fetch <팀장레포지토리주소> pull/<풀리퀘스트 번호>/head:<현재 팀장 로컬에서 브랜치>

origin은 remote로 단축어설정된 nurdworker의 팀장 레포지토리 주소이다.

풀 리퀘스트 번호는 저기 #1으로 붙어있음. 직접들어가도 대문짝만하게 번호가 적혀있음. fetch로 원하는 pull request번호를 저기 양식에 적어주면된다. 

그리고 마지막 로컬 브랜치이름은 아까 생성한 muzzi-code-test라는 브랜치에다가 저 pull request 커밋을 가져온다는 거다.

git switch muzzi-code-test

fetch후, 해당 브랜치로 이동을 해서 코드를 보면 무찌가 pull request로 보낸 코드로 변해있다.

node명령어로 로컬에서 테스트해보니 무찌가 코드를 아주 잘 짰다.

 

 

# 무찌의 pull request를 팀장 레포지토리에 merge하기

다시 팀장인 나는 깃허브 풀리퀘스트 페이지에 왔따. 저 버튼을 클릭해서 merge해주면 된다

확인! 

참고로 무찌가 한 commit이라고 스트링 더 붙여줌

 

다시 팀장계정의 fork-puppy-project를 보면 무찌의 업데이트된 코드가 잘 반영된걸 확인 가능하다.

 

# 무찌계정에서는 어떨까?

메세지가 와있다.

확인하니까 무찌 코드가 merge되었다고 뜬다.

또한 팀장님이 칭찬 댓글도 남겨주셨음

 

이런식으로 fork협업을 할 수 있다.

 

 

만약 팀장님과 무찌가 병렬로 코드를 짜고있었다면?

상황을 가정해보자.

무찌는 팀장님의 first commit 상태의 레포지토리를 muzzi worker계정에서 작업하고있었음.

근데 그 와중에 팀장님은 다른 기능을 개발하여 팀장님 깃헙 계정의 main브랜치로 commit을 때려놨음 (second commit)

무찌계정에선 현재 해당 커밋의 코드상태가 반영이 안되어 있는 상태임

무찌는 새로짜고있는 코드가 팀장님이 커밋한 second commit과 문제가있을지없을지도 모르겠고 상태도 업데이트하고싶음

이런 경우엔 어떻게 해야할까?

 

현재 팀장 nurd worker는 무찌는 바보라는 코드를 추가 한 후, 레포지토리에 push해 둔 상태이다.

무찌 컴퓨터로 돌아와서 현재 무찌 컴퓨터에서는 무찌는 바보라는 코드가 업데이트 되어있지 않다.

이미 무찌는 기존 코드에서 무찌는 천재라는 코드를 적어놓고 commit한 상태!

팀장님이 커밋한 코드를 반영못한채로 코딩을 하고있음.

 

# 무찌계정에서 연결 레포지토리 두개로 관리하기!

현재 무찌컴퓨터에서는 팀장님 레포지토리를 fork한 무찌계정의 복사본 레포지토리가 origin으로 선택되어있다.

여기서 연결할 레포지토리를 하나 더 만드는것이다.

upstream이라는 단축어를 만들고 여기에 팀장님 레포지토리를 설정해뒀다.

 

git fetch upstream main

그럼 팀장님의 최신코드 정보를 fetch로 가져온다.

git merge upstream/main

그다음 팀장님 레포지토리의 main브랜치 코드를 merge하자

충돌이 났다.

이전 포스팅을 보면 한 파일을 두 브랜치가 동시에 수정하고 commit한 경우 충돌이 난다

충돌난 코드를 무찌가 확인했다

무찌는 vim에디터로 코딩을 하고있는 천재강아지므로 팀장님의 코드를 받아들이기 싫다.

그리고 커밋후 merge를 확정. 본인 레포지토리에 push해놨다.

 

이런식으로 관리하는거다. 팀장님코드의 최신 업데이트는 팀장님 레포지토리 (upstream)에서 가져오는거고, 내코드는 origin으로 관리하는거임

이렇게 협업을 계속 이어나갈 수 있다.

우분투에서하는거라 커밋히스토리를 중간에 캡쳐못했는데 이렇게 팀장님이 커밋한 second commit이 잘 나옴

 

 

 

팀장님이 pull request 거부하기

무찌는 아까 팀장님이 짠 무찌는 바보라는 코드를 지우고 충돌 해결을 하였다

이 상태에서 다시 팀장님에게 pull request를 보낸다.

이제 다시 팀장 nurdworker의 시점이다

무찌가 보낸 pull request가 잘 와있다.

사실 이 강아지가 아까부터 계속 main브랜치로 커밋한거 보내는게 좀 짜증이 났던 상태였는데, 내 코드도 지우고 pull request를 보내다니 괘씸하다는 생각이 듦

받아들이지 말자.

 

저렇게 아랫쪽에 close pull request버튼을 클릭하면

저렇게 pull request가 closed도ㅒㅆ다고 뜨게됨

참고로 닫아도 다시 open할수 있다고함.

무찌 계정의 깃헙을 가면 저렇게 거절됐다고 메세지가 와있음

 

 

 

fetch는 뭘까?

혼자쓸때는 써본적이 없어서 잘 몰랐는데, 협업하는걸 연습하다보니 자주 등장한다.

떄문에 이걸 알아보려고함.

일단 팀장시점에서 새로운 commit을 하고, 본인 레포지토리에 push를 해놓은 상태임.

test fetch가 있는걸 확인할 수 있다.

 

이제 무찌시점이다

현재 무찌코드는 제일마지막에 second commit이 있다. test fetch가 없으니 팀장레포지토리의 최신상태가 반영안된거임.

팀장 레포지토리의 최신상태도 현재 로컬에선 반영이 안되어있다.

이떄 fetch명령어를 사용해보자

git fetch <팀장레포지토리> <팀장브랜치>

이 명령어는 팀장레포지토리의 특정 브랜치 상태를 fetch로 가져오겠다는 소리임

팀장 레포지토리는 remote로 upstream이고, 팀장의 main브랜치를 가져오겠다는거임.

잘 가져왓다.

다시 log를 볼까?

팀장의 가장 최근 커밋인 test fetch가 잘 가져왔다

 

 

# 음 근데?

팀장은 test fetch라는 코드를 넣어 놓은 상태임

근데 무찌가 fetch로 팀장님 코드를 잘 받아왔는데, 무찌 코드는 반영이 안되어있다.

약간 add와 commit같은 시스템같은데,

 

로컬에 팀장님 코드를 가져왔지만, 아직 내 코드엔 반영을 못한거다. 즉 내 코드에 merge를 해야한다는거임.

git merge <팀장님레포주소>/<팀장님 브랜치명>

이렇게하면 무찌코드에 팀장님의 코드가 반영된걸 확인 할 수 있다.

다만 중요한건, fetch후에 merge를 해야한다는거다. merge명령어를 쓸때는 현재 로컬주소의 커밋정보를 가지고 merge를 하는것이기 때문. 

 

++참고로 pull을 해봤는데 뭔가 안됐다. 왜인지는 모르겠는데.. fetch, merge조합으로 하니까 됐음. 걍 단순히 fetch로 팀장님 레포의 main브랜치의 최신상태를 로컬에 가져오고, 이 브랜치 최신상태를 현재 내 branch에 merge하면 팀장님 코드가 반영이 된다는것.

 

 

 

pull은 뭘까?

레포지토리의 최신상태를 현재코드에 반영할때 사용을 했었다.

pull은 정확히는 fetch와 merge를 합친거다.

테스트를 또 해보자.

팀장이 test pull이라는 코드를 추가하고 push했다.

팀장 레포지토리에 test pull커밋이 적용된걸 확인 할 수 있음.

 

이제 무찌 시점에서

아까 test fetch커밋상태였지만 pull명령어 한번을 쓰니 코드가 test pull로 업데이트가 잘 되었다.

다만 가끔 pull시에 이렇게 되는경우가 있더라.. 나도 잘은 모르는데 그냥 fetch + merge를 쓰는게 더 좋다고함 gpt가.

만약 그냥 이렇게 나눠쓰기 싫으면 

git pull --rebase upstream main

이렇게 rebase전략으로 pull해오라고하면됨. 뭔진 나도 잘 모름

 

 

 

협업을 해보자!

이런식으로 전략을 짜보자.

먼저 팀장 github계정의 기준이 되는 레포지토리를 판다.

그리고 무찌랑 도리가 각 본견의 계정으로 팀장의 레포지토리를 fork해서 가져간다.

이후에 fork로 가져간 레포지토리에서 무찌랑 도리는 각각 main브랜치로 작업을한다. (기능별로 브랜치 따로 해서 해도됨. 기준은 각자 fork한 레포지토리의 main브랜치에서 작업한다는거임.)

 

강아지 개발자들이 본인 main브랜치를 기준으로 작업을 한 후, 코드가 맘에들면 팀장님 레포지토리의 muzzi-dev, dori-dev라는 브랜치에 pull request를 보낸다.

팀장은 해당 리퀘스트가 진심을 담아 보낸 리퀘스트인지 판단후 merge를 결정한다.

사실 팀장은 muzzi-dev브랜치와 dori-dev브랜치를 건들일이 없다. 그냥 이건 강아지 개발자들의 코드 테스트용임.

 

 

팀장은 각 muzzi-dev, dori-dev브랜치에서 코드가 정상적으로 동작하면 해당 브랜치를 main브랜치에 merge를 한다

그림에는 안그렸지만 팀장도 본인 개발용 브랜치를 따로 만들어서 main에 넣을지 말지 결정할 수 있음

 

 

왜이렇게 설계했나요? & 장점은 뭘까?

1. 위에 코드를 테스트해보면서 매번 팀장이 브랜치를 새로 만들고, pull request의 코드를 가져와서 테스트 하는게 귀찮을것 같았다. 때문에 리퀘스트를 각 강아지개발자-dev 브랜치에 그냥 받아들이고, 이 브랜치를 로컬로 가져와서 테스트 해보면 될것같았음.

2. 이렇게 설계하면 원본 브랜치인 main브랜치도 안전할거임.

3. 각 강아지 개발자들도 깃에 대해서 수준차가 있다 가정. 예를들어 무찌는 브랜치를 잘 구분 하는 강아지이고, 도리는 내 포스팅 step1처럼 main브랜치만 다루는 남자다운 스타일이라고 한다면, 어차피 지네 레포지토리의 main브랜치기준으로 최종 커밋을 하는 시스템임. 만족한다면 이걸 팀장 레포에 만들어진 지네 브랜치에 pull request를 보내면 됨.

4. 직접적으로 팀장의 main브랜치에 pull request보내는것이 아니기 때문에 팀장은 pull request를 merge하냐마냐에 따른 리스크를 줄일 수 있음. 즉 팀장은 강아지-dev브랜치는 해당 pull request코드를 테스트할때만 사용하는 용도.

5. 다만 팀장은 main브랜치가 업데이트 될때마다 팀원들에게 직접 알려줘야하고 팀원은 이런 공지가 내려올때마다 upstream에서 코드를 본인 main브랜치와 작업중인 브랜치에 merge해야함. 그냥 심심하면 팀장 main브랜치 fetch, merge하라고하면됨

 

 

 

해보자

# 각 강아지들이 코드를 쓰고 pull request를 보냈음.

각 강아지 개발자가 코딩을 하고 본인 레포지토리에 push를 함.

 

팀장 nurd worker는 muzzi-dev라는 브랜치와, dori-dev라는 브랜치를 생성함.

 

무찌가 보내는 pull request

pull request를 보내는 branch가 muzzi-dev라는걸 확인해야함 꼭

dori도 마찬가지로 pull request를 보냄.

 

팀장은 무찌랑 도리가 보내는 pull request를 잘 받았다.

일단 수용하자!

둘다 수용했음. 그럼 이제 팀장은 이 코드를 테스트 해봐야 한다.

git fetch --all
git switch muzzi-dev
git switch dori-dev

이렇게 각각의 코드를 테스트 해 볼 수 있다. fetch는 원격저장소 origin의 모든 브랜치정보도 다 가져온다는거임.

두 코드가 전부 맘에들어서 merge하자

git switch main
git merge muzzi-dev
git merge dori-dev

둘 다 app.js파일을 만진거므로 충돌을 해결해주고 commit해서 push해야함

충돌을 해결한 코드를 팀장님이 본인 레포지토리 main브랜치로 commit해놨음.

 

 


# 이제 강아지 개발자들은 이 업데이트된 코드를 본인 로컬에 가져오자~

git fetch upstream main
git merge upstream/main
git push origin main

각 강아지 개발자들이 이렇게 하면 팀장님의 코드가 현재 자기 로컬코드에 적용이된다. 개발중인 커밋에 충돌이나면 알아서 수정을 해야함.

그리고 origin에 push를 해줌으로써 레포지토리도 업데이트 가능

이 명령어를 그냥 간식먹을때마다 치라고 하면 됨

 

각각 fork레포지토리에 팀장님 레포 main의 최종커밋이 적용된다. 

이런식으로 협업을 하면 될거임.

 

 

 

팀장님의 레포지토리가 public레포지토리다..?

이번 포스팅에서 시뮬레이션한 환경은 모두 팀장의 레포지토리가 public레포지토리였다.

퍼블릭 레포지토리는 전체공개인데? 회사에서 공개레포지토리로 작업할리는 없지 않나?

그럼 private레포지토리를 기준으로 fork를 해야하는데 강아지 개발자들이 팀장의 private 레포지토리를 깃허브 웹에서 접속하려면 안되지 않나?

 

private 레포지토리를 fork방식으로 협업하려면 강아지 개발자들을 collaborator로 설정하면 된다.

먼저 private레포지토리 priv-fork-puppy-project를 하나 팀장이 새로 만들자.

이제 강아지 개발자 무찌가 신나서 팀장님의 레포지토리를 fork하려고 달러감

404가 떠버림 

 

당연한거다. private레포지토리는 아무도 들어올수 없는데 default니까..

 

# 해결방법

팀장은 해당 private 레포지토리의 setting에 들어가자

그리고 collaborators탭에서 add people버튼 클릭

우리 강아지 개발자들 깃허브 이름을 검색해서 collaborator로 초대

무찌랑 도리 둘다 검색해서 초대하샘

collaborator로 잘 초대하였다.

무찌의 깃허브 계정의 메세지를 확인하면

priv-fork-puppy-project레포지토리로 초대가 잘 왔다. 

도리 깃허브에서도 잘 와있을거임

 

무찌계정에서 초대장을 승락하자.

이렇게 팀장의 사설 레포지토리에 잘 접속이 된다.

 

이제 fork도 걍 해오면 됨.

무찌 계정에서 fork해온 priv-fork-puppy-project도 역시 private레포지토리로 지정되어있다.

 

 

사실 collaborator로 팀원을 초대하는 방법은 다음 포스팅에서 다루려고 했다. 간단하게 미리 말하자면 fork는 레포지토리를 통째로 가져와서 pull request를 보내는 방식인 방면에, collaborator는 한 레포지토리에 collaborator를 초대해서 레포지토리의 권한을 부여하는 식이라서 바로 push를 때릴 수 있다.

 

지금 위에서는 사설 레포지토리를 팀원이 fork해야하기위해 초대를 한건데, 지금 상태에서 muzzi랑 dori는 팀장의 레포지토리에 fork하고 그럴것도 없이 직접적으로 push할 수 있는 막강한 상태이다.

따라서 이 권한을 조절해줘야한다.

하지만 이건 collaborator로 협업하는 방법 포스팅에서 다룰것이므로 이번 포스팅에선 적지 않겠다.

 


Conclusion
사실 이번 포스팅 쓰면서 rebase나 뭐 pull머시기 명령어를 많이 썼다.
최대한 단순한 방법으로 적으려고 상황을 일일히 안적었음..
포스팅도 사진이 많다보니 너무 드러워져서..

또한 브랜치작명이라던지 이런것도 같이 포함하고싶었는데 지금도 포스팅이 난잡한데 더 이상해질까봐 생략함.
fork방식만으로 하지는 않기도 한다. 이후에 적을 collaborator방법도 적절히 섞으면서 협업을 한단다. 이건 팀장의 몫임.
암튼 fork방식은 이런형식이고 오픈소스 코드 수정할때도 많이 쓰인다.

여기까지 쓰겠다. 지침 ㅠㅠ