at kaneshin

Free space for me.

Clojure と Leiningen と時々 Container 〜そしてデプロイへ〜

f:id:laplus-knsn:20161219001217p:plain

この記事は Clojure Advent Calendar 2016 の19日目の記事です。

Clojure を一度も使ったことが無かったのですが、少しは触ってみようと思い、今回 Clojure Advent Calendar 2016 に参加させてもらいました。

書くにあたり、凝ったことなんて全くできるはずもないので、自分らしい記事を書こうと思います。プログラミングには熟れており、これから初めて Clojure 触ってみる人の足がかりになるような記事になればと思います。

環境整備〜開発

とにかく、開発するなら Clojure を実行できなければダメだろうということで、Docker コンテナのイメージを使って楽に環境整備することに決めました。

Docker container

f:id:laplus-knsn:20161215020441p:plain

ここにあるコンテナイメージを pull して使用します。

$ docker pull clojure

タグでよく使うのは Description に記載されている通りすかね。

  • latest, lein-2.7.1
  • onbuild, lein-2.7.1-onbuild
  • alpine, lein-2.7.1-alpine
  • alpine-onbuild, lein-2.7.1-alpine-onbuild

コンテナイメージで不要なものが削減されたものを使用したければ alpine タグのを、実行するイメージなら onbuild タグのものを使用します。

まずは実行してみたいので、何も考えずに latest で取得して実行します。

$ docker pull clojure:latest
$ docke run -it -v ~/.clojure:/data-share clojure /bin/bash

これでインタラクティシェルでアタッチします。

Leiningen

アタッチしても Clojure の動かし方がわからなかったのですが、 /usr/local/bin に謎の lein というコマンドがあるので、「こいつが肝なんだろうな」とピンときました。

$ lein -h | head -n1
Leiningen is a tool for working with Clojure projects.

調べてみたところ、本当に色々やってくれるやつらしいので、こいつに乗っかることにします。

f:id:laplus-knsn:20161218104126p:plain



  < Clojure はワシが育てた。

さて、作業ディレクトリですが、私は Docker 起動時に /data-share というディレクトリをマウントしているので、そこで作業するとします。

まず、 lein new app でアプリケーションの雛形を作成します。

$ cd /data-share
$ lein new app hello-world

次に、 lein run で実行します。 勝手に色々とやってくれるので便利ですね。

$ cd hello-world
$ lein run
Retrieving org/clojure/clojure/1.8.0/clojure-1.8.0.pom from central
Retrieving org/sonatype/oss/oss-parent/7/oss-parent-7.pom from central
Retrieving org/clojure/tools.nrepl/0.2.12/tools.nrepl-0.2.12.pom from central
Retrieving org/clojure/pom.contrib/0.1.2/pom.contrib-0.1.2.pom from central
Retrieving clojure-complete/clojure-complete/0.2.4/clojure-complete-0.2.4.pom from clojars
Retrieving org/clojure/tools.nrepl/0.2.12/tools.nrepl-0.2.12.jar from central
Retrieving org/clojure/clojure/1.8.0/clojure-1.8.0.jar from central
Retrieving clojure-complete/clojure-complete/0.2.4/clojure-complete-0.2.4.jar from clojars
Hello, World!

もう Hello world 出ちゃいました。

REPL

Clojure でなく、 Leiningen でも REPL が起動できるんですね。なんかすごい。

$ lein repl
nREPL server started on port 59269 on host 127.0.0.1 - nrepl://127.0.0.1:59269
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
OpenJDK 64-Bit Server VM 1.8.0_111-8u111-b14-2~bpo8+1-b14
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

hello-world.core=> (-main)
Hello, World!
nil

完璧じゃないっすか…!と、いうことで、ここで終わりにしたいと思ったのですが、Clojure 使いの人から…

f:id:laplus-knsn:20161218103156p:plain




f:id:laplus-knsn:20161218103645p:plain



Webアプリケーションデプロイ とな。笑止。

duct on GKE

duct については紹介してもらった Qiita の記事を、GKE については自分の記事を参考にしています。

qiita.com

blog.kaneshin.co

duct

Leiningen にテンプレートがあるので、そちらから作成します。 +example は example というエンドポイントをリソースとして用意してくれるようです。

$ lein new duct myduct +example +cljs

ローカルの設定ファイルを生成するために、セットアップを行います。

$ lein setup

