여러 Git 저장소를 한 번에 Pull하는 방법: find·xargs·PowerShell 실전 가이드
프론트엔드, 백엔드, 배치, 인프라처럼 프로젝트가 여러 저장소로 나뉘어 있으면 작업을 시작할 때마다 같은 명령을 반복하게 됩니다.
cd frontend && git pull
cd ../backend && git pull
cd ../batch && git pull
저장소가 두세 개라면 괜찮지만, 수가 늘어날수록 빠뜨리는 프로젝트가 생기고 현재 어느 저장소를 갱신하는지도 헷갈리기 쉽습니다. 이럴 때 상위 폴더에서 Git 저장소를 자동으로 찾은 뒤 git pull을 순차 또는 병렬로 실행하면 작업을 크게 줄일 수 있습니다.
이 글에서는 macOS·Linux·Windows Git Bash에서 사용할 수 있는 find와 xargs, Windows PowerShell 명령을 함께 살펴봅니다. 단순히 명령만 나열하지 않고 공백이 포함된 경로를 안전하게 처리하는 방법, git pull --ff-only를 사용하는 이유, 실패한 저장소를 확인하는 방법까지 설명합니다.
먼저 결론: 어떤 방법을 선택하면 될까?
| 상황 | 권장 방법 | 특징 |
|---|---|---|
| 저장소가 많지 않거나 처음 실행할 때 | find 순차 실행 |
로그를 읽기 쉽고 오류 원인을 찾기 편함 |
| 저장소가 많고 다운로드 대기 시간이 길 때 | xargs -P 4 병렬 실행 |
최대 4개 저장소를 동시에 갱신 |
| Windows PowerShell 5.1을 사용할 때 | Get-ChildItem + ForEach-Object |
별도 도구 없이 순차 실행 |
| PowerShell 7 이상을 사용할 때 | ForEach-Object -Parallel |
ThrottleLimit으로 동시 실행 수 제한 |
| 매일 여러 저장소를 관리할 때 | myrepos |
등록한 저장소의 상태·업데이트를 반복 관리 |
처음에는 순차 실행으로 저장소 목록과 인증 상태를 확인한 뒤, 문제가 없을 때 병렬 실행으로 전환하는 것이 가장 안정적입니다.
실행 전에 알아둘 점
git pull은 다운로드만 하는 명령이 아니다
git pull은 원격 변경 사항을 가져오는 git fetch를 실행한 다음, 현재 브랜치에 그 변경을 통합합니다. 즉, 로컬 브랜치가 실제로 변경될 수 있습니다. 인자 없이 실행하면 현재 브랜치에 설정된 upstream을 대상으로 하며, 저장소 안의 모든 로컬 브랜치를 한꺼번에 갱신하는 명령은 아닙니다.
원격 상태만 미리 받아 보고 싶다면 다음 명령이 더 보수적입니다.
git fetch --all --prune
--all: 등록된 모든 원격 저장소를 가져옵니다.--prune: 원격에서 삭제된 브랜치에 대응하는 오래된 원격 추적 참조를 정리합니다.
일괄 작업에서 자동 병합이나 자동 리베이스를 피하고 싶다면 git pull --ff-only를 명시하는 편이 좋습니다. 로컬과 원격 이력이 갈라진 저장소는 자동으로 합치지 않고 실패로 남기므로, 해당 저장소만 따로 확인할 수 있습니다.
다만 --ff-only는 분기된 이력에서 자동 병합을 막는 옵션이지, 수정 중인 작업 트리를 보호하는 기능은 아닙니다. staged·unstaged·untracked 변경은 별도로 검사해야 합니다.
최신 Git은 분기된 브랜치의 조정 방식을 명시하도록 안내합니다. Git 버전과 사용자 설정에 따른 모호함을 없애기 위해 이 글의 명령에서는
--ff-only를 명시합니다.
로컬 변경 사항을 먼저 확인하자
여러 저장소를 한 번에 갱신하기 전에 수정 중인 파일과 현재 브랜치를 확인하는 것이 좋습니다.
find . -type d -name .git -prune -exec sh -c '
for git_dir do
repo=${git_dir%/.git}
printf "\n[Status %s]\n" "$repo"
git -C "$repo" status --short --branch
done
' sh {} +
출력에 M, A, ?? 등이 표시되면 커밋하지 않은 변경 사항이 있다는 뜻입니다. 모든 저장소에 자동으로 stash를 적용하는 방식은 작업 내용을 숨기거나 복구를 어렵게 만들 수 있으므로 권장하지 않습니다. 위 명령은 상태를 보여 주기만 하므로, 사람이 확인하지 않는 cron·CI 작업에는 다음과 같은 사전 검사가 필요합니다.
무인 실행이라면 dirty·detached HEAD·upstream을 검사하자
다음 Bash 스크립트는 먼저 저장소 목록을 모두 수집한 다음, 수정 사항이 있거나 detached HEAD 상태이거나 upstream이 없는 저장소를 건너뜁니다. 탐색 중에 Pull이 작업 트리를 바꾸지 않도록 “탐색”과 “업데이트” 단계도 분리했습니다. 루트 경로가 없거나 탐색이 실패하면 전체 대상 목록을 신뢰할 수 없으므로 Pull을 시작하지 않고 실패로 종료합니다. 정상적으로 탐색했지만 대상이 없는 경우는 별도로 표시합니다.
#!/usr/bin/env bash
root=${1:-.}
markers=()
failed=0
if [[ ! -d $root ]]; then
printf "[FAILED discovery: not a directory] %q\n" "$root" >&2
exit 1
fi
# 상대 경로가 '-' 등으로 시작해도 find의 식으로 해석되지 않게 한다.
case $root in
/*|./*|../*) ;;
*) root=./$root ;;
esac
if ! marker_file=$(mktemp "${TMPDIR:-/tmp}/git-pull-markers.XXXXXXXX"); then
printf '[FAILED discovery: temporary file]\n' >&2
exit 1
fi
trap 'rm -f -- "$marker_file"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
# '/.'은 루트가 디렉터리 심볼릭 링크여도 그 디렉터리부터 탐색하게 한다.
# 하위 심볼릭 링크는 -P에 따라 따라가지 않는다.
if ! find -P "$root"/. -type d -name .git -prune -print0 > "$marker_file"; then
printf "[FAILED discovery] %q\n" "$root" >&2
exit 1
fi
if [[ ! -s $marker_file ]]; then
printf "[NO repositories] %q\n" "$root"
exit 0
fi
while IFS= read -r -d '' marker; do
markers+=("$marker")
done < "$marker_file"
for marker in "${markers[@]}"; do
repo=${marker%/.git}
if ! git -C "$repo" symbolic-ref --quiet HEAD >/dev/null 2>&1; then
printf "[SKIP detached HEAD] %q\n" "$repo"
continue
fi
if ! git -C "$repo" rev-parse --verify --quiet '@{upstream}' >/dev/null; then
printf "[SKIP no upstream] %q\n" "$repo"
continue
fi
if ! state=$(git -C "$repo" status \
--porcelain=v1 \
--untracked-files=normal \
--ignore-submodules=none); then
printf "[FAILED status] %q\n" "$repo" >&2
failed=1
continue
fi
if [[ -n $state ]]; then
printf "[SKIP dirty] %q\n" "$repo"
continue
fi
printf "\n[Updating %s]\n" "$repo"
if ! GIT_TERMINAL_PROMPT=0 \
git -C "$repo" pull --ff-only --no-recurse-submodules; then
printf "[FAILED pull] %q\n" "$repo" >&2
failed=1
fi
done
exit "$failed"
이 예제는 Bash 배열과 [[ ... ]]을 사용하므로 sh script.sh가 아니라 bash script.sh로 실행해야 합니다. GIT_TERMINAL_PROMPT=0은 무인 실행에서 Git의 자격 증명 질문을 기다리지 않고 실패하게 하지만, 외부 SSH 프로그램이 만드는 모든 프롬프트까지 차단하는 것은 아닙니다.
while ... done < <(find ...) 같은 프로세스 치환만으로는 find의 종료 상태가 반복문의 종료 상태로 전달되지 않습니다. 위 예제는 NUL 구분 목록을 임시 파일에 완성하고 find의 성공을 확인한 뒤에만 Git 명령을 실행합니다. set -e만 추가하는 것으로 이 검사를 대신하지 않습니다. 동작 배경은 Bash 공식 Reference Manual의 Process Substitution을 참고하세요. [SKIP ...]는 의도적으로 건너뛴 대상이며, 모든 저장소의 갱신 성공을 뜻하지는 않습니다.
실제 실행 결과: 정상 저장소만 갱신하고 예외는 남겼다
2026년 9월 9일, 위 Bash 사전 검사 스크립트를 공개 본문에서 추출해 수정 없이 실행했습니다. Ubuntu 24.04.3 LTS·Linux 6.18.35·Git 2.51.1·Bash 5.2.21에서 임시 bare 원격 저장소와 독립 clone들을 만들었습니다. 사용자의 작업 저장소나 외부 Git 서버에는 접근하지 않았고, 전송은 로컬 파일 경로로만 제한했습니다.
먼저 같은 초기 커밋에서 8개를 clone한 뒤 상태를 나눴습니다. 정상 저장소 두 개 중 하나는 이름을 02-공백 한글 저장소로 만들었습니다. 나머지는 tracked 파일 수정, staged 파일 추가, untracked 파일 추가, detached HEAD, upstream 제거, 로컬 전용 커밋으로 구분했습니다. 이후 원격에 새 커밋 하나를 올리고 8개가 들어 있는 상위 폴더에 스크립트를 실행했습니다.
| 실행 전 상태 | 실제 처리 | 실행 뒤 확인 |
|---|---|---|
| 정상 저장소 | fast-forward | HEAD가 원격의 새 커밋으로 이동 |
| 공백·한글 경로의 정상 저장소 | fast-forward | 같은 새 커밋과 파일 내용 확인 |
| unstaged 수정 / staged 추가 / untracked 추가 | 각각 [SKIP dirty] |
세 저장소의 HEAD·수정 상태·파일 내용 유지 |
| detached HEAD | [SKIP detached HEAD] |
기존 커밋과 detached 상태 유지 |
| upstream 없음 | [SKIP no upstream] |
기존 브랜치와 파일 유지 |
| 로컬·원격 이력 분기 | [FAILED pull] |
로컬 HEAD와 파일 유지, 자동 병합 없음 |
정상 두 저장소는 실제로 9eef821에서 1fe78e2로 이동했고, 원격에서 바꾼 tracked.txt와 새로 추가한 remote-only.txt가 일치했습니다. 건너뛰거나 Pull에 실패한 저장소는 실행 전후의 HEAD, 브랜치, git status --porcelain과 파일 내용·SHA-256을 대조했습니다. 출력에 SKIP이 보인다는 사실만으로 보존 성공을 판단하지 않았습니다.
분기된 저장소에서는 다음 오류가 발생했습니다. 긴 임시 경로만 줄였습니다.
fatal: Not possible to fast-forward, aborting.
[FAILED pull] .../00-divergent
이 실행의 최종 종료 코드는 1이었지만 정상 저장소 두 개는 갱신됐습니다. 여러 저장소의 Pull은 하나의 트랜잭션이 아니므로, 실패가 나왔다고 앞서 성공한 갱신이 되돌아가지는 않습니다. 또한 분기된 저장소의 로컬 HEAD는 그대로였지만 origin/main은 fetch로 새 커밋을 가리켰습니다. --ff-only 실패를 “저장소 내부에서 아무것도 바뀌지 않음”으로 해석해서는 안 됩니다. Git pull 공식 문서
루트 경로와 종료 코드도 별도로 확인했습니다.
| 별도 입력 조건 | 출력 | 종료 코드 |
|---|---|---|
| 존재하지 않는 루트 | [FAILED discovery: not a directory] |
1 |
| 존재하지만 저장소가 없는 디렉터리 | [NO repositories] |
0 |
| dirty 저장소 하나만 있는 디렉터리 | [SKIP dirty], 갱신 없음 |
0 |
따라서 자동화 결과를 읽을 때는 종료 코드와 FAILED뿐 아니라 SKIP·NO repositories도 함께 확인해야 합니다. 종료 코드 0은 이 스크립트에서 실패로 집계한 일이 없다는 뜻이지, 모든 저장소가 최신이라는 보장은 아닙니다.
이번 검증은 위 Bash 순차 스크립트와 일반 clone만 대상으로 했습니다. PowerShell, xargs 병렬 실행, SSH·HTTPS 인증, worktree·서브모듈, 탐색 권한 오류, 실행 중 다른 프로그램이 파일을 수정하는 상황은 검증하지 않았습니다. 특히 사전 상태 검사와 Pull 사이의 동시 수정까지 막는 잠금 기능은 없으므로, 작업 중인 편집기·다른 Git 자동화와 동시에 실행하지 않는 운영 규칙이 필요합니다.
macOS·Linux·Git Bash에서 순차로 Pull하기
업데이트할 저장소들이 들어 있는 최상위 폴더로 이동합니다.
cd ~/workspace
먼저 실제로 어떤 저장소가 발견되는지 확인합니다.
find . -type d -name .git -prune -exec sh -c '
for git_dir do
printf "%s\n" "${git_dir%/.git}"
done
' sh {} +
목록이 예상과 같다면 다음 명령으로 하나씩 업데이트합니다.
find . -type d -name .git -prune -exec sh -c '
for git_dir do
repo=${git_dir%/.git}
printf "\n[Updating %s]\n" "$repo"
git -C "$repo" pull --ff-only ||
printf "[FAILED] %s\n" "$repo" >&2
done
' sh {} +
이 명령은 한 저장소의 Pull이 실패하더라도 [FAILED]를 표시하고 다음 저장소를 계속 처리합니다.
명령을 부분별로 이해하기
find .: 현재 폴더부터 하위 항목을 탐색합니다.-type d -name .git: 이름이.git인 디렉터리를 찾습니다.-prune: 찾은.git내부까지 불필요하게 다시 탐색하지 않습니다.-exec sh -c '...' sh {} +: 찾은 경로를 셸 코드에 직접 삽입하지 않고 위치 인자로 전달합니다.repo=${git_dir%/.git}: 경로 끝의/.git을 제거해 저장소 루트 경로를 얻습니다.git -C "$repo": 현재 셸의 위치를 바꾸지 않고 해당 폴더에서 Git을 실행합니다.pull --ff-only: fast-forward가 가능한 경우에만 현재 브랜치를 업데이트합니다.
경로를 위치 인자로 전달하고 변수를 큰따옴표로 감쌌기 때문에 폴더 이름에 공백, 한글, 작은따옴표가 있어도 안전하게 처리할 수 있습니다.
왜 cd 대신 git -C를 사용할까?
다음과 같이 매번 폴더를 이동해도 동작은 합니다.
cd "$repo" && git pull
하지만 반복문에서 현재 위치가 계속 바뀌면 실패 이후의 흐름을 추적하기 어려워질 수 있습니다. git -C <경로>는 “해당 경로에서 Git을 실행한 것처럼” 동작하므로 원래 작업 위치를 유지하면서 여러 저장소를 다루기에 적합합니다.
pwd와 $(pwd)는 무엇이 다를까?
pwd는 Print Working Directory의 약자로 현재 작업 중인 폴더의 경로를 출력합니다.
pwd
예를 들어 다음과 같이 출력될 수 있습니다.
/Users/example/workspace/backend-api
$(pwd)는 pwd 명령의 출력 결과를 다른 문자열 안에 넣는 명령 치환입니다.
printf "[Updating %s]\n" "$(pwd)"
따라서 저장소로 먼저 이동한 뒤 $(pwd)를 출력하면 현재 어떤 저장소를 처리하는지 확인할 수 있습니다. 중첩된 sh -c 명령 안에서 실행 시점을 늦추려면 바깥 큰따옴표 문맥에서 \$(pwd)처럼 이스케이프하는 예제도 볼 수 있습니다.
다만 이 글의 권장 명령은 폴더를 이동하지 않고 git -C를 사용하므로 $(pwd) 대신 이미 확보한 $repo 경로를 출력합니다. 이 방식이 따옴표와 실행 시점을 이해하기 더 쉽습니다.
xargs -P 4로 여러 저장소 병렬 업데이트하기
저장소가 수십 개라면 네트워크 응답을 기다리는 시간이 누적됩니다. 다음 명령은 최대 4개 저장소를 동시에 처리합니다.
find . -type d -name .git -prune -print0 |
xargs -0 -n 1 -P 4 sh -c '
[ "$#" -eq 1 ] || exit 0
repo=${1%/.git}
printf "\n[Pulling %s]\n" "$repo"
git -C "$repo" pull --ff-only
' sh
공백이 있는 경로를 안전하게 처리하는 핵심
find의 기본 출력은 경로를 줄바꿈으로 구분하고, 기본 xargs는 공백과 줄바꿈을 인자 구분자로 해석하며 따옴표와 역슬래시도 특별하게 처리합니다. 따라서 파일명에 이런 문자가 있으면 경로가 나뉘거나 바뀔 수 있어 -print0과 -0을 함께 사용합니다.
find ... -print0: 각 경로의 끝을 NUL 문자로 구분합니다.xargs -0: NUL 문자로 구분된 입력을 그대로 읽습니다.-n 1: 한 번의 셸 실행에 저장소 하나만 전달합니다.-P 4: 최대 4개의 프로세스를 동시에 실행합니다.
-P 4는 무조건 큰 숫자로 바꾼다고 빨라지지 않습니다. 원격 서버의 요청 제한, 디스크 I/O, SSH 인증 처리, 네트워크 대역폭이 병목이 될 수 있습니다. 일반적인 개발 PC에서는 4부터 시작하고, 문제가 없다면 6~8 정도로 조금씩 늘리는 편이 좋습니다.
병렬 방식은 서로 독립적인 형제 저장소에만 사용하는 것이 좋습니다. 부모 저장소와 그 안의 중첩 저장소, 동일한 Git 공용 디렉터리를 공유하는 worktree, 상위 저장소와 서브모듈을 같은 병렬 그룹에 넣으면 서로의 작업 트리나 잠금에 영향을 줄 수 있습니다. 여러 저장소 갱신은 하나의 원자적 트랜잭션도 아니므로, 중간에 하나가 실패해도 이미 성공한 저장소는 자동으로 이전 상태로 돌아가지 않습니다.
병렬 실행 중에는 여러 저장소의 로그가 섞여 보일 수 있습니다. 오류 원인을 자세히 봐야 하거나 인증 입력이 필요하다면 먼저 순차 방식으로 실행하세요.
Windows PowerShell에서 재귀적으로 Pull하기
단순히 Get-ChildItem -Directory만 사용하면 현재 위치 바로 아래의 모든 폴더에 Git 명령을 시도하고, 더 깊은 위치의 저장소는 놓칠 수 있습니다. .git 디렉터리만 재귀적으로 찾도록 조건을 지정해야 합니다. 아래 PowerShell 예제도 일반 clone처럼 .git이 디렉터리인 저장소를 대상으로 하며, .git이 파일인 worktree·서브모듈은 의도적으로 제외합니다.
Windows PowerShell 5.1에서도 가능한 순차 실행
$repos = Get-ChildItem -Path . -Directory -Filter .git -Recurse -Force |
ForEach-Object { $_.Parent.FullName } |
Sort-Object -Unique
$repos | ForEach-Object {
$repo = $_
Write-Host "`n[Updating $repo]" -ForegroundColor Green
git -C $repo pull --ff-only
if ($LASTEXITCODE -ne 0) {
Write-Warning "[FAILED] $repo"
}
}
-Recurse: 모든 하위 폴더를 탐색합니다.-Force: 숨김 항목인.git도 탐색 대상에 포함합니다.Parent.FullName:.git의 상위 폴더, 즉 저장소 루트 경로를 가져옵니다.$LASTEXITCODE: 직전에 실행한 Git 프로세스의 종료 코드를 확인합니다.
PowerShell 7 이상에서 4개씩 병렬 실행
PowerShell 7부터 ForEach-Object -Parallel을 사용할 수 있습니다.
$repos = Get-ChildItem -Path . -Directory -Filter .git -Recurse -Force |
ForEach-Object { $_.Parent.FullName } |
Sort-Object -Unique
$repos | ForEach-Object -Parallel {
$repo = $_
Write-Host "`n[Pulling $repo]" -ForegroundColor Cyan
git -C $repo pull --ff-only
if ($LASTEXITCODE -ne 0) {
Write-Warning "[FAILED] $repo"
}
} -ThrottleLimit 4
ThrottleLimit 4는 동시에 실행되는 스크립트 블록을 4개로 제한합니다. Windows에 기본 포함된 Windows PowerShell 5.1에서는 -Parallel을 사용할 수 없으므로 앞의 순차 명령을 사용해야 합니다.
git pull 대신 git fetch가 더 나은 경우
업무 시작 전에 원격 변경 여부만 확인하거나, 로컬 브랜치를 자동으로 움직이고 싶지 않다면 모든 저장소에 fetch만 실행하는 편이 안전합니다.
find . -type d -name .git -prune -exec sh -c '
for git_dir do
repo=${git_dir%/.git}
printf "\n[Fetching %s]\n" "$repo"
git -C "$repo" fetch --all --prune ||
printf "[FAILED] %s\n" "$repo" >&2
done
' sh {} +
이후 필요한 저장소에 들어가 다음과 같이 차이를 확인할 수 있습니다.
git status --short --branch
git log --oneline --decorate --graph --all -n 20
정리하면 다음과 같습니다.
- 로컬 브랜치를 바로 최신 상태로 맞추려면
git pull --ff-only - 원격 정보만 받아 검토한 뒤 반영하려면
git fetch --all --prune
자주 발생하는 오류와 대응 방법
로컬과 원격 브랜치가 갈라진 경우
--ff-only가 실패하는 대표적인 상황입니다. 해당 저장소로 이동해 이력을 확인한 뒤 팀 정책에 맞게 rebase 또는 merge를 선택해야 합니다.
git status
git log --oneline --graph --decorate --all -n 30
일괄 스크립트 안에서 자동으로 --rebase나 강제 초기화를 적용하면 여러 저장소의 작업 내용이 예상치 못하게 바뀔 수 있으므로 피하는 것이 좋습니다.
추적 브랜치가 설정되지 않은 경우
현재 브랜치에 upstream이 없으면 Pull 대상이 불분명하다는 오류가 발생할 수 있습니다.
git branch -vv
git branch --set-upstream-to=origin/main main
브랜치 이름과 원격 이름은 실제 저장소 구성에 맞게 바꿔야 합니다.
인증·권한 오류가 반복되는 경우
병렬 실행 전에 저장소 하나에서 git pull --ff-only가 정상 동작하는지 확인하세요. SSH 저장소라면 SSH agent, HTTPS 저장소라면 운영체제의 Git Credential Manager가 설정되어 있어야 여러 인증 프롬프트가 동시에 뜨는 일을 줄일 수 있습니다. 무인 Bash 작업에서는 GIT_TERMINAL_PROMPT=0을 Git 명령 앞에 붙여 대화형 자격 증명 요청을 즉시 실패 처리할 수 있습니다. 토큰을 원격 URL에 직접 넣는 방식은 로그와 설정에 노출될 수 있으므로 사용하지 않는 것이 좋습니다.
일부 저장소만 실패하는 경우
순차 예제는 [FAILED]를 표시한 뒤 나머지 저장소를 계속 처리합니다. 병렬 예제도 이미 시작된 작업은 계속 진행되며, xargs는 하위 명령 실패를 비정상 종료 코드로 반환할 수 있습니다. 마지막 로그에서 실패 경로를 찾아 해당 저장소만 다시 점검하세요.
로그가 너무 뒤섞이는 경우
병렬 수를 -P 2로 낮추거나 순차 실행으로 되돌리면 됩니다. 속도보다 실패 원인 확인이 중요한 최초 실행에서는 순차 방식이 더 낫습니다.
자주 사용한다면 셸 함수로 저장하기
매번 긴 명령을 입력하기 어렵다면 ~/.bashrc 또는 ~/.zshrc에 다음 함수를 추가할 수 있습니다.
git_pull_all() {
find "${1:-.}" -type d -name .git -prune -exec sh -c '
for git_dir do
repo=${git_dir%/.git}
printf "\n[Updating %s]\n" "$repo"
git -C "$repo" pull --ff-only ||
printf "[FAILED] %s\n" "$repo" >&2
done
' sh {} +
}
셸 설정을 다시 불러온 뒤 상위 폴더를 인자로 전달합니다.
source ~/.zshrc
git_pull_all ~/workspace
Bash를 사용한다면 source ~/.bashrc로 불러오면 됩니다.
저장소가 더 많다면 myrepos 활용하기
저장소 업데이트를 자주 반복한다면 명령 조합보다 다중 저장소 관리 도구가 편할 수 있습니다. myrepos는 mr 명령으로 등록한 여러 버전 관리 저장소를 한 번에 관리합니다.
macOS에서는 Homebrew로 설치할 수 있습니다.
brew install mr
Debian·Ubuntu 계열에서는 다음과 같이 설치합니다.
sudo apt install myrepos
각 저장소 안에서 한 번씩 등록합니다.
mr register
등록한 저장소 전체를 업데이트하거나 4개씩 병렬로 처리할 수 있습니다.
mr update
mr -j4 update
mr update가 각 저장소에서 실행할 명령은 설정으로 바꿀 수 있습니다. 팀마다 rebase·merge·fast-forward 정책이 다르다면 도구를 도입하기 전에 업데이트 동작을 명시적으로 맞추세요.
Git worktree와 서브모듈은 별도로 생각해야 한다
이 글의 기본 명령은 .git이 디렉터리인 일반적인 clone 저장소를 대상으로 합니다. Git worktree와 일부 서브모듈에서는 저장소 루트의 .git이 디렉터리가 아니라 실제 Git 디렉터리를 가리키는 파일일 수 있어 -type d 탐색에 포함되지 않습니다.
서브모듈은 상위 저장소가 기록한 커밋에 맞춰 다음 명령으로 갱신하는 것이 일반적입니다.
git submodule update --init --recursive
--remote를 추가하면 기록된 커밋이 아니라 서브모듈의 원격 추적 브랜치를 기준으로 동작하므로 의미가 달라집니다. 일괄 Pull 명령에 서브모듈과 worktree를 무조건 섞기보다 각각의 관리 정책을 정한 뒤 별도로 처리하는 편이 안전합니다.
자주 묻는 질문
커밋하지 않은 변경 사항이 있어도 일괄 Pull해도 될까?
Git이 항상 거부하는 것은 아니지만, 원격 변경과 겹치면 Pull이 중단되거나 충돌이 발생할 수 있습니다. 먼저 일괄 git status --short --branch를 실행하고, 변경 사항을 커밋하거나 저장소별로 정리한 뒤 진행하세요.
git pull --ff-only는 일반 git pull과 무엇이 다른가?
fast-forward로 곧바로 이동할 수 있을 때만 업데이트하고, 로컬과 원격 이력이 갈라졌다면 자동으로 합치지 않고 실패합니다. 여러 저장소를 무인 또는 반자동으로 갱신할 때 예상치 못한 병합을 줄이는 데 유용합니다.
폴더 이름에 공백이나 한글이 있어도 동작할까?
이 글의 권장 명령은 경로를 위치 인자로 전달하고 변수를 큰따옴표로 감쌉니다. 병렬 명령도 -print0와 -0을 사용하므로 공백과 한글이 포함된 일반적인 경로를 안전하게 처리합니다.
xargs -P의 숫자는 어느 정도가 적당할까?
4부터 시작하는 것이 무난합니다. 저장소가 작고 네트워크와 인증에 문제가 없다면 6~8로 늘릴 수 있지만, 원격 서비스의 요청 제한이나 디스크 병목이 나타나면 오히려 느려질 수 있습니다.
한 저장소가 실패하면 나머지도 중단될까?
이 글의 순차 명령은 실패를 출력하고 다음 저장소로 넘어갑니다. 병렬 명령도 이미 시작된 다른 작업은 진행됩니다. 다만 최종 종료 코드는 실패를 나타낼 수 있으므로 CI나 자동화에서 사용할 때는 종료 코드와 로그를 함께 확인해야 합니다.
PowerShell 5.1에서도 병렬 실행할 수 있을까?
ForEach-Object -Parallel은 PowerShell 7 이상에서 지원됩니다. Windows PowerShell 5.1에서는 순차 예제를 사용하거나 별도의 Job 기반 스크립트를 작성해야 합니다.
마무리 체크리스트
- 처음에는 저장소 목록과
git status만 출력해 대상을 확인합니다. - 자동 병합을 피하려면
git pull --ff-only를 명시합니다. - 공백이 포함된 경로를 위해
-print0과xargs -0을 함께 사용합니다. - 병렬 실행 수는 4부터 시작합니다.
- 인증 입력이나 오류 분석이 필요하면 순차 실행으로 전환합니다.
- worktree와 서브모듈은 일반 clone 저장소와 분리해 관리합니다.
- 반복 작업이 많다면 셸 함수 또는
myrepos를 사용합니다.
저장소 몇 개를 가끔 갱신한다면 순차 find 명령만으로 충분합니다. 수십 개의 저장소를 반복 관리한다면 병렬 실행이나 전용 도구가 시간을 크게 줄여 줍니다. 중요한 점은 “모두 자동으로 합치기”가 아니라, 안전하게 업데이트할 수 있는 저장소만 처리하고 예외를 분명하게 남기는 것입니다.