概要
異なるOS間でGitリポジトリを共有する際、WindowsとUnix系システムの差異により発生する三つの主要な問題点とその解決策について解説します。特にUnix系環境で作成されたリポジトリをWindowsで利用する場合の実践的な対処法を中心に、設定ベースの対応からスクリプトによる自動化までを網羅します。
改行コードの自動変換設定
各OSにおける改行表現の違いは、ファイルの差分検出に深刻な影響を与えます。WindowsがCRLF(\r\n)を採用するのに対し、LinuxはLF(\n)、macOSはCR(\r)を使用します。この不一致により、コードをcheckoutしただけでmodified状態になってしまう事象が頻発します。
Gitには組み込みの変換機構が存在し、コア設定を有効化するだけで自動的に変換を行います。チェックアウト時にLFをCRLFに、コミット時にCRLFをLFに変換する設定は以下の通りです。
git config --global core.autocrlf true
ファイル権限差異の無視設定
Unix系システムの所有者・グループ・その他ユーザに対する実行権限は、Windowsには相当する概念が存在しません。この乖離により、実行ビットの設定差異だけで大量のファイルがmodifiedとして検出されるtypechange問題が発生します。
Gitのfilemode設定を無効化することで、権限変更を差分として認識させないよう制御できます。ローカルリポジトリ単位での設定例:
git config core.filemode false
すでにtypechange状態になってしまった場合、以下のワンライナーで一括復元が可能です。
git status | grep "typechange" | cut -d' ' -f2- | xargs -I{} git checkout -- {}
シンボリックリンクの変換処理
Unix系システムで活用されるシンボリックリンクは、Windows環境では単なるテキストファイルとして扱われ、IDEやビルドシステムから正しく認識されません。重要なディレクトリにリンクが設定されている場合、リポジトリが事実上利用不能になることもあります。
Gitは現在のところ、改行コードのようにシンボリックリンクをネイティブに変換する機能を提供していません。Windowsのmklinkコマンドを活用した手動変換が必要です。mklinkの主要なオプションは以下の通りです。
/D: シンボリックリンク(ソフトリンク)を作成/J: ディレクトリジャンクション(ディレクトリ向けハードリンク)を作成/H: ハードリンクを作成(ファイル専用)
変換プロセスは以下の三ステップで構成されます。
git ls-files -sでモード120000のエントリを特定- mklinkコマンドでWindows形式のリンクを再作成
git update-index --assume-unchangedでGitの追跡を除外
自動変換スクリプト実装例
上記手順を自動化するPythonスクリプトを示します。リポジトリルートで実行してください。
#!/usr/bin/env python3
import os
import subprocess
import sys
def extract_symlink_entries():
"""Gitインデックスからシンボリックリンクパスを抽出"""
try:
result = subprocess.run(
["git", "ls-files", "-s"],
capture_output=True,
text=True,
check=True
)
symlink_paths = []
for entry in result.stdout.splitlines():
fields = entry.split()
if len(fields) >= 4 and fields[0] == "120000":
symlink_paths.append(fields[3])
return symlink_paths
except subprocess.CalledProcessError:
print("Gitコマンドの実行に失敗しました", file=sys.stderr)
return []
def retrieve_link_target(link_file):
"""シンボリックリンクファイル内の対象パスを取得"""
try:
with open(link_file, 'r', encoding='utf-8') as handle:
return handle.read().strip()
except Exception as error:
print(f"リンク先読取エラー: {error}", file=sys.stderr)
return None
def generate_windows_link(link_location, target_location):
"""Windowsネイティブリンクを生成"""
if not os.path.isabs(target_location):
base_path = os.path.dirname(link_location)
target_location = os.path.join(base_path, target_location)
if os.path.exists(link_location):
os.unlink(link_location)
is_target_file = os.path.isfile(target_location)
try:
if is_target_file:
command = f'cmd /c mklink /H "{link_location}" "{target_location}"'
else:
command = f'cmd /c mklink /J "{link_location}" "{target_location}"'
subprocess.run(command, shell=True, check=True, capture_output=True)
return True
except subprocess.CalledProcessError as cmd_error:
print(f"リンク作成失敗: {cmd_error}", file=sys.stderr)
return False
def execute_conversion():
"""メイン変換処理を実行"""
repository_path = os.getcwd()
symlinks = extract_symlink_entries()
if not symlinks:
print("対象となるシンボリックリンクが存在しません")
sys.exit(0)
print(f"{len(symlinks)}件のシンボリックリンクを検出")
for symlink_path in symlinks:
absolute_link = os.path.join(repository_path, symlink_path)
if not os.path.exists(absolute_link):
continue
destination = retrieve_link_target(absolute_link)
if not destination:
continue
print(f"変換開始: {symlink_path} → {destination}")
if generate_windows_link(absolute_link, destination):
try:
subprocess.run(
["git", "update-index", "--assume-unchanged", symlink_path],
cwd=repository_path,
check=True,
capture_output=True
)
print(f" 完了: {symlink_path}")
except subprocess.CalledProcessError:
print(f" 警告: Gitインデックス更新失敗: {symlink_path}")
if __name__ == "__main__":
execute_conversion()