次に、サーバーを起動するために REPL を起動します。Clojure の REPL 文化は素晴らしいですね。

$ lein repl
nREPL server started on port 53589 on host 127.0.0.1 - nrepl://127.0.0.1:53589
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
OpenJDK 64-Bit Server VM 1.8.0_111-8u111-b14-2~bpo8+1-b14
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (dev)
:loaded
dev=> (go)
2016-12-18 01:59:12.911:INFO::nREPL-worker-0: Logging initialized @14796ms
Compiling "target/figwheel/myduct/public/js/main.js" from ["src" "dev"]...
Successfully compiled "target/figwheel/myduct/public/js/main.js" in 12.965 seconds.
notifying browser that file changed:  target/figwheel/myduct/public/js/goog/deps.js
notifying browser that file changed:  target/figwheel/myduct/public/js/cljs_deps.js
notifying browser that file changed:  out/cljs/user.js
:started
dev=> 

参考:Getting Started · duct-framework/duct Wiki · GitHub

起動したら、 curl などで叩いてみましょう。ポートは3000番で起動しています。

# execute command from outside.
# $ docker exec -it [Container Name] curl localhost:3000/example

$ curl localhost:3000/example
This is an example endpoint

REPL 上で (reset) すればリロードされるようです。 +cljs を付与しているので、 ClojureScript もここでコンパイルされるようです。(正直、よくわからない)

dev=> (reset)
:reloading (myduct.endpoint.example myduct.main myduct.endpoint.example-test cljs.stacktrace dev user)
:resumed
dev=>

エンドポイント追加

エンドポイントをひとつ追加してみます。

$ vim src/myduct/endpoint/hello.clj

ファイルの中身は example とほぼ同じですが、Hello 用にカスタマイズしました。

(ns myduct.endpoint.hello
  (:require [compojure.core :refer :all]))

(defn hello-endpoint [config]
  (context "/hello" []
    (GET "/" []
      "Hello, World!")))

次に、 system.edn ファイルにエンドポイントを定義します。

$ vim resources/myduct/system.edn

:endpoints で先ほどの Hello world を定義し、それを :app に登録します。

