여러 git 리파지토리 한꺼번에 git 명령어 적용하기

Posted by 김순철 on December 15, 2018

이 글의 목표는 리눅스 명령어를 통해 다수의 git 리파지토리에 명령어를 한번에 적용하는 방법에 대해 배워 보는 것입니다. 실습 가능한 OS는 아래와 같습니다.

  • 리눅스
  • 유닉스
  • Mac OSX 터미널
  • windwos 10 Ubuntu 또는 Cygwin(Windows 용)

서론

MSA 프로젝트를 진행하다 보면 이전에 하던 프로젝트와 다르게 리파지토리가 2개 이상인 경우를 만나게 됩니다. 최근 참여한 프로젝트 경우 제 PC에 clone 받은 git 리파지토리 갯수만 26개 였습니다. 물론 다 제가 개발하는 소스는 아니고 다른 팀의 소스까지 포함된 경우였습니다.

이 중에서 배포를 위해서 제가 관리했던 프로젝트는 8개 였습니다. 요즘 IDE는 굉장히 편리한 기능이 많습니다. 특히 인텔리J IDEA를 쓰면 편리한 점이 한두개가 아니죠. 하지만… IDE를 사용하는 경우 8개의 리파지토리에 동일한 git 명령어를 적용하는 것이 결고 간단하지는 않습니다.

이유는 일괄 적용 기능이 없기 때문입니다. 물론 인텔리J에서 Update Project 메뉴를 실행하면 현재 프로젝트의 모든 리파지토리를 최신화하는 기능을 제공하고 있습니다. 하지만 작업을 하다보면 일괄적으로 reset을 한다거나 merge를 하거나 일괄 checkout을 경우가 생깁니다. 하지만 이런 작업을 IDE에서는 일일이 메뉴 클릭을 통해 해야하는 단점이 존재합니다. 게다가 저 같이 기억력이 안 좋은 사람은 방금 제가 뭘 했는지 까먹는 경우가 많구요…

이런 이유로 저는 IDE보다는 콘솔에서 명령어로 작업을 많이 합니다.

차근차근 알아보기 전에 바로 최종적으로 배워 볼 명령어를 보고 어떤 결과를 보여주는지 확인한 뒤, 하나씩 알아보도록 하겠습니다.

0. 여러 git 리파지토리에 병렬로 명령어 처리하기

우선 git 프로젝트가 아래와 같이 8개 있다고 가정하겠습니다. 모든 예제는 아래 목록 기준으로 작성 됩니다.

1. common-api
2. delivery-api
3. goods-api
4. members-api
5. order-api
6. payment-api
7. settle-api
8. vendor-api

8개 전체에 대해서 병렬로 develop 브랜치를 checkout 하는 명령어를 보겠습니다.

$ ls | xargs -P10 -I{} git -C {} checkout develop
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
Switched to branch 'develop'

현재 모든 git 리파지토리에 대해서 checkout이 되었다는 결과를 볼 수 있습니다.

1. 다른 경로에서 원하는 git 리파지토리에 명령어 사용하기

일반적으로 git을 콘솔에서 사용하는 경우 .git 디렉토리가 존재하는 경로에서 명령어를 사용해야 정상 동작합니다. 만약 .git 이 없는 곳에서 git 명령어를 사용한다면 아래와 같이 오류가 발생합니다.

$ git checkout develop
fatal: not a git repository (or any of the parent directories): .git

이럴 땐 -C(대문자) 옵션을 사용하면 됩니다. 아래와 같이 실행하면 현재 위치가 어디든 원하는 git 리파지토리에 명령을 내릴 수 있습니다.

git -C <상대경로|절대경로> <명령어>
git -C /source/common-api checkout develop
git -C /source/payment-api commit -m '테스트'

이제 git 리파지토리의 디렉토리 이름과 경로만 알면 명령어를 일괄 적용할 수 있게 되었습니다.

2. 여러 디렉토리명을 하나씩 넘겨주는 방법

xargs를 사용하면 for-each 문을 사용하는 것처럼 여러개의 값을 순차적으로 1개씩 넘겨줄 수가 있습니다. 이를 통해 git 명령어를 적용하는 방법을 알아봅니다.

2-1. ls 명령어

ls 명령어는 단순합니다. 현재 위치에서 파일/디렉토리 목록을 보여주는 명령어입니다. ls만 단독으로 사용하면 아래와 같이 출력 됩니다.

$ ls
common-api  delivery-api  goods-api  members-api  order-api  payment-api  settle-api  vendor-api

위 ls 명령어의 결과를 xargs에 넘겨보도록 하겠습니다.

2-2. xargs 명령어

xargs 명령어는 바로 앞의 명령어 결과를 받아서 각각 1개씩 처리할 수 있게 해주는 명령어 입니다. 일종의 for문이라 생각하면 됩니다. 대신 xargs는 단독으로 사용할 수는 있으나 의미 있는 작업은 아닙니다.

위 2-1에서 본 ls와 xargs를 합쳐 각 디렉토리 내부에 파일 목록을 보는 예제를 살펴보겠습니다.

$ ls | xargs ls
common-api:
others.common-api.iml  pom.xml  src  target

delivery-api:
mine.api.delivery-api.iml  pom.xml  src  target

goods-api:
.log  others.goods-api.iml  pom.xml  src  target

members-api:
others.members-api.iml  pom.xml  src  target

order-api:
mine.api.order-api.iml  pom.xml  src  target

payment-api:
mine.api.payment-api.iml  pom.xml  src  target

settle-api:
mine.api.settle-api.iml  pom.xml  src  target

