gitbucket-ci-plugin に機能追加してみた

この記事は[Ponos Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ponos) 8日目の記事です。

*** 

## はじめに
[Scala](https://www.scala-lang.org/)で記載されたオープンソースの[GitHub](https://github.com) Cloneとして[GitBucket](https://github.com/gitbucket/gitbucket)というものがあります。
このGitBucketですが[いくつかのプラグイン](https://gitbucket-plugins.github.io/)が公開されています。

今回はこの中の[CI](https://ja.wikipedia.org/wiki/%E7%B6%99%E7%B6%9A%E7%9A%84%E3%82%A4%E3%83%B3%E3%83%86%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)に関連するpluginの[git-bucket-ci plugin](https://github.com/takezoe/gitbucket-ci-plugin)に機能追加した話をしようかと思います。

（ちなみにGitBucketは[Apacheライセンス](https://ja.wikipedia.org/wiki/Apache_License)です。）

## この記事の対象者
* GitBucketのソースコードを読んでみたい人
* [プルリクエスト](https://en.wikipedia.org/wiki/Distributed_version_control#Pull_requests)するまでに何を考えて作業しているのかを知りたい人
    * （免責）私のやり方のためこの記事の方法は一般的ではないかもしれません

## 追加した機能
1. プルリクエストをマージした直後にCIを走らせる機能
2. 上記機能をプラグインの設定画面で有効化/無効化する機能
    1. 今はない機能を追加実装するためデフォルトの挙動は無効化にする

## 事前に試したこと
1. GitBucketにgitbucket-ci-pluginを設定して利用した
    1. [プラグインのインストール方法（プラグインのホイットデプロイに対応 の部分）](https://takezoe.hatenablog.com/entry/2017/08/05/123017)
    2. gitbucket-ci-pluginのjarファイルは[こちら](https://github.com/takezoe/gitbucket-ci-plugin/releases)
2. プルリクエストを作成してマージするときにCIが走らないことを確認した
3. GitBucketやgitbucket-ci-pluginのREADMEを読み上記の機能が実装されてなさそうなことを確認した

## 機能の確認と追加
### 1. プルリクエストをマージした直後にCIを走らせる機能の追加
#### 1-1. 本当に機能が実装されていないかどうかを確認する
##### 1-1-1. [GitBucket](https://github.com/gitbucket/gitbucket)のディレクトリ構造を確認する
![GitBucket.top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543379/20a03b55-f473-bf84-d9c2-e2c6ff0b32f7.png)

1. ビルドツールを確認する
    1. build.sbtがあるのでビルドツールは[sbt](https://www.scala-sbt.org/)を使っている
2. （ビルドツールを確認しなくても大抵は）**src/main** の下にコードが入っている
3. **src/main/scala/gitbucket/core** の下に主なコードが入っている

##### 1-1-2. [gitbucket-ci-plugin](https://github.com/takezoe/gitbucket-ci-plugin)のディレクトリ構造を確認する
![gitbucket-ci-plugin.top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543379/01f6b044-9f3b-364b-eed5-d6cc7a3993b3.png)

1. ビルドツールを確認する（こっちもbuild.sbtがあるのでsbt）
2. **src/main** の下にコードが入っている
3. **src/main/scala/io/github/gitbucket/ci** の下に主なコードが入っている

##### 1-1-3. gitbucket-ci-pluginのプルリクエスト時に実行されそうなコードを探す
![gitbucket-ci-plugin.ci.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543379/b9eee2fe-f563-e25e-9479-c84e8c635b05.png)

1. CIの拡張機能は[Hook](https://ja.wikipedia.org/wiki/%E3%83%95%E3%83%83%E3%82%AF_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0))で動作する筈なので***hook***に処理が記載されていそう。次点で処理が記載されていそうなのは[サービス層](https://en.wikipedia.org/wiki/Service_(systems_architecture))なので**service**も確認候補に入れる
2. ***hook***の中に[***CIPullRequestHook.scala***](https://github.com/takezoe/gitbucket-ci-plugin/blob/master/src/main/scala/io/github/gitbucket/ci/hook/CIPullRequestHook.scala)というプルリクエストと関係ありそうなファイルがあるので確認する
    1. ```override def created``` ```override def addedComment```と[オーバーライド](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89)されている処理があるのでこれがプルリクエスト時に動いている機能だと推測する
    2. この関数を定義しているクラスはGitBucketの***gitbucket.core.plugin.PullRequestHook***を継承しているのでこちらにフックを受け付ける処理が入っている筈

##### 1-1-4. GitBucketにプルリクエストのマージ後に実行される（かつ、上書きできる）処理があるかを調べる
1. ***gitbucket.core.plugin.PullRequestHook***の中を確認する
2. GitHubの検索窓で検索すると***src/main/scala/gitbucket/core/plugin/IssueHook.scala***で定義されていることが分かる
3. ***IssueHook.scala（下のコード）***は以下の機能拡張を提供しているので機能追加はできそう
    1. ```created```
    2. ```addedComment```
    3. ```closed```
    4. ```reopened```
    5. ```assigned```
    6. ```closedByCommitComment```
    7. ```merged```
4. GitHubで```merged```を検索したところ、この関数のオーバーライドで目的が達成できそう
    1. ```merged```は***src/main/scala/io/github/gitbucket/ci/hook/CIPullRequestHook.scala***で利用されている
    2. ```mergePullRequest```関数の終わり（プルリクエストのマージ処理の直後）に実行されている
5. gitbucket-ci-pluginで```merged```はオーバーライドされておらず、GitBucketで```merged```のフックが提供されているのでこの機能は実装されていないと判断する

``` IssueHook.scala
package gitbucket.core.plugin

import gitbucket.core.controller.Context
import gitbucket.core.model.{Account, Issue}
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.model.Profile._
import profile.api._

trait IssueHook {

  def created(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
  def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(
    implicit session: Session,
    context: Context
  ): Unit = ()
  def closed(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
  def reopened(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
  def assigned(
    issue: Issue,
    repository: RepositoryInfo,
    assigner: Option[String],
    assigned: Option[String],
    oldAssigned: Option[String]
  )(
    implicit session: Session,
    context: Context
  ): Unit = ()
  def closedByCommitComment(issue: Issue, repository: RepositoryInfo, message: String, pusher: Account)(
    implicit session: Session
  ): Unit = ()

}

trait PullRequestHook extends IssueHook {

  def merged(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()

}
```

#### 1-2. 機能を追加する
##### 1-2-1. ***src/main/scala/io/github/gitbucket/ci/hook/CIPullRequestHook.scala***に```override def merged```を実装する
1. [コードを修正](https://github.com/uneme/gitbucket-ci-plugin/blob/7de0f62b26a27ea1c33e207aa7e8de37a4d04532/src/main/scala/io/github/gitbucket/ci/hook/CIPullRequestHook.scala)する
    1. ```merged```はプルリクエストの後で動く。マージ後のコードに対してCIしたいので***base repository***ではなく***head repository***のコードでCIが走るようにする
    2. ```created```や```addedComment```はプルリクエストのコード修正の確認をすることが目的なので***base repository***に対してCIが走るようになっている

**<font color="red">GitBucketのプラグインを置き換えたら動いた!!</font>**

### 2. 上記機能をプラグインの設定画面で有効化/無効化する機能の追加
#### 2-1. GitBucketとgitbucket-ci-pluginの構成を把握する
##### 2-1-1. GitBucketの構成を把握する
1. ***build.sbt***の中の```libraryDependencies```を確認し、どんなライブラリが利用されているのか調べる
    1. [scalatra](http://scalatra.org): Web application framework
    2. [solidbase](https://github.com/gitbucket/solidbase): GitBucketと同じ作者が作成した[DBマイグレーションツール](https://qiita.com/cocoa-maemae/items/d55c7b53f95425efdce8)
2. ***project/plugins.sbt***の中も確認する
    1. [twirl](https://github.com/playframework/twirl): [Playフレームワーク](http://www.playframework.com/)の[テンプレートエンジン](https://ja.wikipedia.org/wiki/テンプレートエンジン)
        2. scalatraのテンプレートエンジンは[scalate](http://scalatra.org/guides/2.6/views/scalate.html)が標準だと思っていたが[そうでもない](http://scalatra.org/guides/2.6/)らしい

##### 2-1-1. gitbucket-ci-pluginの構成を把握する
1. ***build.sbt***の中の```libraryDependencies```を確認し、どんなライブラリが利用されているのか調べる
    1. 特筆すべき項目は特になかった
2. ***project/plugins.sbt***の中も確認する
    1. 特筆すべき項目は特になかった

#### 2-2. 調査結果から変更が必要そうなものを考える
1. gitbucket-ci-pluginの設定画面のGUI（twirlのテンプレートの修正）
2. gitbucket-ci-pluginの設定画面で保存する設定項目（gitbucket-ci-pluginのコードを読んで確認する）
3. DBマイグレーションツールの設定（上記2.はDBに保存されている筈なので）
4. gitbucket-ci-pluginのユニットテスト（必要に応じて）

#### 2-3. [コードを修正](https://github.com/takezoe/gitbucket-ci-plugin/compare/master...uneme:master)する

##### 2-3-1. 方針を決める
1. gitbucket-ci-pluginの管理画面に***Run a build script after the merge***という項目を新たに作り、この項目のオン/オフで挙動を制御する

##### 2-3-2. コードを修正する
1. ***src/main/twirl/gitbucket/ci/config.scala.html***にチェックボックスを追加する
2. ***src/main/scala/io/github/gitbucket/ci/controller/CIController.scala***に上記画面から値を受け取るための処理を定義する
    1. このファイルの```saveCIConfig```の部分でDBに設定を永続化しているのが分かるのでCIConfigに関連する部分も変更する
3. ***src/main/scala/io/github/gitbucket/ci/model/CIConfig.scala***に```runAfterMerge: Boolean```を追加する
4. ```merged```の処理を```runAfterMerge```の項目を評価するように変更する
    1. 変更するファイルは***src/main/scala/io/github/gitbucket/ci/hook/CIPullRequestHook.scala***
5. DBの変更を自動反映するためにDBマイグレーションに関連する部分を変更する
    1. ***src/main/resources/update/gitbucket-ci_1.9.1.xml***
    2. ***src/main/scala/Plugin.scala***
6. DBスキーマの変更が入るため（勝手に）バージョン番号をあげたのでgitbucket-ci-pluginの```build.sbt```も修正する
7. ユニットテストを修正する（本当は最初にユニットテストを修正してから本番コードを書くのが良い筈）
    1. ```created```や```addedComment```に対するユニットテストが特になかったので実施しなかった
    2. また、どのようなユニットテストを行うのかについてもこの段階ではあまり考えていなかった

##### 2-3-3. 動作確認する

1. ***Run a build script after the merge***オフの状態
    1. プルリクエストの作成とマージ後の挙動の確認
2. ***Run a build script after the merge***オンの状態
    2. プルリクエストの作成とマージ後の挙動の確認

**<font color="red">想定通りに動いた!!</font>**

##### 2-3-4. 最後に本体にプルリクエストする

https://github.com/takezoe/gitbucket-ci-plugin/pull/85
（プルリクエストは実施前に```git rebase -i HEAD~xx```（**xx**には数字が入る）等して自分の変更を[fixup](https://fle.github.io/git-tip-keep-your-branch-clean-with-fixup-and-autosquash.html)してから行うのが良い。
私自身のプルリクエストが中途半端なfixupの状態になっているのであまり大きな声では言えないが。。。）