Github 공동작업을 위한 안전한 fork 사용법(2)

Updated:

지난 포스팅에서 GitHub 저장소로부터 분산된 개발환경을 위한 Fork 리파지토리를 만드는 방법에 대해 설명하였다.

이번 편에는 Original 리파지토리와 Fork 리파지토리의 동기화 방안에 대해 추가 설명하고, 특별히 주의해야 할 사항에 대해 설명한다.

Original 변경 사항을 Fork 리파지토리에 반영하기

Fork 리파지토리의 변경은 pull request를 통해 Original에 반영할 수 있지만, 역으로 Original에서 변경된 사항을 내 Fork 리파지토리에 반영해야 할 필요가 있는 경우도 있을 것이다.

Fork 리파지토리 입장에서 바라본 Original 리파지토리는 upstream이라고 부르는데, 로컬에서 자신의 원격을 origin으로 인식하듯, Original 참조시에는 upstream을 사용하면 된다. upstream을 인식할 수 있도록 다음과 같이 등록하자. (Original의 GitHub URL 주소를 등록)

  $ git remote     ## remote로 등록되어 있는 저장소 주소를 보여줌
    origin
  $ git remote add upstream "https://github.com/engineering-skcc/engineering-skcc.github.io" 
  $ git remote     
    origin
    upstream
  ...
  $ git pull upstream master ## Original remote의 변경사항을 내 로컬로 반영 

그런데, 위와 같이 Original 변경사항을 내 fork로컬로 가져와 반영할 수는 있지만, 문제가 있다.

CNAME 파일의 경우 순환 변경이 발생한다. 이는 각자의 fork 리파지토리에서 CNAME을 각각 다르게 설정해주었기 때문에 발생하는 문제인데,
Fork Remote에서 CNAME 파일 생성 or 변경 -> pull request를 하면서 Original Remote에 반영 -> 다른 사람이 upstream으로부터 pull을 하면 다른 사람의 CNAME 변경 !! 과 같은 식으로 계속 순환 변경이 발생하게 된다.
(이 경우에는 .gitignore에 등록해도 소용이 없다. .gitignore는 Working 디렉토리에서 발생한 변경을 커밋되지 않도록 하게 할 뿐, 이미 커밋한 정보에 대해서는 skip하지 않는다.)

따라서, 이와 같은 현상을 방지하려면 git pull upstream master에 의해 내 CNAME이 바뀐 경우, 해당 부분의 커밋 정보를 건너 뛸 방법이 있다.(할렐루야) 대신에 반드시 pull upstream 을 실행한 후에 바로 적용해야 한다.

* CNAME 충돌방지를 위한 커밋 스킵하기 *

CNAME 충돌 방지를 위해 좀 어려운 기술을 사용해야 한다. 다행이도 git에서 특정파일의 커밋을 스킵할 수 있는 기능을 제공한다. 원칙적으로는 git에서는 커밋 정보를 chain 형식으로 관리하기 때문에 특정 커밋만 빼먹을 수는 없다. 따라서, 원치 않는 변경이 발생한 커밋 이전 상태로 돌려야 한다.

즉, CNAME 파일이 변경된 내용을 무효로 하기 위해 그 이전에 커밋된 정보 중 내가 원하는 지점을 찾아 그때의 상태로 돌리는 것이다. 먼저 CNAME 변경이력에서 변경되기 이전의 커밋 정보를 찾는다.

  # 명령어 : git log -p <filename>
  $ git log -p CNAME

위에서 CNAME이 가장 최근의 커밋 정보는 fork-skcc로 변경된 것이며, 그 이전의 커밋정보는 delete CNAME 이다. 필자가 원하는 상태가 바로 CNAME 삭제가 된 상태이므로 delete CNAME이 발생한 커밋까지 되돌려 보자. 되돌리는 명령어는 다음과 같다.

  # 명령어 : git reset <commit ID> <filename>
  $ git reset 0037e22e96490522eaabbef5b29c6ce1d8dcac8c CNAME

그런데, 위 그림에서 보듯이 reset 명령어 다음에 git status를 하면 복원시킨 delete CNAME 커밋정보가 stage상태에 있는 것을 알 수 있다. 사실 delete CNAME도 우리가 원하지 않는 것일 것이다. stage에 올라간 것은 그냥 stage에서 삭제하면 된다.

  $ git restore --staged CNAME
  $ git status

