Operations 13 min read

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.

Cilantro Loves Linux
Cilantro Loves Linux
Cilantro Loves Linux
SVN to Git Migration Pitfall: Empty Directories Disappear? One-Click Python Fix

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.py

Scenario 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/code

Confirm Execution

The script prints a warning with the target path and waits for confirmation:

警告:即将处理以下目录及其所有子目录:
  D:\03_学习\demo
是否继续?(y/n):y

Review 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)
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

migrationDevOpsGitVersion ControlPython scriptSVNempty directories.gitkeep
Cilantro Loves Linux
Written by

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!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.