Macでgitリポジトリを毎晩寝てる間に同期する方法


## 概要
基本的には[mac: launchd で anacron っぽいことをする](http://blog.zakkie.net/2010/01/mac-launchd-anacron.html)の内容に、具体的なgitあたりの処理を含めた内容。

出先でMac開いて作業するかーとgit fetch(またはgit pull)をかけたら数十MBのデータ転送が始まり、しかもgithubからの転送が遅くて悲しい思いをした経験、ありますよね。

この状態でも一定以上の作業はできるのが分散VCSの良いところではありますが、やっぱり最新コードが手元にないと色々厳しいものがあります。

どうせなら毎日寝てるうちに更新しておいてほしいなー、でもMacBook系だと夜中に電源入ってないこともあるよなーと思っていたところ、[OECさん](https://twitter.com/roishi2j2)に良い方法を教えていただいたのでメモ。OS Xに標準で含まれているlaunchdをcron風に利用して指定時刻実行をおこないます。

この場合、指定時刻に電源が入ってなかったら次に起動したタイミングで自動的にやってくれる模様(今回はユーザ単位のエージェント経由で実行するので、起動してても未ログイン状態では何もしてくれないかも)。

## 手順1: gitの更新スクリプトを用意する
まずは以下のようなファイルを作成します。今回はホームディレクトリの下にbinというディレクトリを掘って、その中に格納しました。

```~/bin/update-git-repos.sh
#!/bin/bash
REPO_DIR=~/Documents/workspace
REPOS=(mono PlaygroundOSS) # ここに定期更新したいリポジトリの一覧を書く
GIT_OPTS="fetch -a --prune origin"
RETRY_INT=60
LOG_FILE=~/.update_git.log

function fetch_git_reops {
    for r in "${REPOS[@]}"
    do
        echo "fetching... ${r}"
        cd $REPO_DIR/$r
        git $GIT_OPTS
    done
}

if [ ! -t 1 ]; then
    exec 1>> $LOG_FILE 2>&1
fi
echo "Starting git repos update at `date`"
# check network connectivity
i=0
while :
do
    i=$((i + 1))
    QRES=`dig +time=5 +tries=1 +short github.com` ; QRET=$?
    if [ $QRET -eq 0 -a -n "$QRES" ]; then
        fetch_git_reops
        exit 0
    fi
    if [ $i -eq 5 ]; then
        echo "No network access. Giving up."
        exit 9
    fi
    echo "No network access. Retrying in $RETRY_INT seconds."
    sleep $RETRY_INT
done
```

先に述べたように、指定時刻に実行されず後回しにされた場合は、コマンド実行時点でインターネットへ接続されていない可能性もあります。今回は、github.comの名前解決を出来るか否かでネット接続状態の判定をしてみました。

### 設定項目

 * ここでは、~/Documents/workspace以下にgitのローカルリポジトリが集約されているものとします。他の場所に置いている場合は、適宜`REPO_DIR`の値を変更してください
 * `REPOS`には、定期更新対象としたいリポジトリの相対パスをスペース区切りで記載してください

## 手順2: 動作確認
bashスクリプトの編集が完了したら
$ `chmod +x ~/bin/update-git-repos.sh`
として実行権限を付与し、
$ `~/bin/update-git-repos.sh`
と実行してみてきちんとリポジトリの更新がおこなわれていることを確認してください。

## 手順3: 寝てる間に実行してくれるようスケジュール設定

続いて、定期実行用のタスクを仕込みます。

```~/Library/LaunchAgents/jp.muo.gitupdater.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>jp.muo.gitupdater</string>
    <key>EnableGlobbing</key>
    <true/>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/muo/bin/update-git-repos.sh</string>
    </array>

    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>1</integer>
        <key>Minute</key>
        <integer>30</integer>
    </dict>
</dict>
</plist>
```

Yosemiteでは、`ProgramArguments`でも`~`を展開してくれなくなりました。
環境にあわせて自分のホームディレクトリ以下を指定してください。

ここでは毎日01:30になるべく処理を起動するようにしました。生活スタイルにあわせて適宜調整してください。

最後に

$ `launchctl load ~/Library/LaunchAgents/jp.muo.gitupdater.plist`

と実行するか、一旦ログアウトして再ログイン(またはマシンごと再起動)すると、タスクの仕込みが完了します。


pullするわけではないので、作業途中のブランチの中身を勝手に上書きすることもなく安心です。
逆に、fetchしかしないのでリモートで変更があっても自動では取り込まれないので注意してください。うっかりすると、ものすごく古い作業コピーのまま諸々変更をおこなって、PR出した時に悲しい思いをするかもしれません(あるいは、そういう状態で-fを強行してチームメンバーからボッコボコにされても知りません)。

## 注意事項
gitはfetch途中のネットワーク切断に対してあまり良いリカバリ方法を持っていないので、Macの電源を入れてリポジトリ更新開始→すぐに移動するため閉じる、などをするとリポジトリの更新ができないかもしれません(ただ更新できないだけで、データ破損などはまず無いと思いますが)。

## その他(手順には関係ないので、興味の無い方は読み飛ばしてください)

### ログ出力
StandardOutPathとStandardErrorPathを適切に設定しておくと、plistのXML内にログの出力先設定を集約できるのですが、この指定には$HOMEの展開が効かないため、今回は使っていません。代わりに、bashスクリプト内で`~/.update_git.log`ファイルへ書き出すようにしています。
参考: [plistのリファレンス](https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html)

### 電源断状態での更新
Power Napをうまくサポートすると、電源を切っていても処理できるので更に嬉しさありそうですが、このへんの方法はまだ調べていないので分かりません。そもそもAppleはサードパーティにPower Nap利用を開放していない気がします(安易に開放するとFileVaultに穴を開けることにもなりかねないし、仕方ないかも)。

### 指定ディレクトリ以下の全リポジトリ更新
~/Documents/workspace/ 以下の全gitリポジトリを同期、というのも良いような気がしましたが、さすがに普段あまり(例えば半年レベルで)触らないようなリポジトリを更新してもしょうがないかなーという気がしたので、ホワイトリスト形式にしました。