이제 git status 명령어로 알 수 있듯이 CNAME 파일에 대해서 더이상 변경할 게 없는 것으로 나온다. 본인의 로컬에서도 파일이 원래 상태대로 돌아가 있을 것이다. 이상과 같이 CNAME 파일이 순환 변경되지 않게 하려면 내 로컬에서 더이상 CNAME에 대한 커밋을 발생시키지 않게 하면 된다.

참고할 내용 : Git Reset 바로 알기


upstream에 직접 push하기 ?

Fork 리파지토리의 변경 내용을 자신의 원격에 해당하는 origin/master로 push하지 않고, Original 리파지토리로 push할 수도 있다. 이 경우에도 origin 대신 upstream을 사용하면 된다.

    $ git push upstream master

하지만, Original 원본인 upstream master로 직접 push 하는 것은 지양해야 한다.

그런데 왜 알려주냐고? 정말 정말 급할 때에만 사용할 수 있다는 차원에서 방법을 미리 알려주는 거다. Original 저장소의 master 브랜치에 반영하는 작업은 무조건 Pull Request 방법을 쓰는 것이 제일 안전하다.

또한 Original로 바로 push를 해버리면 원격 Fork 리파지토리에는 반영을 Skip한 셈이므로 이는 별도로 동기화 작업을 해주어야 할 것이다.

Copy & Paste ?

Fork 리파지토리에서 마음껏 변경 테스트를 하고 난 뒤, Original로 보내면 Fork에서 작업했던 모든 커밋 흔적들도 Original 쪽에 반영된다. 변경 히스토리야 얼마든지 쌓여도 무방하기는 하지만, 사소한 조정 작업으로 무시무시한 커밋 흔적들이 쌓여 있다면, 이 부분은 보내지 않고 아름답게 포장된 최종 모습만을 Original로 보내고 싶을 것이다.

그런 경우, 로컬에서 작업한 내 파일을 Original 리파지토리의 로컬에 복사하여 붙여넣기로 하여 Original 브랜치에서 작업했던 것처럼 할 수도 있을 것이다. 하지만, 버전관리 측면에서도 그렇고 커밋 관리가 되지 않으므로 되돌리기도 어렵고 무조건 비추이다.

정리 및 당부사항

10여명이 공동 집필진이 구성하는 기술블로그에서 좀 더 작업을 편리하게 하고자 이것저것 시도해보다가 결국에 찾은 방법이 Fork라는 방법이었다. 처음에는 원본 리파지토리를 통째로 개인 리파지토리에 복사하여 작업을 해보았다. 내 리파지토리에서 MD소스파일이 웹페이지 형태로 포스팅된 결과 화면을 아무때나 테스트해볼 수 있는 장점이 있었지만, 원본 파일과의 버전관리가 어려웠고 무엇보다 개인 리파지토리를 public으로 공개해야 하는 문제가 있기 때문에 문을 닫아야 했다. Fork를 어떻게 사용해야 하는지도 모르는 상태에서 이것저것 눌러보면서 마침내 원하는 답을 찾게 되었다. 원하는 답을 찾게 되어 기쁘다.

Pull request를 사용하여 Fork 리파지토리를 원본으로 merge할 때, 마지막 스냅샷만 merge되는 것이 아니라 일반 브랜치 merge와 마찬가지로 그동안 발생한 모든 commit 모두가 Original에 반영되어 버린다. 앞에서도 이야기했지만 Fork한 리파지토리를 내 마음대로 작업할 수 있다고 해서 다른 사람이 작업하는 소스 파일까지 마음대로 변경했다가는 merge하는 순간에 함께 Original로 모두 반영이 되어 버릴 수 있기 때문에 각별한 주의가 필요하다.

다른 사람이 집필한 포스팅 내용을 엉뚱한 것으로 바꾸거나, 특히 본인 입장에서 필요없다고 지워버린 파일이 그대로 원본 리파지토리에서도 삭제되는 과오를 범할 수 있으므로… 🥶🥶

이럴 땐 .gitignore를 활용해보는 것도 도움이 될 것이다.

Fork에 대한 이야기는 여기서 마친다.