본문으로 건너뛰기
Previous
Next
Git에서 파일 이동·이름 변경 제대로 하기 — git mv 완벽 가이드

Git에서 파일 이동·이름 변경 제대로 하기 — git mv 완벽 가이드

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를 쓰는 이유

  1. 명시적: 의도가 명확히 드러남
  2. 안전: 파일 존재 여부 자동 확인
  3. 원자적: 한 번에 처리되어 실수 방지
  4. 리뷰 친화적: 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

기억할 점

  1. 항상 git mv 사용: 히스토리 보존의 핵심
  2. 이동과 수정 분리: 리뷰어를 배려
  3. 논리적 단위로 커밋: 되돌리기 쉽게
  4. 테스트 후 커밋: 빌드 깨짐 방지

다음 단계


이 글이 도움이 되었나요? 파일 이동 시 겪은 어려움이나 팁이 있다면 댓글로 공유해 주세요! 🚀