vendor-api:
others.vendor-api.iml  pom.xml  src  target

명령어에서 최초에 나오는 ls는 2-1에서 살펴본 결과와 동일합니다. 이는 각 디렉토리 명을 출력해줍니다. 이 결과를 |(파이프)를 통해 xargs에 전달합니다. xargs는 결과를 받아 xargs 다음에 나오는 명령어 끝에 받은 값을 넣어 줍니다. 내부적으로는 아래와 같이 동작한다고 보시면 됩니다.

ls common-api
ls delivery-api
ls goods-api
ls members-api
ls order-api
ls payment-api
ls settle-api
ls vendor-api

2-2-1. xargs: -I 옵션

xargs는 기본적으로 앞에서 넘어온 값을 넘겨줄 때 xargs 뒤에 나오는 명령어의 제일 끝에 값을 붙여 줍니다. 이 글의 가장 처음 본 명령어에서 -I{}를 빼고 실행하는 경우는 아래와 같이 동작하게 됩니다. 물론 -I{} 를 빼면 명령어 자체가 오류를 발생하므로 실행 되지 않으니, 된다고 가정하고 설명하겠습니다.

$ ls | xargs git -C {} checkout develop  # 이 명령어는 에러임. 정상 실행된다고 가정.

# -I 옵션이 빠진 위 명령어는 내부적으로 아래와 같이 실행됨 
git -C checkout develop common-api
git -C checkout develop delivery-api
git -C checkout develop goods-api
git -C checkout develop members-api
git -C checkout develop order-api
git -C checkout develop payment-api
git -C checkout develop settle-api
git -C checkout develop vendor-api

결과에서 보듯 -I 를 빼면 디렉토리명이 뒤에 붙어 명령어 문법이 맞지 않게 됩니다. -I의 의미가 이제 이해 될 것입니다. -I 옵션을 사용하면 앞에서 넘어온 값을 원하는 곳에 위치시킬 수 있습니다. 관례적으로 -I{} 를 사용하지만 -IK과 같이 문자로 대치할 수 있습니다. 또한 -I R 처럼 공백을 넣을 수도 있습니다. 다만 문자로 치환하는 경우는 해당 문자가 명령어의 옵션인지 -I 옵션의 대치문자인지 판단히기 힘들기 때문에 추천하지는 않습니다. -IR 이라고 옵션을 사용하는 경우 아래와 같이 쓸 수 있습니다.

$ ls | xargs -P10 -IR git -C R checkout develop

2-2-2. xargs: -P 옵션

기본적으로 xargs로 실행을 하는 경우 명령어는 순차적으로 실행이 됩니다. 첫 예제에서 8개 리파지토리를 checkout 하는 명령어는 순차적으로 각각 실행이 됩니다. 하지만 단순 checkout 하는 명령어이니 병렬로 처리하는 경우 빠르게 처리할 수 있습니다. 이 때 xargs에서 제공하는 옵션이 -P입니다. -P10 과 같이 -P 뒤에 숫자를 붙이면 됩니다. 숫자는 최대 쓰레드 갯수를 의미합니다.

최종: 처음 봤던 명령어 다시 확인

이제 다시 제일 처음 살펴본 명령어로 돌아가보겠습니다.

$ ls | xargs -P10 -I{} git -C {} checkout develop

지금까지 살펴본 내용 토대로 살펴보면 내부적으로는 아래와 같이 처리가 됩니다.

git -C common-api checkout develop
git -C delivery-api checkout develop
git -C goods-api checkout develop
git -C members-api checkout develop
git -C order-api checkout develop
git -C payment-api checkout develop
git -C settle-api checkout develop
git -C vendor-api checkout develop

xargs를 이용하여 대량의 git 리파지토리를 처리하는 방법에 대해서 알아 봤습니다. 자주 쓰는 명령어의 경우 alias 걸어두면 유용하게 활용할 수도 있습니다. 아래 제가 주로 사용했던 명령어들을 나열하는 것을 마지막으로 글을 마치겠습니다.

# checkout 하기
$ ls | xargs -I{} -P10 git -C {} checkout develop

# 현재 브랜치 전체 pull 하기
$ ls | xargs -I{} -P10 git -C {} pull

# 현재 브랜치 전체 hard reset
$ ls | xargs -I{} -P10 git -C {} reset --hard HEAD

# merge 하기
$ ls | xargs -P10 -I{} git -C {} checkout release
$ ls | xargs -I{} git -C {} merge develop  # merge 할 때는 conflict 확인을 위해 -P 옵션을 사용하지 않음

# merge 하기 (한줄버전)
$ ls | xargs -I{} sh -c 'echo --{}--; git -C {} checkout release; git -C {} merge develop;'

# remote URL 얻기
$ ls | xargs -I{} git -C {} remote -v | grep fetch

# 바로 위 remote URL 결과를 파일로 만든 후 전체 clone 받기 (url이 있는 파일을 url_list.txt라 가정)
$ cat url_list.txt | xargs -P10 git clone   # clone은 뒤에 url이 붙으므로 -I 옵션이 필요 없음 

# 각 리파지토리 마지막 tag 확인
$ ls | xargs -I{} sh -c 'echo --{}--; git -C {} describe --abbrev=0 --tag;'

# stash 목록 확인
$ ls | xargs -I{} sh -c 'echo --{}--; git -C {} stash list;'

# develop, release, master 전체 pull 받기
$ ls | xargs -I{} -P10 sh -c 'git -C {} checkout master; git -C {} pull; git -C {} checkout release; git -C {} pull; git -C {} checkout develop; git -C {} pull;'