본문 바로가기

일상

오래된 Git 저장소를 브랜치와 태그, 히스토리까지 통째로 옮길 때 (git clone --mirror와 push --mirror)

반응형

오래된 GitLab 에서 새 저장소로 옮기면서 브랜치, 태그, 커밋 이력을 하나도 빠뜨리지 않고 그대로 가져가는 방법입니다.


오래 쓴 GitLab 을 새 서버로 옮기게 됐는데, 요구사항이 프로젝트를 이력까지 전부 옮겨달라는 것이었습니다.


처음 들은 이야기는 부정적이었습니다. 기존 서버가 워낙 오래된 버전이라 새 버전과 호환이 안 돼서 전체 이관은 어렵다는 것이었고, 최근 태그 몇 개만 옮기고 나머지는 포기하자는 말까지 나왔습니다.


그런데 알아보니 버전 호환이 문제 되는 건 GitLab 의 내보내기/가져오기 기능이고, Git 저장소 자체는 Git 명령만으로 옮길 수 있었습니다. 실제로 이 방법으로 브랜치와 태그, 이력을 전부 옮겼습니다.


- GitLab 의 내보내기/가져오기는 버전을 타지만, Git 명령은 버전을 타지 않는다

- clone --mirror 로 받고 push --mirror 로 밀면 모든 브랜치와 태그가 그대로 간다

- GitLab 이 내부용으로 만든 참조는 새 GitLab 이 거부하니 푸시 전에 지운다

- 머지 리퀘스트, 이슈, 위키는 저장소가 아니라서 따라가지 않는다


1. 왜 내보내기 기능 대신 Git 명령인가

GitLab 에는 프로젝트를 파일로 내보내고 다른 GitLab 에서 가져오는 기능이 있는데, 편하긴 해도 내보낸 쪽과 가져오는 쪽의 버전이 맞아야 하고, 차이가 크면 가져오기가 실패합니다.


반면 저장소 안의 브랜치, 태그, 커밋은 GitLab 이 아니라 Git 이 관리하는 데이터입니다. Git 끼리 주고받는 방식은 오래전부터 거의 바뀌지 않아서 10년 된 서버에서 받은 저장소를 최신 서버에 올려도 문제가 없습니다.


그래서 "전체 이관이 안 된다"는 말은 절반만 맞는 말입니다. GitLab 프로젝트 통째로는 어렵지만 Git 저장소 통째로는 됩니다.


2. clone --mirror 로 받고 push --mirror 로 민다

평소 쓰는 git clone 은 기본 브랜치 하나만 작업용으로 꺼내고, 나머지 브랜치는 원격 추적용으로만 받아두기 때문에, 이 상태로 새 저장소에 푸시하면 내가 체크아웃한 브랜치만 올라갑니다.


--mirror 는 작업 폴더 없이 원본의 모든 참조(브랜치, 태그 등)를 똑같이 복사해서, 받을 때도 전부 받고 밀 때도 전부 밉니다.



핵심은 set-url --push 입니다. 받아오는 주소는 원본 그대로 두고 올리는 주소만 새 저장소로 바꾸면 한 폴더로 받기와 올리기를 둘 다 할 수 있습니다.


새 저장소는 비어 있는 상태로 만들어 두는 게 안전합니다. push --mirror 는 원본에 없는 브랜치를 새 저장소에서 지워서라도 똑같이 맞추기 때문에, 이미 뭔가 올려둔 저장소에 밀면 그게 사라집니다.


3. GitLab 에서 받았다면 내부 참조를 먼저 지운다

원본이 GitLab 이면 --mirror 로 받을 때 GitLab 이 내부용으로 만든 참조까지 같이 딸려오는데, 머지 리퀘스트마다 생기는 refs/merge-requests/... 같은 것들입니다.


이걸 그대로 새 GitLab 에 밀면 숨김 참조라 갱신할 수 없다는 에러와 함께 거부됩니다. 브랜치와 태그는 올라가도 에러가 섞여 나와서 제대로 됐는지 헷갈리니, 푸시 전에 지워두는 편이 깔끔합니다.



