Git에서 파일 이동·이름 변경 제대로 하기 — git mv 완벽 가이드
이 글의 핵심
Git에서 파일을 이동하거나 이름을 변경할 때 git mv를 사용해야 하는 이유와 실전 활용법. git mv vs mv 차이, 히스토리 보존, 대량 파일 처리
Git 파일 이동·이름 변경 완벽 가이드
이 글을 읽으면 Git에서 파일을 이동하거나 이름을 변경할 때 히스토리를 보존하면서 안전하게 작업하는 방법을 배울 수 있습니다.
프로젝트 리팩토링이나 디렉터리 구조 개편을 할 때, 파일을 옮기거나 이름을 바꾸는 일은 흔합니다. 하지만 잘못 옮기면 Git 히스토리가 끊어져서 git blame이나 git log로 변경 이력을 추적할 수 없게 됩니다.
이 글에서는 git mv 명령어를 사용해 파일 히스토리를 보존하면서 안전하게 파일을 관리하는 방법을 다룹니다.
헷갈리기 쉬운 것: mv + git add로도 이동이 가능하지만, git mv가 명시적이고 안전합니다. 특히 대량 파일 이동 시 실수를 줄일 수 있습니다.
1. 왜 git mv를 써야 할까?
일반 mv의 문제점
# ❌ 잘못된 방법: 일반 mv 사용
mv old-name.js new-name.js
git add new-name.js
git rm old-name.js
git commit -m "Rename file"
이렇게 하면:
- Git이 삭제 + 새 파일 생성으로 인식할 수 있음
- 파일 히스토리가 끊어질 위험
- Code review에서 전체 파일이 diff로 표시됨
git mv의 장점
# ✅ 올바른 방법: git mv 사용
git mv old-name.js new-name.js
git commit -m "Rename old-name.js to new-name.js"
장점:
- Git이 이름 변경(rename)으로 명확히 인식
- 파일 히스토리 완벽 보존
git log --follow로 이전 이름의 히스토리도 추적 가능- Code review 시 rename으로 표시되어 가독성 향상
2. 기본 사용법
파일 이름 변경
# 같은 디렉터리에서 이름만 변경
git mv README.md README_OLD.md
# 상태 확인
git status
# renamed: README.md -> README_OLD.md
파일을 다른 디렉터리로 이동
# 디렉터리 생성 (필요시)
mkdir src/utils
# 파일 이동
git mv helper.js src/utils/helper.js
# 여러 파일 한 번에 이동
git mv *.js src/
디렉터리째 이동
# 디렉터리 이름 변경
git mv old-folder/ new-folder/
# 디렉터리를 다른 위치로 이동
git mv components/ src/components/
3. 실전 예제: 프로젝트 구조 개편
시나리오: 영문 포스트를 en/ 폴더로 정리
# 현재 구조
blog/
├── post-1-en.md
├── post-2-en.md
└── post-3.md
# 목표 구조
blog/
├── en/
│ ├── post-1.md
│ ├── post-2.md
└── post-3.md
자동화 스크립트
# move_en_posts.py
import os
import subprocess
blog_dir = 'blog/'
en_dir = os.path.join(blog_dir, 'en')
# en 폴더 생성
os.makedirs(en_dir, exist_ok=True)
# -en.md 파일 찾기
for filename in os.listdir(blog_dir):
if filename.endswith('-en.md'):
old_path = os.path.join(blog_dir, filename)
new_filename = filename.replace('-en.md', '.md')
new_path = os.path.join(en_dir, new_filename)
# git mv 실행
result = subprocess.run(
['git', 'mv', old_path, new_path],
capture_output=True,
text=True
)
if result.returncode == 0:
print(f'✅ Moved: {filename} -> en/{new_filename}')
else:
print(f'❌ Failed: {filename}')
print(f' Error: {result.stderr}')
print('\n커밋하기:')
print('git commit -m "refactor: move English posts to en/ folder"')
실행 결과
$ python move_en_posts.py
✅ Moved: post-1-en.md -> en/post-1.md
✅ Moved: post-2-en.md -> en/post-2.md
커밋하기:
git commit -m "refactor: move English posts to en/ folder"
$ git status
Changes to be committed:
renamed: blog/post-1-en.md -> blog/en/post-1.md
renamed: blog/post-2-en.md -> blog/en/post-2.md
4. 대량 파일 이동 Best Practices
단계별 접근
# 1단계: 백업 브랜치 생성
git checkout -b refactor-file-structure
git checkout -b backup-before-refactor # 안전장치
# 2단계: 작은 단위로 이동 (리뷰 가능하게)
git mv src/components-old/* src/components/
git commit -m "refactor: move components to new structure"
# 3단계: 테스트 실행
npm test
# 4단계: 문제 없으면 다음 단계
git mv src/utils-old/* src/utils/
git commit -m "refactor: move utils to new structure"
주의사항
❌ 한 번에 모든 것을 옮기지 마세요
# 나쁜 예: 수백 개 파일을 한 커밋에
git mv src/* new-src/
git commit -m "refactor: restructure everything"
✅ 논리적 단위로 나누세요
# 좋은 예: 의미 있는 단위로 분리
git mv src/components/* src/ui/components/
git commit -m "refactor: move components to ui folder"
git mv src/api/* src/services/api/
git commit -m "refactor: move API modules to services"
5. 히스토리 추적하기
git log —follow 옵션
파일 이름이 변경되었을 때 이전 히스토리를 보려면:
# 이름 변경 후에도 전체 히스토리 확인
git log --follow src/utils/helper.js
# 누가, 언제, 왜 수정했는지 확인
git blame src/utils/helper.js
# 이름 변경 전 버전과 비교
git log --follow --diff-filter=R src/utils/helper.js
실제 사용 예
$ git log --follow --oneline src/en/nginx-guide.md
a1b2c3d (HEAD) docs: update nginx configuration
e4f5g6h refactor: move English posts to en/ folder # ← 이동 커밋
h7i8j9k docs: add nginx load balancing section
k0l1m2n docs: initial nginx guide
# 이동 전 파일명으로 추적됨!
6. 흔한 실수와 해결법
실수 1: 파일 이동과 내용 수정을 동시에
# ❌ 나쁜 예
git mv old.js new.js
# new.js 내용 수정
git commit -m "Rename and refactor old.js"
문제: Git이 이동인지 새 파일인지 헷갈림
# ✅ 좋은 예
git mv old.js new.js
git commit -m "Rename old.js to new.js"
# 별도 커밋으로 수정
# new.js 내용 수정
git commit -m "Refactor new.js logic"
실수 2: 대소문자만 변경 (case-only rename)
# ❌ 나쁜 예 (macOS/Windows에서)
git mv README.md readme.md
# 작동하지 않을 수 있음!
해결법:
# ✅ 임시 이름 사용
git mv README.md temp.md
git mv temp.md readme.md
git commit -m "Rename README.md to lowercase"
실수 3: 이동 후 import 경로 미수정
# 파일 이동
git mv src/utils.js src/lib/utils.js
# ❌ import 경로를 안 고침
// other-file.js
import { helper } from './utils'; // 404!
해결법: 이동 후 import 경로 일괄 수정
# 모든 import 경로 찾기
grep -r "from './utils'" src/
# 또는 IDE의 "Find and Replace" 사용
7. git mv vs mv + git add/rm
내부적으로는 동일
# 다음 두 가지는 결과적으로 같음
git mv old.js new.js
# = 다음과 동일
mv old.js new.js
git add new.js
git rm old.js
그럼에도 git mv를 쓰는 이유
- 명시적: 의도가 명확히 드러남
- 안전: 파일 존재 여부 자동 확인
- 원자적: 한 번에 처리되어 실수 방지
- 리뷰 친화적: PR에서 rename으로 표시
# GitHub PR에서
git mv old.js new.js
# → "renamed: old.js → new.js" (간결)
mv old.js new.js && git add . && git rm old.js
# → deleted old.js + created new.js (복잡)
8. 대량 이동 자동화 스크립트
Bash 스크립트
#!/bin/bash
# move_to_src.sh - 루트의 .js 파일을 src/로 이동
# src 디렉터리 생성
mkdir -p src
# .js 파일 찾아서 이동
for file in *.js; do
if [ -f "$file" ]; then
echo "Moving $file to src/"
git mv "$file" "src/$file"
fi
done
echo "Done! Review changes:"
git status
Python 스크립트 (더 복잡한 로직)
#!/usr/bin/env python3
# reorganize_project.py
import os
import subprocess
from pathlib import Path
def git_mv(old, new):
"""git mv 실행 및 에러 핸들링"""
# 부모 디렉터리 생성
os.makedirs(os.path.dirname(new), exist_ok=True)
result = subprocess.run(
['git', 'mv', old, new],
capture_output=True,
text=True
)
if result.returncode == 0:
return True, f"✅ {old} → {new}"
else:
return False, f"❌ {old}: {result.stderr.strip()}"
def main():
# 이동 규칙 정의
rules = {
'components': 'src/ui/components',
'utils': 'src/lib/utils',
'hooks': 'src/lib/hooks',
}
moved = []
failed = []
for old_dir, new_dir in rules.items():
if not os.path.exists(old_dir):
continue
for file in Path(old_dir).glob('**/*.js'):
old_path = str(file)
new_path = old_path.replace(old_dir, new_dir, 1)
success, msg = git_mv(old_path, new_path)
if success:
moved.append(msg)
else:
failed.append(msg)
# 결과 출력
print(f"\n✅ Successfully moved: {len(moved)}")
for msg in moved[:10]: # 처음 10개만
print(f" {msg}")
if len(moved) > 10:
print(f" ... and {len(moved) - 10} more")
if failed:
print(f"\n❌ Failed: {len(failed)}")
for msg in failed:
print(f" {msg}")
print("\n다음 단계:")
print(" 1. git status로 확인")
print(" 2. 테스트 실행")
print(" 3. git commit -m 'refactor: reorganize project structure'")
if __name__ == '__main__':
main()
실행 예
$ python reorganize_project.py
✅ Successfully moved: 45
✅ components/Button.js → src/ui/components/Button.js
✅ components/Input.js → src/ui/components/Input.js
... and 43 more
다음 단계:
1. git status로 확인
2. 테스트 실행
3. git commit -m 'refactor: reorganize project structure'
9. 트러블슈팅
문제: “fatal: bad source”
$ git mv nonexistent.js new.js
fatal: bad source, source=nonexistent.js, destination=new.js
원인: 원본 파일이 존재하지 않음
해결:
# 파일 존재 확인
ls -la nonexistent.js
# Git 추적 여부 확인
git ls-files | grep nonexistent.js
문제: “destination exists”
$ git mv old.js new.js
fatal: destination exists, source=old.js, destination=new.js
해결:
# 강제로 덮어쓰기 (-f 옵션)
git mv -f old.js new.js
# 또는 대상 파일을 먼저 백업
git mv new.js new.js.bak
git mv old.js new.js
문제: 파일이 unstaged 상태
$ git mv modified.js new.js
fatal: not under version control, source=modified.js
해결:
# 먼저 스테이징
git add modified.js
git mv modified.js new.js
10. 베스트 프랙티스 체크리스트
✅ 파일 이동 전
- 현재 브랜치가 최신인지 확인 (
git pull) - 작업 중인 변경사항 커밋 또는 stash
- 백업 브랜치 생성 (대규모 리팩토링 시)
- 이동 계획 문서화 (팀 공유)
✅ 이동 중
-
git mv명령어 사용 - 논리적 단위로 나눠서 이동
- 각 단계마다 커밋 메시지 명확히
- 테스트가 통과하는지 확인
✅ 이동 후
- Import 경로 수정
- 빌드/테스트 실행
-
git log --follow로 히스토리 확인 - PR에서 rename으로 표시되는지 확인
- 문서 (README 등) 업데이트
커밋 메시지 예시
# 단순 이동
git commit -m "refactor: move components to src/ui/"
# 이동 + 이유
git commit -m "refactor: move English posts to en/ folder
- Separate English and Korean content
- Simplify path detection logic
- Prepare for i18n structure"
# 대규모 리팩토링
git commit -m "refactor: reorganize project structure (1/3)
Part 1: Move UI components
- components/ → src/ui/components/
- 45 files affected
- All tests passing"
정리
핵심 요약
| 명령어 | 용도 | 예시 |
|---|---|---|
git mv old new | 파일 이름 변경 | git mv README.md README_v2.md |
git mv file dir/ | 파일 이동 | git mv app.js src/app.js |
git mv dir1/ dir2/ | 디렉터리 이동 | git mv old/ new/ |
git log --follow | 이동 후 히스토리 추적 | git log --follow src/new.js |
기억할 점
- 항상 git mv 사용: 히스토리 보존의 핵심
- 이동과 수정 분리: 리뷰어를 배려
- 논리적 단위로 커밋: 되돌리기 쉽게
- 테스트 후 커밋: 빌드 깨짐 방지
다음 단계
- Git 인터랙티브 리베이스로 커밋 정리하기
- Git 워크플로우 베스트 프랙티스
- Git으로 협업하기 — 브랜치 전략
이 글이 도움이 되었나요? 파일 이동 시 겪은 어려움이나 팁이 있다면 댓글로 공유해 주세요! 🚀