{:components
 {:app  #var duct.component.handler/handler-component
  :http #var ring.component.jetty/jetty-server}
 :endpoints
 {:example #var myduct.endpoint.example/example-endpoint
  :hello #var myduct.endpoint.hello/hello-endpoint}
 :dependencies
 {:http [:app]
  :app  [:example :hello]
  :example []}

ここまでできたら REPL でアプリを一度リセットします。

dev=> (reset)
:reloading (myduct.endpoint.hello)
Compiling "target/figwheel/myduct/public/js/main.js" from ["src" "dev"]...
Successfully compiled "target/figwheel/myduct/public/js/main.js" in 0.227 seconds.
:resumed

また curl などで /hello のパスを叩くと、Hello, World! が返却されると思います。

$ curl localhost:3000/hello
Hello, World!

duct をコンテナ化

ここまでできたらコンテナイメージとして作成し、Google Container Registry にプッシュします。

Dockerfile を duct プロジェクトのルートに置き、下記のように onbuild タグのコンテナイメージを使って docker build します。

FROM clojure:onbuild
# FROM clojure:alpine-onbuild

軽量にしたい場合は alpine-onbuild を使用してください。

# ビルド
$ docker build -t hello-duct .

# 実行
$ docker run -p 8080:3000 hello-duct

# 確認
$ curl -XGET localhost:8080/hello
Hello, World!

Google Container Registry にプッシュします。

# タグのエイリアス
$ docker tag hello-duct asia.gcr.io/[YOUR_PROJECT_ID]/hello-duct

# Registry にプッシュ
$ gcloud docker push asia.gcr.io/[YOUR_PROJECT_ID]/helloworld

ここまでできれば後は下記を参考にして GKE にデプロイすることが可能です。

blog.kaneshin.co

ClojureScript is 何者?

「まじでわからん。」という状態だったのですが、下記のスライド読んだら大体把握できたのと、「すごくね?」という感情が生まれました。

www.slideshare.net

GKE を使って golang アプリケーションコンテナを稼働させる

f:id:laplus-knsn:20161215015401p:plain

この記事は Google Cloud Platform(1) Advent Calendar 2016 の15日目の記事です。

普段は golang をやってるアプリケーションの人っぽい感じになっていますが、他にも色々とやっています。(と、いうアピールをたまにしておかないと) さて、今回は GKE + golang のデプロイ話でもしましょう。結局、golang が絡んでいますね。

準備

Docker をインストールしておき、使用できるようにしましょう。

コンテナイメージの作成・プッシュ

f:id:laplus-knsn:20161215020441p:plain

プロジェクト構成は下記のようにし、docker ビルドしたイメージを Container Registry にプッシュするようにします。

helloworld/
├── deployment.yaml
├── Dockerfile
├── main.go
└── service.yaml

main.go

アプリケーションコードはとてもシンプルに、8080番ポートで構えるサーバーを用意し、アクセスがあったときに "Hello world!" と表示するアプリケーションを用意します。

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello world!")
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}

動作確認するには go run してビルド・起動し、 curl などで叩いてみてください。

# 実行
$ go run main.go

# 確認
$ curl -XGET localhost:8080
Hello world!

Dockerfile

golang をビルドするイメージが公開されているので、そのイメージを使用します。

FROM golang:1.7.4-onbuild

Docker ビルド・実行

main.go と Dockerfile が用意できたら実際にイメージをビルド・実行してみます。

# ビルド
$ docker build -t hello-container .

# 実行
$ docker run -p 8080:8080 hello-container

# 確認
$ curl -XGET localhost:8080
Hello world!

ビルドや実行で使用している hello-container はコンテナイメージへのタグです。

Container Registry にプッシュ

先ほど作成したコンテナイメージを Google Container Registry にプッシュします。先ほど作成したタグにエイリアスを張っておきます。

$ docker tag hello-container asia.gcr.io/[YOUR_PROJECT_ID]/helloworld

asia.gcr.io の部分はホスト先を指定することが可能です。

Registry Host Region
us.gcr.io US
es.gcr.io EU
asia.gcr.io ASIA
gcr.io US (unfixed)

あと、わざわざエイリアスを張らずに、直接レジストリの命名でタグを作成しても大丈夫です。 Google Container Registry 用にエイリアス・タグを作成したらプッシュします。

$ gcloud docker push asia.gcr.io/[YOUR_PROJECT_ID]/helloworld

[YOUR_PROJECT_ID] は GCP の Project ID を指定するようにしてください。


ここまで作成できたらコンテナイメージ作成については完了です。

Container Engine クラスタの作成

デプロイ先となるクラスタの作成を行います。コンソールから作成もできますし、コマンドからも作成が可能です。 お試しで試してみるなら Cloud Shell をオススメします。

f:id:laplus-knsn:20161215012952p:plain

クラスタ作成

今回は gcloud コマンドから作成します。

$ gcloud container clusters create helloworld --zone asia-northeast1-c \
  --scopes cloud-platform \
  --num-nodes 2

scopes オプションはアプリケーションの必要に応じて datastore や bigquery のスコープも --scopes cloud-platform,datastore,bigquery のように増やしてください。

f:id:laplus-knsn:20161215013439p:plain

クラスタの認証

gcloud コマンドで認証を行うために、作成したクラスタの認証から行います。

$ gcloud container clusters get-credentials helloworld --zone asia-northeast1-c

Fetching cluster endpoint and auth data. kubeconfig entry generated for helloworld.

が出力されればOK。

クラスタの確認

問題なく作成されたか確認してみます。

$ kubectl cluster-info
Kubernetes master is running at https://xxx.xxx.xxx.xxx
GLBCDefaultBackend is running at https://xxx.xxx.xxx.xxx/api/v1/proxy/namespaces/kube-system/services/default-http-backend
Heapster is running at https://xxx.xxx.xxx.xxx/api/v1/proxy/namespaces/kube-system/services/heapster
KubeDNS is running at https://xxx.xxx.xxx.xxx/api/v1/proxy/namespaces/kube-system/services/kube-dns
kubernetes-dashboard is running at https://xxx.xxx.xxx.xxx/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard

デプロイ

コンテナイメージ化されたアプリケーションをデプロイするためクラスタのレプリカセットとサービスの作成を行います。

Deployment

deployment.yaml という命名で下記のような Yaml ファイルを用意し、 kubectl コマンドを使用してレプリカセットを作成します。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: helloworld
  labels:
    app: helloworld
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
      - name: helloworld-app
        image: us.gcr.io/[YOUR_PROJECT_ID]/hello-golang:latest
        imagePullPolicy: Always
        ports:
        - name: http-server
          containerPort: 8080

continers.image には先ほどプッシュしたコンテナイメージの Registry Host を指定します。ここまでできたら kubectl コマンドを使用します。

$ kubectl create -f deployment.yaml 

レプリカセットの進捗状況は下記で確認がとれます。

$ kubectl get deployments
NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
helloworld   3         3         3            3           42m

Service

今度は service.yaml という命名で下記のような Yaml ファイルを用意します。サービスはレプリカセットと一緒に作成されたポッドにアクセスするための定義になります。

apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: http-server
  selector:
    app: helloworld
$ kubectl create -f service.yaml 

サービスの作成進捗は下記を叩くことでわかりますし、 LoadBalancer Ingress の IP が公開された IP アドレスとなるので、LoadBalancer 作成後、この IP にアクセスすることによって "Hello world!" が表示されるはずです。

$ kubectl describe service helloworld
Name:                   helloworld
Namespace:              default
Labels:                 app=helloworld
Selector:               app=helloworld
Type:                   LoadBalancer
IP:                     xxx.xxx.xxx.xxx
LoadBalancer Ingress:   xxx.xxx.xxx.xxx.
Port:                   <unset> 80/TCP
NodePort:               <unset> 31510/TCP
Endpoints:              xxx.xxx.xxx.xxx:8080,xxx.xxx.xxx.xxx:8080,xxx.xxx.xxx.xxx:8080
Session Affinity:       None
Events:
  FirstSeen     LastSeen        Count   From                    SubobjectPath   Type            Reason                 Message
  ---------     --------        -----   ----                    -------------   --------        ------                 -------
  43m           43m             1       {service-controller }                   Normal          CreatingLoadBalancer   Creating load balancer
  42m           42m             1       {service-controller }                   Normal          CreatedLoadBalancer    Created load balancer

動作検証

ここまでできたら、先ほどの LoadBalancer Ingress にある IP にアクセスして "Hello world!" が表示されることを確認してみましょう。

停止方法

クラスタを削除することによって、ポッドやロードバランサーもあわせて削除されます。超楽。

$ gcloud container clusters delete helloworld --zone asia-northeast1-c
The following clusters will be deleted.
 - [helloworld] in [asia-northeast1-c]

リソース

今年の写真 #TYPAC2016

この記事は 今年の写真 #TYPAC2016 Advent Calendar 2016 の14日目の記事です。


今年も去年と同じCanonの一眼レフのボディはEOS 6DでレンズはEF24-70mm F2.8L II USMで変わらずです。

Canon デジタル一眼レフカメラ EOS 6Dボディ EOS6D

Canon デジタル一眼レフカメラ EOS 6Dボディ EOS6D

Canon 標準ズームレンズ EF24-70mm F2.8L II USM フルサイズ対応

Canon 標準ズームレンズ EF24-70mm F2.8L II USM フルサイズ対応

新しい望遠レンズが欲しいと思いつつも、そこまでズームをしないので事足りているのですが、今以上のズームができたら世界が広がるんだろうなぁと思っています。

Canon 望遠ズームレンズ EF70-200mm F2.8L IS II USM フルサイズ対応

Canon 望遠ズームレンズ EF70-200mm F2.8L IS II USM フルサイズ対応

今年の写真

さて、今年の写真は自分が訪れた場所でチョイスしました。

  • デンバー
  • 鎌倉
  • 尾道

の三地点となりました。

デンバー

デンバーで開催された Golang のカンファレンスの GopherCon に参加したときに撮影した写真です。北米の内陸部は土地が広大で、RTD(電車)に乗ってると緩やかな旅のように感じることができました。

f:id:laplus-knsn:20161122010740j:plain

f:id:laplus-knsn:20161122010749j:plain

f:id:laplus-knsn:20161122010754j:plain

鎌倉

鎌倉は瑞泉寺がオススメなので、是非行ってもらいたいです。

f:id:laplus-knsn:20161122013746j:plain

f:id:laplus-knsn:20161122013754j:plain

f:id:laplus-knsn:20160507162012j:plain

夜の段葛は趣があるので、昼に歩くときとはまた違った印象になると思います。

f:id:laplus-knsn:20161122013808j:plain

f:id:laplus-knsn:20161122013813j:plain

七里ガ浜高校近くの踏切。スラムダンクのオープニングです。

f:id:laplus-knsn:20161122013802j:plain

尾道

大林宣彦監督の尾道三部作や、月刊マガジンで連載されている「ぱすてる」の舞台で、いつかは行きたいと思っていたところでした。

f:id:laplus-knsn:20161122015502j:plain

映画「ふたり」の電柱です。この場所、見つけ出すのに苦労しました。ちなみに、僕は28歳なので「ふたり」世代ではないです(笑)

f:id:laplus-knsn:20161122015509j:plain

ここは漫画「ぱすてる」で出てくる場所です。尾道を散策しているだけで、漫画に出てくる所がたくさんあるので読み込んでから行くと楽しいです。

f:id:laplus-knsn:20161122015517j:plain

海龍寺〜浄土寺山〜浄土寺山展望のルートです。浄土寺山からの見晴らしはとても素晴らしいですが、展望台に行くまで若干険しかった記憶が。

f:id:laplus-knsn:20161122015538j:plain

f:id:laplus-knsn:20161122015523j:plain

f:id:laplus-knsn:20161122015532j:plain

また来年

来年はもっと撮影しようかなと!

Vim をあまりカスタマイズしなくなった話

f:id:laplus-knsn:20161212225440p:plain

この記事は Vim Advent Calendar 2016 - Qiita の11日目の記事です。

社内ではVimハラスメントが横行していますが、私自身のVimは毎朝やる最新版ビルドを除いてカスタマイズすることがほぼ無くなりました。 元からハードにカスタマイズはしておらず、 ctrlp あれば十分だなという感じで、たまに .vimrc をいじるのは新しい言語を試すときにシンタックスのプラグインを導入するくらいです。

さて、そんなVimmerの私が「これだけおさえていれば充分」という基本機能を紹介します。


ちなみに、 ctrlp の記事を4年前の Vim Advent Calendar で書いています。

blog.kaneshin.co

バッファ

Vimにはバッファという機能があり、開いたファイルをVim上で管理されています。バッファを確認するには :buffers:ls コマンドを叩いて確認します。

f:id:laplus-knsn:20161211221409p:plain

バッファの表示について、詳しくは :h ls などでヘルプを確認すれば詳細はわかるかと思います。

このバッファで表示された際、添字として数字がついています。このバッファを開くためには :b [N] のように数字を指定することによって開くことができます。

上にある画像を例にすると、 get.go というバッファを開くためには :b 1 とするか :b # とすることによって開きます。

タブ

タブもとても便利です。ただ、バッファを使いこなせばGUI用の上位互換でしかないのかなという印象にもなるので、バッファを使いこなすことがピュアな Vim を使うための大事な一歩だと思います。

GUI の Vim を使っている場合はタブをクリックすることにより、そのタブを開くことができるので、GUI用なのかなという気持ちもあります。

補完

f:id:laplus-knsn:20161211223016p:plain

Vimプラグインには強力な補完が存在していますが、私は補完をするときのロードによって起こる若干の引っかかりがとても気になるため、デフォルトのキーワード補完以外は使用していません。

ただ、オムニ補完はもっと使った方が良いなとは思っており、私は golang を主に実装するのですが、これから述べることにも書きますが、 GoDoc を開きながら実装することもしばしばあるので、そこは自身の課題として少し思っています。

が、キーワード補完便利過ぎる…(ふだん、他の補完を使用している人には機能として薄いと思われていると思いますが…)

Vim with golang

f:id:laplus-knsn:20161211223939p:plain

※これはカスタマイズしてる Vim

そもそも、ここまでシンプルに Vim を使うことになった理由は golang のシンプルさが関係している気がします。キーワード補完も、バッファに対象となるキーワードが存在することによって補完できるため、 GoDoc を用いながら実装をしています。

また、ほとんど golang の構文や関数を覚えているので、ぶっちゃけると GoDoc やシンタックスハイライトは目に見える情報としてなくても実装できる が正直なところとしてあります。

目に見える情報でコーディングするのも大事ですが(不具合を出さない点で)、環境を依らずに実装できるのも楽なのでオススメ(?)です。

おわりに

Vim をカスタマイズしなくとも golang は実装できるので、下記の画像にある状態でも実装を進めていたりします。

f:id:laplus-knsn:20161211223443p:plain

実装したあと、インデントに関しては gofmt -s -w get.go のようにしてフォーマッティングすれば良いため、あとは普通に実装が通るか否か、実装が間違えていないかの問題でしかありません。

普段、高級エディタに慣れている人も、たまにはプレインなエディタに戻ってみるのもアリだと思って Vim を触ってみましょう。

リソース

www.kaoriya.net

【Git】コミットメッセージのスペルチェック

f:id:laplus-knsn:20161209000103p:plain

この記事は Git Advent Calendar 2016 - Qiita の8日目の記事です。

スペルチェッカー - aspell

スペルチェックをするために aspell というスペルチェッカーを使用します。

f:id:laplus-knsn:20161209000151j:plain

インストール on macOS

macOS では Homebrew 🍺 を使ってインストールします。

# To install aspell
$ brew install aspell

# make sure that the aspell has been installed.
$ which aspell
/usr/local/bin/aspell

インストール on Debian

Debian しか使っていないものでして…

# To install apell
$ apt-get install aspell -y

# make sure that the aspell has been installed.
$ which aspell
/usr/bin/aspell

使い方

list コマンドを使用して、STDIN から文字列を流します。

# Test (Typo beautifl)
$ echo What a beautifl sky | aspell list
beautifl

上の例では beautiful をわざとタイポしています。

Git フック

どのタイミングで検証をするかはやはり Git フックで対応することになりますが、pre-commit などのクライアントサイドフック はよくあるので今回はサーバーサイドフックを使用します。

サーバーサイドフック

サーバーサイドフックスクリプトは知らない人は結構いる気がします。GitHub で特定のブランチのフォースプッシュを禁じる設定があると思いますが、あれはサーバーサイドフックが設定されているからです。

クライアントサイドフックの他に、いくつかのサーバーサイドフックを使うこともできます。これは、システム管理者がプロジェクトのポリシーを強制させるために使うものです。これらのスクリプトは、サーバへのプッシュの前後に実行されます。pre フックをゼロ以外の値で終了させると、プッシュを却下してエラーメッセージをクライアントに返すことができます。つまり、プッシュに関するポリシーをここで設定することができるということです。

サーバーサイドフックには下記の3種類存在しています。

  • pre-receive
  • post-receive
  • update

pre-receive, post-receive フック

クライアントからのプッシュを処理するときに最初に実行されるスクリプトが pre-receive で、処理が終了した後に実行されるのが post- receiveです。スクリプトは、プッシュされた参照のリストを標準入力から受け取り、ゼロ以外の値で終了させるとプッシュが却下されます。post-receive フックについては処理の完了通知などに使うことができます。

update フック

update フックは pre-receive フックと似ていますが、pre-receive と違うのは複数のブランチへのプッシュがあったときにブランチ単位でそれぞれ一度ずつ実行されます。

スペルチェック方法

1. pre-receive

下記のスクリプトを .git/hooks/pre-receive に記述します。

#!/bin/sh

which aspell > /dev/null
if [ ! $? -eq 0 ] ; then
  exit 0
fi

while read oldrev newrev refname
do
  misspelled=`git log --format=%B -n 1 "$newrev" | aspell list`
  if [ -n "${misspelled}" ] ; then
    echo >&2 "Possible misspelled words in the commit message:"
    for e in $misspelled; do echo >&2 " - ${e}"; done
    exit 1
  fi
done

2. コミットメッセージ例

今回、下記のようなコミットメッセージを用意しました。beautiful をタイポしています。

$ git log
commit f4eb3de
Author: Shintaro Kaneko <kaneshin0120@gmail.com>
Date:   Thu Dec 8 14:03:37 2016 +0000

    Initial commit: What a beautifl sky

3. git push

下記のようにプッシュを行うと、メッセージとともにプッシュがリジェクトされます。

$ git push origin master 
Counting objects: 1, done. 
Writing objects: 100% (1/1), 190 bytes | 0 bytes/s, done. 
Total 1 (delta 0), reused 0 (delta 0) 
remote: Possible misspelled words in the commit message: 
remote:  - beautifl 
To /home/kaneshin/foo/rem 
 ! [remote rejected] master -> master (pre-receive hook declined) 
error: failed to push some refs to '/home/kaneshin/foo/rem' 

おわりに

pre-commit とかでは個人の環境に依ってしまうので、サーバーサイドフックでカバーできるところは積極的にカバーしていきたいものです。