こんにちは。先週アドベントカレンダーを待てずにブログを出してしまったせいで、若干ネタ切れの(ほ)です。
※この記事は 『CRESCO Advent Calendar 2017』 6日目の記事です。
最近は企業でもソースコード管理にGitを使用していることが多いと思いますが、ホスティングには何を使用していますでしょうか。
GitHub Enterpriseを使用している企業もあると思いますが、ソースコードは社外に預けたくないという企業もまだ多いと思います。
そんなときに候補になるのが各種Githubクローンだと思いますが、弊社のあるプロジェクトではGitBucketを使用しています。
そのプロジェクトの人から
「masterブランチに管理者以外がPushできないようにしたい」
という話を聞いたのでプラグインを作ってみたのですが、GitのコマンドをHookするプラグインの作成方法が記載されているサイトを見つけられなかったので、ここに書いておきます。
(ちなみに、GitHubでもProtected branchesはGitBucketと同様にForce PushやDeleteを防ぐだけで、通常のPushは防げません。)
通常のGitBucketのプラグインの作り方
“通常の”と書きましたが、新しいWeb APIのエンドポイントを作成する方法です。
基本的に下記のGitbucket作者様のサイトどおりに進めれば作成できます。
ただし、GitBucket 4.15 からは、ブログの “/helloworld” からサンプルコードの11行目と同じように “/*” にしないと “Not Found” と表示されるようになります。
(私が作ったのはGitHubのコードが修正される前というタイミングの悪さで、若干はまってしまいました。)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import io.github.gitbucket.helloworld.controller.HelloWorldController import io.github.gitbucket.solidbase.model.Version class Plugin extends gitbucket.core.plugin.Plugin { override val pluginId: String = "helloworld" override val pluginName: String = "HelloWorld Plugin" override val description: String = "First example of GitBucket plug-in" override val versions: List[Version] = List(new Version("1.0.0")) override val controllers = Seq( "/*" -> new HelloWorldController() ) } |
さらに複雑なことをやりたい場合は、下記のGitBucket Community Pluginsのサイトのソースも参考になると思います。
https://gitbucket-plugins.github.io/
GitコマンドをHookするプラグインの作り方
次に本題のGitコマンドをHookするための方法です。
0. 準備
通常のプラグインの作成方法で紹介したGitbucket作者様のサイトの “プロジェクト作成”, “プラグインの定義”をやっておきます。
1. Hook処理の作成
まず、下記の ReceiveHook
クラス(trait)を extend したクラスを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package gitbucket.core.plugin import gitbucket.core.model.Profile._ import org.eclipse.jgit.transport.{ReceivePack, ReceiveCommand} import profile.api._ trait ReceiveHook { def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String) (implicit session: Session): Option[String] = None def postReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String) (implicit session: Session): Unit = () } |
preReceive
, postReceive
の引数はそれぞれ下記です。
- owner : リポジトリにOwner名
- repository : 対象のリポジトリ
- receivePack : 各種接続情報など
Ref : http://download.eclipse.org/jgit/site/4.2.0-SNAPSHOT/apidocs/org/eclipse/jgit/transport/ReceivePack.html - command : 実行されたGitコマンドの内容
Ref : http://download.eclipse.org/jgit/site/4.2.0-SNAPSHOT/apidocs/org/eclipse/jgit/transport/ReceiveCommand.html - pusher : Gitコマンド実行者
preReceive
, postReceive
の違いは下記が参考になると思います。
8.3 Git のカスタマイズ – Git フック
実装例
GitBucketのProtected Branchのコードが参考になります。
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
object ProtectedBranchService { class ProtectedBranchReceiveHook extends ReceiveHook with ProtectedBranchService with RepositoryService with AccountService { override def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String) (implicit session: Session): Option[String] = { val branch = command.getRefName.stripPrefix("refs/heads/") if(branch != command.getRefName){ val repositoryInfo = getRepository(owner, repository) if(command.getType == ReceiveCommand.Type.DELETE && repositoryInfo.exists(_.repository.defaultBranch == branch)){ Some(s"refusing to delete the branch: ${command.getRefName}.") } else { getProtectedBranchInfo(owner, repository, branch).getStopReason(receivePack.isAllowNonFastForwards, command, pusher) } } else { None } } } |
2. Hook処理の登録
gitbucket.core.plugin.Plugin
を extend したクラスで、先ほど作成したクラスを下記のコードの10-12行目のように receiveHooks
に渡すことで、hookに登録されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import com.github.tohosokawa.ProtectedBranchService.ProtectedBranchReceiveHook import io.github.gitbucket.solidbase.model.Version class Plugin extends gitbucket.core.plugin.Plugin { override val pluginId: String = "branch-protect" override val pluginName: String = "Branch Protect Plugin" override val description: String = "Disables force and non force-pushes to this branch and prevents it from being deleted." override val versions: List[Version] = List(new Version("1.0.0"),new Version("1.0.1")) override val receiveHooks = Seq( new ProtectedBranchReceiveHook() ) } |
サンプルとして、今回作成したコードをサンプルとして下記に置いておきます。
さらっと作り方だけ書いてしまいましたが、Gitbucket作者様のサイトに書かれていた ReceiveHook
をキーワードにソースを読みながら作り方を調べました。
プロテクトされたブランチへのforce push、ブランチの削除、ステータスチェックが成功していないコミットのpushはリジェクトされます。
なお、この機能はReceiveHookという新しい拡張ポイントを使用して実装されています。この拡張ポイントを使用することでブランチプロテクションのようにリポジトリへのpushをフックするプラグインを作成することができます。
今回の実装はオリジナルのProtectedBranchService.scala
をほとんどそのまま使えたので大変ではありませんでしたが、初Scalaなのと、やり方を探すので意外に手間取ってしまいました。
でも、GitBucketはWindowsでも容易に動作させられるので、試してみてください。
Happy Coding!