Obsidian Gitプラグインで学ぶ、Gitのロック機構の仕組み

※ この記事はAIによって執筆されています。

はじめに

Obsidianで自動バックアップのためにGitプラグインを使っていたところ、ターミナルからgit addを実行しようとしたら、以下のようなエラーが出て困ったことはありませんか?

fatal: Unable to create '.git/index.lock': File exists.

Another git process seems to be running in this repository.

この記事では、このエラーの背景にあるGitのロック機構について、技術的に深掘りして解説します。

目次

Gitのロック機構とは

Gitは、複数のプロセスが同時にリポジトリを変更することを防ぐため、ロックファイルという仕組みを使っています。

主要なロックファイル

Gitリポジトリには、以下のようなロックファイルが使われます:

  • .git/index.lock - git addgit commit実行時に作成
  • .git/refs/heads/<branch>.lock - ブランチ更新時に作成
  • .git/packed-refs.lock - 参照の最適化時に作成

これらのファイルは、Git操作中に一時的に作成され、操作完了後に自動的に削除されます。

ロックの動作フロー

Gitがどのようにロックファイルを使っているか、git addを例に見てみましょう。

  1. Gitコマンド実行開始
    • .git/index.lockファイルを作成(排他制御開始)
    • ファイルが既に存在する場合は即座にエラー
  2. 操作の実行
    • .git/index.lockに新しいインデックス情報を書き込み
  3. 操作完了
    • .git/index.lock.git/indexにアトミックにリネーム
    • ロックファイルが消える

重要なのは、Gitは待機せずに即座に失敗するという点です。これを「フェイル・ファスト」設計と呼びます。

なぜロックが必要なのか

データ破損の防止

もしロック機構がなかったら、以下のような問題が発生します:

# もしロックがなかったら...

# プロセスA: git add file1.txt
# - .git/indexファイルを読み込み
# - ファイル情報を追加
# - .git/indexに書き込み中...

# プロセスB: git add file2.txt (同時実行)
# - .git/indexファイルを読み込み (古いデータ)
# - ファイル情報を追加
# - .git/indexに書き込み
# → プロセスAの変更が消える!

このような競合状態(Race Condition)を防ぐために、ロック機構が必須なのです。

アトミック操作の保証

Gitは、ロックファイルを使ってアトミック(原子的)な書き込みを実現しています:

# Gitの安全な書き込み手順
1. .git/index.lock を作成 (排他制御開始)
2. .git/index.lock に新しいデータを書き込み
3. rename(.git/index.lock, .git/index)  # アトミック操作

ファイルのリネームは、ほとんどのファイルシステムで1命令で完了する操作です。そのため、以下のような中途半端な状態にはなりません:

  • 成功 ✓
  • 失敗 ✓
  • 中途半端 ✗(発生しない)

Obsidian Gitプラグインとの競合

プラグインの設定を確認

.obsidian/plugins/obsidian-git/data.jsonを見ると、以下のような設定があります:

{
  "commitMessage": "vault backup: {{date}}",
  "autoSaveInterval": 1,
  "autoPushInterval": 1,
  "autoPullInterval": 1,
  "autoPullOnBoot": true
}

この設定では、1分ごとに自動的にgit addgit commitgit pushが実行されます。

競合が発生する仕組み

Obsidian Gitプラグイン
  ├→ 1分ごとに自動実行 (autoSaveInterval: 1)
  │   └→ git add → git commit → git push
  │
  └→ .git/index.lock を保持中(数秒間)
       ↓
     ターミナルで git add 実行
       ↓
     エラー: "Unable to create '.git/index.lock'"

プラグインがロックを保持している間は、ターミナルからのGit操作が即座に失敗します。

技術的な実装の詳細

POSIX環境でのロック実装

Gitの内部では、以下のようなシステムコールを使ってロックを実現しています:

// Gitの内部実装(簡略版)
int lock_file(const char *path) {
    // O_CREAT | O_EXCL: ファイルが存在しなければ作成、
    // 既に存在すればエラー(アトミック操作)
    int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0666);

    if (fd < 0) {
        if (errno == EEXIST) {
            // ロックファイルが既に存在
            return -1;  // ロック取得失敗
        }
    }
    return fd;  // ロック取得成功
}

O_CREAT | O_EXCLフラグの組み合わせにより、ファイルシステムレベルでアトミックなロック取得が可能になります。

なぜスピンロックではないのか

多くの並行処理システムでは、ロックが取れるまで待機する「スピンロック」を使いますが、Gitは即座に失敗します。その理由は:

  • Git操作は通常数秒で完了する
  • 長時間のロックは異常な状態(クラッシュなど)を示す
  • ユーザーに状況を知らせるべき

この設計により、デッドロックやハングアップを防いでいます。

認証処理の仕組み

Obsidian Gitプラグインには、認証情報を安全に扱うための工夫があります。

.obsidian/plugins/obsidian-git/obsidian_askpass.shというスクリプトを見ると:

#!/bin/sh

PROMPT="$1"
TEMP_FILE="$OBSIDIAN_GIT_CREDENTIALS_INPUT"

cleanup() {
    rm -f "$TEMP_FILE" "$TEMP_FILE.response"
}
trap cleanup EXIT

echo "$PROMPT" > "$TEMP_FILE"

while [ ! -e "$TEMP_FILE.response" ]; do
    if [ ! -e "$TEMP_FILE" ]; then
        echo "Trigger file got removed: Abort" >&2
        exit 1
    fi
    sleep 0.1
done

RESPONSE=$(cat "$TEMP_FILE.response")
echo "$RESPONSE"

この仕組みは以下のように動作します:

  1. Gitが認証情報を要求
  2. スクリプトが一時ファイルにプロンプトを書き込み
  3. Obsidianプラグインがそれを検出
  4. ユーザーにパスワード入力を促す
  5. 入力された認証情報を.responseファイルに書き込み
  6. スクリプトがそれを読み取ってGitに渡す

これにより、コマンドラインでパスワードを直接扱わずに済みます。

競合を回避する方法

方法1: プラグインを一時停止

.obsidian/plugins/obsidian-git/data.jsonを編集:

{
  "autoSaveInterval": 0,
  "autoPushInterval": 0,
  "autoPullInterval": 0
}

すべてを0にすることで、自動実行を完全に無効化できます。

方法2: タイミングをずらす

自動実行の間隔を長くする:

{
  "autoSaveInterval": 10,  // 10分に1回
  "autoPushInterval": 10
}

これにより、ターミナル操作の余地が増えます。

方法3: リトライロジックを実装

ターミナルでスクリプトを使う:

#!/bin/bash
# 最大10回リトライ
for i in {1..10}; do
  if git add .; then
    echo "Success!"
    break
  else
    echo "Locked, retrying in 2 seconds... ($i/10)"
    sleep 2
  fi
done

このスクリプトは、ロックされている場合に自動的にリトライします。

トラブルシューティング

ロックファイルが残ってしまった場合

プラグインやGitがクラッシュすると、ロックファイルが残ることがあります:

# 1. Gitプロセスが実行中でないことを確認
ps aux | grep git

# 2. ロックファイルを手動で削除
rm -f .git/index.lock

# 3. 再度Git操作を試す
git add .

⚠️ 警告: Gitプロセスが実行中の場合は削除しないでください。データ破損の原因になります。

まとめ

この記事では、以下について学びました:

  • Gitのロックファイルは、データ破損を防ぐための重要な仕組み
  • ロックファイルにより、アトミックな操作が保証される
  • Gitは「フェイル・ファスト」設計で、即座にエラーを返す
  • Obsidian Gitプラグインとの競合は、自動実行の設定で回避可能
  • ファイルシステムレベルのO_CREAT | O_EXCLフラグでロックを実現

Obsidian Gitプラグインのような自動化ツールは便利ですが、その背後にある技術を理解することで、より安全に使いこなせるようになります。

参考資料


この記事が役に立ったら、ぜひブックマークやシェアをお願いします!