지우는 건 받아온 복사본에서만 일어나고 원본 서버에는 아무 영향이 없습니다. 새 GitLab 에서 보호 브랜치 설정 때문에 푸시가 막히면, 이관하는 동안만 보호를 풀었다가 다시 거는 방법도 있습니다.


4. 옮긴 뒤 확인하기

다 옮겼다고 끝내지 말고 원본과 새 저장소의 참조 목록이 같은지 비교하는데, 브랜치와 태그 이름, 그리고 각각이 가리키는 커밋이 모두 같아야 합니다.



두 목록을 뽑아 비교해서 아무것도 안 나오면 똑같이 옮겨진 것이고, 태그가 많은 저장소라면 눈으로 세는 것보다 이 방법이 훨씬 확실합니다.


5. 따라가지 않는 것

--mirror 가 옮기는 건 Git 저장소 안에 있는 것까지라서, GitLab 이라는 서비스가 따로 관리하는 것들은 따라가지 않습니다.


- 머지 리퀘스트와 그 안의 코멘트

- 이슈, 위키, 마일스톤

- CI/CD 변수, 러너 설정, 웹훅

- 멤버와 권한, 보호 브랜치 설정


이 중에 꼭 필요한 게 있으면 따로 옮겨야 합니다. 제 경우는 코드 이력만 필요했고 머지 리퀘스트나 위키는 쓰지 않아서 이 방법으로 충분했습니다.


6. 원본 서버에 직접 못 들어갈 때

오래된 서버는 관리하던 사람이 떠나서 서버에 로그인할 수 있는 사람이 아무도 없는 경우가 종종 있는데, 그래도 저장소 이관은 할 수 있습니다.


여기서 헷갈리기 쉬운 게 권한이 두 종류라는 점입니다.


- 서버 로그인 권한: 서버에 접속해서 설정을 바꾸거나 파일을 직접 만지는 권한

- 저장소 읽기 권한: 평소처럼 git clone 으로 코드를 받는 권한


clone --mirror 는 평범한 git clone 과 같은 방식으로 받기 때문에 저장소 읽기 권한만 있으면 충분하고, 서버 로그인 권한은 필요 없습니다.


다만 방화벽 때문에 내 PC 에서는 원본 서버에 접속 자체가 안 되는 경우도 있습니다. 이때는 원본에 접속이 되는 다른 서버, 예를 들어 평소 그 저장소에서 소스를 받아 빌드하던 빌드 서버에 들어가서 2절 명령을 그대로 실행하면 됩니다.


- 빌드 서버에서 clone --mirror 로 원본을 받는다

- 같은 자리에서 새 저장소로 push --mirror 한다

- 단, 새 저장소에도 그 빌드 서버에서 접속이 돼야 한다


"서버에 못 들어가니 이관은 불가능하다"가 아니라, 저장소를 받을 수 있는 자리 한 곳만 찾으면 된다는 뜻입니다.


용량이 너무 커서 부담스럽다면 받아온 복사본에서 안 쓰는 옛 브랜치나 태그를 정리한 뒤 밀면 됩니다. 이것도 복사본에서만 하는 일이라 원본은 그대로 남습니다.


7. 정리

"버전이 달라서 전체 이관은 안 된다"는 말을 들으면 먼저 무엇을 옮기려는지부터 나눠보는 게 좋습니다. 코드와 이력이라면 Git 명령으로 대부분 해결됩니다.


- GitLab 내보내기는 버전을 타지만 Git 명령은 타지 않는다

- clone --mirror 와 push --mirror 로 모든 브랜치와 태그를 옮긴다

- 받는 주소는 두고 올리는 주소만 set-url --push 로 바꾼다

- 새 저장소는 비워둔 상태에서 민다

- GitLab 내부 참조(refs/merge-requests 등)는 푸시 전에 지운다

- 머지 리퀘스트, 이슈, 위키는 따로 챙긴다


이관 작업은 "통째로는 안 된다"는 말에서 멈추기 쉬운데, 막히는 게 어느 층인지 나눠보면 생각보다 많은 부분이 그대로 옮겨집니다.


여기까지 Git 저장소를 이력까지 통째로 옮기는 방법에 대해서 작성해봤습니다. 여기까지 읽어주셔서 감사합니다!

반응형