SVNを廃止してGitを導入した話（開発フロー/環境の改善）

※ 1年ほど前に対応した内容を思い出しながら書いています

# 背景

- サービス開始から6年ほど稼働し続けている保守プロジェクトで、プログラムソースの管理を「Subversion」(以下SVN)で行っている案件がありました

- 3年目くらいから保守対応に携わっているが、複数人の開発メンバーに対して「開発環境が1つ」しかなく、SVNによる一極集中型のソース管理だったため、複数メンバーによる並行開発に不都合があることが度々ありました

- そこで、VCSをSVNからGitに移行＋Dockerを利用して個別のローカル開発環境を構築することにしました

# 狙い/目的

1. **「検証環境/本番環境(VPS上)」のプログラムソースの差分を解消する**  
→ どちらかの環境にしかソースがアップされていない or 差分の発生による、デグレやバグの予防
2. **複数人で並行開発しやすい環境を作る**  
→ 同じソースを同タイミングで複数人が改修しづらい問題の解消(SVNだとロックの取り合いになる)
3. **リリース対象の手動アップロードを止める**  
→ 人の手と労力に頼らずに、システム＋ツールに頼って可能な限り自動化することでミスや事故を減らす
4. **コードレビューしやすい環境＋文化を作る**  
→ プログラム修正差分をRedmineのチケットに添付する運用が常習化していたため、コードレビューしづらい状況を改善する

# やったこと

### 1. SVN → Gitリポジトリの移行

- SVNから「過去のcommitログ」を引き継いだ状態でGitに移行するために、[git-svn](https://git-scm.com/docs/git-svn)を利用してSVNリポジトリをGitリポジトリに変換する

```bash
git svn clone -s --prefix=svn/ svn+ssh://svnserve@hogehoge.com/hoge
```

### 2. Gitリポジトリ/ブランチの設定

※ 自社で管理しているGitLab(CE)のリポジトリで管理する前提

- ローカル上から以下のコマンドを実行

```shell
git remote add origin {GitLab上に作成したリポジトリ}
git add .
git commit -m "first commit"
git push origin/master
```

- Gitlab管理画面で以下の設定を行う
   1. `master`から`develop`ブランチを作成
   2. `develop`ブランチを「defaultブランチ」に設定
   3. `master`と`develop`ブランチを「protected」に設定（直push禁止にする）

### 3. ローカル開発環境の用意

1. 既存の本番環境と同じ構成になるようにDockerファイルを用意（CentOS6.8/PHP5.4/その他ライブラリ等)
2. 作成したDockerファイルは、新規作成したGitリポジトリで管理できるようにaddする
3. Docker環境のセットアップに関する手順書、Gitブランチの運用方針、検証/本番環境へのデプロイ方法をプロジェクトのメンバーに共有

### 4. 検証環境/本番環境(VPS)のデプロイ設定

- 各環境のWebサーバのドキュメントルートで、Gitリポジトリの設定

```shell
cd {Webサーバのドキュメントルート}
git init
git remote add origin https://{デプロイ用GitLabのユーザ名}:{GitLabユーザのPW}@{リポジトリのURL}
git pull
git checkout master # デプロイに利用するブランチ
```

- GitLabの指定ブランチを監視し、変更を検知して自動でcommit内容をpullしてくるシェルを作成  
(複数台のサーバにrsyncする設定)

※ 本当はCIを使ってデプロイさせたかったが、当時は知見がなかった＋大人の事情(?)があって難しかったので、シェルをデーモン化して常時起動＋自動デプロイを実現した
**→ 何らかのCIが使える状況であれば、普通にCIでデプロイしましょう**

```shell:deploy_maser.sh
#!/bin/sh
function sync_src {
SVRLIST="{rsyncで同期先となるサーバを指定} {rsyncで同期先となるサーバを指定} {rsyncで同期先となるサーバを指定}";
      
  for syncsvr in $SVRLIST ; do
    echo "---------------- ${syncsvr} ---------------" >> /tmp/git_src_update.log
          rsync -vaW -e ssh --delete-after \
              --exclude "data/*" \
              {git initコマンドを実行したディレクトリを指定} {ssh認証ユーザ名}@${syncsvr}:{rsyncで同期する先のディレクトリを指定}  >> /tmp/git_src_update.log
        done
      
      }
      
      function run {
      # how we want to extract the variables from the commit message.
      format_name="--format=%cn"
      format_when="--format=%cr"
      format_summary="--format=%s"
      format_body="--format=%b"
      
      # what repository do we want to watch (default to origin/master)
      if [ -z "$1" ]; then
              repository="origin/master" # ここは環境によって書き換えるべし
      else
              repository="$1"
      fi
      latest_revision="none"
      
      # loop forever, need to kill the process.
      while [ 1 ]; do
          # get the latest revision SHA.
          git remote update > /dev/null 2>&1
          current_revision=`git rev-parse $repository`
      
          # if we haven't seen that one yet, then we know there's new stuff.
          if [ $latest_revision != $current_revision ]; then
      
              # mark the newest revision as seen.
              latest_revision=$current_revision
      
              # extract the details from the log.
              commit_name=`git log -1 $format_name $latest_revision`
              commit_when=`git log -1 $format_when $latest_revision`
              commit_summary=`git log -1 $format_summary $latest_revision`
              commit_body=`git log -1 $format_body $latest_revision`
      
              # notify the user of the commit.
              summary="$commit_name committed to $repository $commit_when!"
              body="$commit_summary\n\n$commit_body"
      
              echo "#-------------------------------------#" >> /tmp/git_commit_log
              date >> /tmp/git_commit_log
              echo -e "Summary:$summary" >> /tmp/git_commit_log
              echo -e "Body:$body" >> /tmp/git_commit_log
              git pull >> /tmp/git_commit_log
              sync_src
          fi
          sleep 5
      done
}
      
cd {git initコマンドを実行したディレクトリを指定}
if git rev-parse --git-dir > /dev/null 2>&1; then
        (run $1)
else
        echo "Error: not a git repository"
fi      
```

- nohupコマンドでデーモン化

```bash
nohup sh deploy_master &
```

これで`master`ブランチにpushが走ると本番環境へ自動デプロイされる

# 感想

- 複数人かつ同タイミングで同じソースを修正する際、SVNだと「ロックの取り合い」による管理の煩わしさや生産性の低下が気になっていたが、Gitに移行したことでその束縛＋ストレスから解き放たれた開発が可能となりました

- 今回は比較的小規模なプロジェクト（開発メンバー3～4人ほど）だったので、並行する保守開発の作業にあまり影響が出ないような計らいが出来ました

- Gitの操作やDocker環境に慣れていないメンバーへの教育が課題だが、手順書やRedmineの社内Wikiを充実させたり、質問や相談の窓口を用意して、適切にアドバイスするか一緒に悩むことでカバーします