git diff と git archive を活用して変更ファイルのみをパッケージ化し、インクリメンタルデプロイを実現する方法

Git tutorial - IT technology blog
Git tutorial - IT technology blog

背景:フルデプロイが悪夢に変わる時

キャリアの初期、私は5GBを超える「レガシーな」PHPプロジェクトを管理していました。このソースコードには膨大な数の古い画像や不要なライブラリが含まれていましたが、誰も削除する勇気がありませんでした。FTPやRSYNCでコードを更新するたびに、待ち時間が長すぎてハラハラしたものです。

辛かったのは、auth.py のたった2行を修正しただけなのに、システムが比較とアップロードのために何万ものファイルをスキャンしなければならなかったことです。ある時、作業中にネットワークが不安定になり、サイトが15分間ダウンしてしまいました。その時、この問題を解決する鍵は Incremental Deployment(インクリメンタルデプロイ/増分デプロイ)であると確信しました。

現在はDockerやCI/CDが一般的ですが、ソースコード全体を転送するのは依然としてコストがかかる場合があります。特にビルドサーバーとデプロイサーバー間の帯域幅が数Mbpsに制限されている場合は顕著です。git diffgit archive を組み合わせる手法を使えば、変更されたものだけを含む非常に軽量な更新パッケージを作成できます。

必要なツール:Gitに標準搭載された2つの「助っ人」

複雑なソフトウェアを追加でインストールする代わりに、Gitの基本的な2つのコマンドを最大限に活用します。

1. git diff:更新ファイルのリストアップ

通常、git diff はコードの変更内容を確認するために使用しますが、パッケージ化のためにはファイル名を取得するだけで十分です。

# 現在のコミットと直前のコミットの間の変更ファイルをリストアップ
git diff --name-only HEAD~1 HEAD

実際には、v1.0.2 から v1.1.0 のように、2つのバージョン間を比較することが多いです。このコマンドはファイルパスのリストを返し、それが次のステップの「買い物リスト」のような役割を果たします。

2. git archive:プロフェッショナルなアーカイブ機能

git archive コマンドは、コミット内のファイルをzipやtar形式で圧縮するのに役立ちます。最大の利点は、ディレクトリ構造を維持しながら、Gitで管理されていない不要なファイルを完全に排除できることです。

# 現在のソースコード全体を update.zip としてアーカイブ
git archive -o update.zip HEAD

プロセスの自動化:2つのコマンドの組み合わせ

私たちの目標は、git diff が見つけたファイルだけを git archive にパッケージ化させることです。

効率化のためのワンライナーコマンド

Linux의サブシェル構文を使用して、これら2つのコマンドをネストできます。例えば、コミット abc1234 から最新版までのすべての変更を取得するには:

git archive -o changes.zip HEAD $(git diff --name-only abc1234 HEAD)

ただし、注意が必要です。リストの中に削除されたファイルが含まれている場合、git archive は現在のコミット内にそのファイルを見つけられず、エラーが発生します。

–diff-filter を使ったファイルのフィルタリング

私の経験上、常に --diff-filter フラグを追加することをお勧めします。関心の対象は、新規追加 (A)、コピー (C)、修正 (M)、または名前変更 (R) されたファイルだけです。削除されたファイル (D) は、サーバー上のファイルを削除する別のスクリプトで処理します。

# 実際に存在するファイルのみを抽出
git archive -o patch_v2.zip HEAD $(git diff --name-only --diff-filter=ACMR v1.0 HEAD)

この方法により、patch_v2.zip パッケージは常にクリーンな状態になり、古いコードに上書き解凍する準備が整います。

Bashスクリプトによる最適化

チームのメンバーが長いコマンドを覚える必要がないように、私はよくシンプルな bundle.sh ファイルを作成します:

#!/bin/bash

OLD_COMMIT=$1
NEW_COMMIT=${2:-HEAD}
OUTPUT="deploy_$(date +%Y%m%d_%H%M%S).zip"

if [ -z "$OLD_COMMIT" ]; then
    echo "エラー:古いコミットハッシュを指定してください!"
    exit 1
fi

FILES=$(git diff --name-only --diff-filter=ACMR $OLD_COMMIT $NEW_COMMIT)

if [ -z "$FILES" ]; then
    echo "変更は検出されませんでした。"
    exit 0
fi

git archive -o $OUTPUT $NEW_COMMIT $FILES
echo "パッケージを作成しました:$OUTPUT ($(echo "$FILES" | wc -l) ファイル)"

以前、このスクリプトを GitLab CI に適用して、Artifact を自動生成するようにしました。その結果、軽微なバグ修正のたびにサーバーに送信されるファイルサイズを 200MB から 1MB 未満に削減できました。

デプロイ前のクオリティチェック

急いでファイルをサーバーにアップロードしないでください。パッケージを再確認するために5秒ほど時間を割きましょう。

  • Zipの内容を確認: unzip -l changes.zip を実行してファイルリストを確認します。.env のような機密性の高い設定ファイルが含まれていないことを確認してください。
  • 不要なファイルの処理: Zipパッケージは上書きまたは新規追加のみに有効です。サーバー上の古いファイルを削除するには、削除されたファイルのリストをテキストファイルに出力します:git diff --name-only --diff-filter=D $OLD $NEW > deleted.txt
  • バージョンの記録: デプロイしたばかりのコミットハッシュを常にサーバー上の VERSION ファイルに書き込みます。次回、スクリプトはこのファイルを読み取って比較の起点を知ることができ、デプロイミスや漏れを防げます。

このテクニックは、低スペックのVPSや共有ホスティング環境で非常に役立ちます。私はこの小さな変更だけで、レガシープロジェクトのデプロイ時間を15分から30秒以内に短縮したことがあります。

Share: