SVN to Git Migration Pitfall: Empty Directories Disappear? One-Click Python Fix
This article explains why empty directories vanish when migrating from SVN to Git due to Git's design, introduces the .gitkeep convention as a solution, and provides a safe, cross-platform Python script that automatically adds .gitkeep files to empty directories with confirmation prompts and error handling.
1. Migration Pain Point: SVN vs Git Directory Handling
Many teams migrating from SVN to Git aim to preserve the original code structure. However, SVN and Git treat directories fundamentally differently:
SVN: Directories as Versioned Entities
In SVN, directories are independent versioned objects just like files. Even an empty folder created in SVN is recorded in the repository and retains its hierarchy during migration. For example, an empty structure like /aa/bb/ is stored intact.
Git: Only Tracks Files, Not Empty Directories
Git's core design tracks file changes; it does not consider empty directories as versionable objects. A directory is merely a container for files — if it contains no files, Git ignores it entirely. This causes a critical conflict during migration: empty directories from SVN (e.g., reserved classification folders or placeholder resource directories) disappear when pushed to Git, breaking the original structure.
2. Solution Principle: .gitkeep Placeholder Files
Since Git only retains directories that contain files, the community-adopted workaround is to place a dummy file named .gitkeep inside each empty directory. This file is not an official Git command but a widely accepted convention.
Purpose: Adds a file to an empty directory so Git treats it as non-empty and includes it in version control.
Characteristics: The file can be empty; naming it .gitkeep clarifies its intent (alternatives like .keep work but are less universal).
Key Point: The file does not affect code functionality; it simply signals Git to preserve the directory.
The migration solution is therefore: traverse all directories exported from SVN, automatically create .gitkeep in every empty directory, then push to Git to retain the original hierarchy.
3. Tool: Safe Cross-Platform Empty Directory Script
Manually adding .gitkeep to hundreds of empty directories is error-prone and tedious. The article provides a dedicated Python script ( create_gitkeep_safe.py) with multiple safety features:
Precise Identification: Only creates .gitkeep in truly empty directories (no files or subdirectories, or only an existing .gitkeep). Non-empty directories are skipped automatically.
Controlled Scope: Strictly limits scanning to the specified root directory and its subdirectories, preventing accidental full-disk traversal.
Cross-Platform Compatibility: Works on both Windows and Linux without modification.
Dual Safeguards: Displays the target directory before execution and requires manual confirmation ( y / n); silently skips directories with permission errors without halting.
Two-Phase Execution: First collects all qualifying empty directories, then creates .gitkeep files in a separate pass to avoid race conditions where newly created files could affect detection.
4. Quick Usage Guide
Prerequisites
Python 3.5+ (system-installed or from python.org).
Save the script as create_gitkeep_safe.py in the repository root (or any convenient location).
Run the Script
Open a terminal (CMD/PowerShell on Windows, Terminal on Linux), navigate to the repository root, and execute:
Scenario 1: Process Current Directory (Recommended)
python create_gitkeep_safe.pyScenario 2: Process a Specific Directory
# Windows example (process D:\03_学习\demo)
python create_gitkeep_safe.py D:\03_学习\demo
# Linux example (process /home/user/code)
python create_gitkeep_safe.py /home/user/codeConfirm Execution
The script prints a warning with the target path and waits for confirmation:
警告:即将处理以下目录及其所有子目录:
D:\03_学习\demo
是否继续?(y/n):yReview Results
After completion, the script reports each directory where .gitkeep was created and a summary:
已在 D:\03_学习\demo\aa\bb 创建 .gitkeep
已在 D:\03_学习\demo\cc 创建 .gitkeep
处理完成,共识别 2 个空目录,成功创建 2 个 .gitkeep 文件Then run the usual git add ., git commit, git push — the empty directories will now be pushed to the Git repository, matching the SVN structure exactly.
5. Migration Tips
Back up the code directory before running the script (though it only creates files, caution is wise).
If a "directory not found" error occurs, verify the path spelling; on Windows both backslashes ( \) and forward slashes ( /) work (e.g., D:/03_学习/demo).
The script is idempotent: re-run it anytime new empty directories appear to automatically add missing .gitkeep files without manual maintenance.
6. Source Code
import os
import sys
import platform
def is_really_empty(folder_path):
"""Determine if a folder is truly empty (no files/subdirs, or only .gitkeep with nothing else)."""
try:
entries = os.listdir(folder_path)
# Completely empty -> empty directory
if not entries:
return True
# Only .gitkeep present -> already processed
if len(entries) == 1 and entries[0] == ".gitkeep":
return False
# Otherwise non-empty
return False
except Exception as e:
# Ignore permission-denied directories
if "Permission denied" in str(e) or "拒绝访问" in str(e):
return False
print(f"检查文件夹 {folder_path} 时出错: {e}")
return False
def create_gitkeep_for_empty_folders(root_dir):
# Convert to absolute path for correctness
root_dir = os.path.abspath(root_dir)
# Validate directory existence
if not os.path.exists(root_dir):
print(f"错误: 目录 {root_dir} 不存在")
return
if not os.path.isdir(root_dir):
print(f"错误: {root_dir} 不是一个目录")
return
# Safety check: show target and require confirmation
print(f"
警告:即将处理以下目录及其所有子目录:")
print(f" {root_dir}")
confirm = input("是否继续?(y/n):").strip().lower()
if confirm != 'y' and confirm != 'yes':
print("操作已取消")
return
# Phase 1: collect all qualifying empty directories
empty_folders = []
for dirpath, _, _ in os.walk(root_dir):
# Ensure we stay within the specified root
if not dirpath.startswith(root_dir):
print(f"警告:尝试访问目录 {dirpath} 超出了指定根目录范围,已跳过")
continue
if is_really_empty(dirpath):
empty_folders.append(dirpath)
# Phase 2: create .gitkeep for each collected directory
created_count = 0
for folder in empty_folders:
# Re-verify path is within root
if not folder.startswith(root_dir):
print(f"警告:目录 {folder} 超出处理范围,已跳过")
continue
gitkeep_path = os.path.join(folder, ".gitkeep")
try:
with open(gitkeep_path, 'w') as f:
pass # create empty file
created_count += 1
print(f"已在 {folder} 创建 .gitkeep")
except Exception as e:
print(f"在 {folder} 创建 .gitkeep 时出错: {e}")
print(f"
处理完成,共识别 {len(empty_folders)} 个空目录,成功创建 {created_count} 个 .gitkeep 文件")
if __name__ == "__main__":
# Get and validate root directory
if len(sys.argv) > 1:
root_directory = sys.argv[1]
else:
root_directory = os.getcwd()
# Windows-specific guard to avoid scanning all drives
if platform.system() == "Windows":
if not os.path.abspath(root_directory).startswith(('C:\\', 'D:\\', 'E:\\', 'F:\\')):
print(f"警告:在Windows系统上检测到非标准路径,已自动更正为当前工作目录")
root_directory = os.getcwd()
create_gitkeep_for_empty_folders(root_directory)Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Cilantro Loves Linux
Focused on Linux system operations and maintenance technology exchange, regularly publishing in-depth technical articles covering core areas such as system architecture, performance optimization, and security hardening. Whether you are a beginner or a seasoned expert, you can find valuable resources to enhance your skills and solve problems here. Follow us to explore the mysteries of Linux operations!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
