【git-hooks】pushされたファイルをFTPでレンタルサーバーにアップロードする

ある人曰く、

> ある製品のホームページがあるんだけどさ、これまでバージョン管理すらしてなかったから、まずいと思ってとりあえずhtmlファイルを全部Gitリポジトリにぶち込んだんだよ。で、リモートリポジトリにpushするのと、FTPでレンタルサーバーにアップロードするので二度手間だから、pushしたら自動でアップロードされるようにしたいんだよね。

……うん。で、詳しく聞くと、要するに

* 社内のファイルサーバーに、Webサイトのリモートリポジトリを置いた。
* リモートリポジトリにpushしたら、更新されたファイルを自動でFTPでレンタルサーバーにアップロードしたい。
* 各編集者は各自の作業ブランチで編集する。編集が終わったら管理者が変更内容をレビューし、OKなら管理者がmasterブランチにマージする。（プルリクエストなんてものは無い）
* だから、
  * 管理者以外はmasterにpushできないようにして、
  * push先がmasterだったらアップロードすればいいと思う。

……ということだったので、書きました。

レンタルサーバーにSSHでログインできるなら、FTPでアップロードする処理は不要です。公開用ディレクトリにpullしさえすればいいです。たとえば、以下のページをご覧下さい。

* [Gitのフック（hook）を使って自動的にデプロイする](https://rfs.jp/server/git/gite-lab/git-hook-post-receive.html)
* [gitでシンプルなデプロイ環境を作る](https://qiita.com/zaburo/items/8886be1a733aaf581045)

また、レンタルサーバーがGitHubなどのホスティングサービスと連携できるならば、そちらの機能を使ったほうが便利で安全だと思います。

本稿の内容によって生じた問題に関して、筆者は責任を負いかねます。


## 概要

Gitには、フックという、特定の操作が発生したタイミングで、その操作に割り込んでスクリプトを実行する仕組みがあります。フックのスクリプトは、`.git/hooks/`または`hooks`に実行タイミングに応じた名前のファイルで置かれます。

今回使用するフックは、`pre-push`と`post-receive`です。前者は、クライアント側のフックで、`git push`でpushが行われる前に実行されます。後者はサーバー側のフックで、push処理が正常終了した後に実行されます。`pre-push`フックで、push先のブランチがmasterであればpushを中止します。`post-receive`フックで、push完了後に更新されたファイルをレンタルサーバーにアップロードします。


## 編集者がmasterへpushするのを禁止する

まず編集者がmasterに直接pushできないようにします。
下記のシェルスクリプトを各編集者ローカルリポジトリの`./git/hooks/`下に`pre-push`という名前で配置してもらいます。

```bash:.git/hooks/pre-push
#!/bin/bash

while read local_ref local_sha1 remote_ref remote_sha1
do
  if [[ "${remote_ref##refs/heads/}" = "master" ]]; then
    echo 'You cannot push to the master branch!'
    exit 1
  fi
done

```

ファイルを作ったら、スクリプトに実行権限を付与します。

```shell-session
$ cd {ローカルリポジトリのルート}
$ chmod +x ./git/hooks/pre-push
```


## push時にFTPでアップロードする

リモートリポジトリはファイルの実態を持たないので、リモートリポジトリがあるファイルサーバーに、デプロイ用のローカルリポジトリを作って一旦そこにpullするようにしました。

つまり、以下のような流れになります。

1. 管理者が変更をpushする。（push先がmasterブランチでなければ何もしない）
1. デプロイ用のディレクトリに、その変更がpullされる。
1. 直前のpushで更新されたファイル名一覧を調べ、
  1. デプロイ用ディレクトリに存在していれば、更新されたとみなしてアップロードする
  1. 存在しなければ、削除されたとみなしてサーバーから削除する


### デプロイ用のローカルリポジトリの準備

リモートリポジトリのあるサーバーに、デプロイ用のローカルリポジトリを作ります。

```shell-session
$ git clone {リモートリポジトリのパス} 
```

### git-ftpのセットアップ

レンタルサーバーへのアップロードには、[git-ftp](https://github.com/git-ftp/git-ftp)を用います。[インストール手順](https://github.com/git-ftp/git-ftp/blob/master/INSTALL.md)に従って、インストールして下さい。

続いて、デプロイ用のリポジトリで、git-ftpの初期設定をします。以下のコマンドで、レンタルサーバーに`.git-ftp.log`というファイルが作られます。

```shell-session
$ cd {デプロイ用リポジトリのルート}
$ git config git-ftp.url "ftp://{アップロード先のホスト}"
$ git config git-ftp.user "{ユーザー名}"
$ git config git-ftp.password "{パスワード}"
$ git ftp init
```

### post-receiveフックの配置

以下のシェルスクリプトを、リモートリポジトリの`hooks/`下に`post-receive`という名前で配置します。

```bash:hooks/post-receive
#!/bin/bash

# Master branch name
MASTER='master'

# Path of the root directory of the repository to deploy
DEPLOY_DIR_ROOT="${HOME}/deploy" # デプロイ用のディレクトリのパスに変える

while read prev_sha curr_sha ref
do
  branch=${ref##refs/heads/}
  if [[ $branch != $MASTER ]]; then
    exit 0
  fi
done

echo 'run post-receive...'

cd $DEPLOY_DIR_ROOT
git --git-dir=.git pull # --git-dir option must be specified
git --git-dir=.git ftp push

echo 'post-receive done.'
```

たとえば、上記のシェルスクリプトがローカルのホームディレクトリにあるなら、以下のようにすればコピーできます。

```shell-session
$ scp ~/post-receive {ユーザー名}@{共有サーバーのアドレス}:{リモートリポジトリのパス}/hooks/post-receive
$ rm ~/post-receive
```

続いて、リモートリポジトリに配置した`post-receive`に実行権限を付与します。

```shell-session
$ ssh {ユーザー名}@{共有サーバーのアドレス}
$ cd {リモートリポジトリのパス}
$ chmod +x ./hooks/post-receive
```


以上です。
ぶっちゃけ、まずい点たくさんあると思うので、ご指摘下さい。