しおしお

IntelliJ IDEAのことなんかを書いてます

WebStormでDenoを使ってみる

お仕事でDenoを使う機会があったので、WebStormで環境構築や実行&デバッグできるか試してみました。

使用したバージョン

Deno

$ deno --version
deno 1.14.1 (release, x86_64-unknown-linux-gnu)
v8 9.4.146.15
typescript 4.4.2

WebStorm

WebStorm 2021.3 EAP

WebStormにDeno環境を準備

プラグインのインストール

WebStorm用のDenoプラグインがあるので、インストールしてあげます。

プラグインの有効化

設定画面の、Languages & Frameworks > Denoからプロジェクトに対するDenoサポートを有効にしてあげます。 f:id:sioiri:20210924132701p:plain

実行

コードの準備

ファイルを読み込んで標準出力に出力するだけの簡単なコードを準備してみました。

const data = await Deno.readTextFile('data.json')
console.log(data)

実行

実行は、メニューのRun > Run..から行います。Denoプラグインを有効化していると自動的にDenoでの実行が選択されるようです。

実行時に権限の追加を行う場合には、実行構成の画面からArgumentsを編集してあげます。 今回は、ファイルを読み込むので--allow-readを追加しています。 f:id:sioiri:20210924134426p:plain

デバッグ実行

デバッグ実行は、ブレークポイントを設定してデバッグ実行してあげるだけになります。 あとは、ブレークポイントを設定した場所で止まってくれるので、状態を見に行ったりステップ実行などをしてあげる感じになります。 f:id:sioiri:20210924134750p:plain

Spring Data R2DBCで複数のデータベースに接続してみる

やりたいこと

単一のWebFluxのアプリケーションで、Spring Data R2DBCを使って複数のデータベースに接続してSQLを実行したい。

環境準備

データベース

準備が簡単なので、Docker Composeを使ってデータベースを2つ起動しています。

version: '3.7'
services:
  db1:
    image: 'postgres'
    ports:
      - "15432:5432"
    environment:
      - POSTGRES_HOST_AUTH_METHOD=trust
  db2:
    image: 'postgres'
    ports:
      - "15433:5432"
    environment:
      - POSTGRES_HOST_AUTH_METHOD=trust

テーブル

アプリケーションで使用するテーブルをそれぞれのデータベースに作成してあげます。

db1

create table hoge (
    id   char(36) not null,
    name varchar(100),
    primary key (id)
);

db2

create table fuga (
    id   serial not null,
    name varchar(100),
    primary key (id)
);

アプリケーションの実装

Spring Bootバージョン

Spring Bootは現時点で最新のバージョンを使って試してみます。

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.4</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>

データベースの接続を切り替えるための実装

Configurationの準備

db1とdb2に接続するためのConfigurationを準備します。

Configurationには@EnableR2dbcRepositoriesアノテーションを追加し、この接続設定を使うベースパッケージをbasePackagesに指定します。 また、指定したベースパッケージ配下で利用するR2dbcEntityTemplateのBean名をentityOperationsRefに指定します。 これらを設定してあげるだけで、パッケージ単位に利用するR2dbcEntityTemplateが自動的に切り替わるので、複数のデータベースへのアクセスが可能になります。

@Configuration
@EnableR2dbcRepositories(
    basePackages = ["siosio.multipler2dbc.db1"],
    entityOperationsRef = "db1EntityTemplate"
)
class Db1Config {

    @Bean
    @ConfigurationProperties(prefix = "db1")
    fun db1Properties(): R2dbcProperties = R2dbcProperties()

    @Bean
    fun db1ConnectionFactory(): ConnectionFactory = db1Properties().toConnectionFactory()

    @Bean
    fun db1EntityTemplate() = R2dbcEntityTemplate(db1ConnectionFactory())
}

@Configuration
@EnableR2dbcRepositories(
    basePackages = ["siosio.multipler2dbc.db2"],
    entityOperationsRef = "db2EntityTemplate"
)
class Db2Config {

    @Bean
    @ConfigurationProperties(prefix = "db2")
    fun db2Properties(): R2dbcProperties = R2dbcProperties()

    @Bean
    fun db2ConnectionFactory(): ConnectionFactory = db2Properties().toConnectionFactory()

    @Bean
    fun db2EntityTemplate() = R2dbcEntityTemplate(db2ConnectionFactory())
}

fun R2dbcProperties.toConnectionFactory(): ConnectionFactory {
    val connectionFactory = ConnectionFactoryOptions.parse(url)
        .mutate()
        .apply {
            username?.let { option(ConnectionFactoryOptions.USER, it) }
            password?.let { option(ConnectionFactoryOptions.PASSWORD, it) }

        }.build().let {
            ConnectionFactories.get(it)
        }
    val poolConfiguration = ConnectionPoolConfiguration
        .builder(connectionFactory)
        .maxSize(pool.maxSize)
        .initialSize(pool.initialSize)
        .build()
    return ConnectionPool(poolConfiguration)
}

接続先の設定

db1db2に対する接続先の情報をproperties(yml)に設定します。 今回は、db1db2としてConfigurationクラスにプレフィックスと指定しているので、設定値はdb1.またはdb2.から始まります。 プレフィクス以降は、R2dbcPropertiesに対して設定可能なキーを指定してあげます。

db1.url=r2dbc:postgresql://localhost:15432/postgres
db1.username=postgres

db2.url=r2dbc:postgresql://localhost:15433/postgres
db2.username=postgres

データベースに対するアクセスする側の実装

それぞれのデータベースに対してアクセスするには、Configurationで指定したパッケージ配下にデータベースに対する処理を実装するだけとなります。 今回の例では、次ののように実装するだけで自動的に接続先が切り替わります。

  • db1に対する処理は、siosio.multipler2dbc.db1パッケージ配下に実装します。
  • db2に対する処理は、siosio.multipler2dbc.db2パッケージ配下に実装します。

あとは、単一データベース接続のときと同じようにデータベースアクセスするだけですね。1

db1への処理

package siosio.multipler2dbc.db1

// importは省略

@Component
class Db1Handler(private val hogeRepository: HogeRepository) {
    suspend fun post(request: ServerRequest) : ServerResponse {
        val hoge = hogeRepository.save(Hoge(UUID.randomUUID().toString(), "hoge")).awaitSingle()
        return ServerResponse.ok().bodyValueAndAwait(mapOf("id" to hoge.id))
    }
}

@Repository
interface HogeRepository: ReactiveCrudRepository<Hoge, String>

//create table hoge (id char(36) not null, name varchar(100), primary key(id))
data class Hoge(
    @Id private val id: String,
    val name: String
) : Persistable<String> {
    override fun isNew(): Boolean = true
    override fun getId() = id
}

db2への処理

package siosio.multipler2dbc.db2

// importは省略

@Component
class Db2Handler(private val fugaRepository: FugaRepository) {
    suspend fun post(request: ServerRequest): ServerResponse {
        val fuga = fugaRepository.save(Fuga(name = "fuga")).awaitSingle()
        return ServerResponse.ok().bodyValueAndAwait(mapOf("id" to fuga.id))
    }
}

@Repository
interface FugaRepository : ReactiveCrudRepository<Fuga, Int>

//create table fuga (id serial not null, name varchar(100), primary key(id))
data class Fuga(
    @Id val id: Int? = null,
    val name: String
)

動作確認

curlでそれぞれのエンドポイントを叩くと、エラーなど起きずにデータベースの処理結果のIDが返ってくることが確認できます。 これで、複数データベースへの接続確認はOKですね。

$ curl -X POST http://localhost:8080/db1
{"id":"6c1d2c95-d36f-4b1d-acea-366f6a45e240"}

$ curl -X POST http://localhost:8080/db2
{"id":9}

ソースコード

使用したサンプルプロジェクト全体は、以下から確認できます。 github.com


  1. この実装例では、トランザクション制御は省略しています

Spring Boot + MVCでkotlinx.serializationを使ってリクエスト・レスポンスのシリアライズとデシリアライズをしてみる

Spring Boot + MVCでkotlinx.serializationを使ってリクエスト・レスポンスのシリアライズとデシリアライズを試してみました。

使用バージョンなど

↓のバージョンを使って試してみました。

  • Spring Bootは、2.5.4
  • kotlinx.serialization

build.gradle.ktsはこのようになっています。

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.5.4"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.5.30"
    kotlin("plugin.spring") version "1.5.30"
    kotlin("plugin.serialization") version "1.5.30"
}

// 〜省略〜
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

お試ししてみる

コード

実装的には、kotlinx.serializationを使うためにdata classに@Serializableアノテーションが設定されている感じですね。 なお、kotlinx-serialization-jsonに関連するクラスがクラスパス上にあると、自動的にkotlinx.serialization用のHttpMessageConverterが使われるようになります。(優先度的には、Jacksonより高くなる感じです。)

今回は、リクエストボディとレスポンスボディの変換でkotlinx.serializationが使われることを確認できるコードを書いてみました。

@RestController
@RequestMapping("/hello")
class HelloController {

    private val logger = LoggerFactory.getLogger(HelloController::class.java)

    @GetMapping
    fun get(): Message {
        return Message("hello!!!")
    }

    @PostMapping
    fun post(@RequestBody message: Message) {
        logger.info("mesage: {}", message)
    }
}

@Serializable
data class Message(
    val text: String
)

実行結果

レスポンスボディの変換

kotlinx.serializationを使ってるか非常に分かりづらいですが、レスポンスとして指定したdata classの内容がJSONで返ってきていますね。

$ curl  localhost:8080/hello
{"text":"hello!!!"}

リクエストボディの変換

curl -X POST -H 'content-type: application/json' localhost:8080/hello -d '{"text": "siosio!"}'

ログにリクエストボディを保持しているdata classの内容が出力されていますね。

2021-09-05 15:38:02.745  INFO 43536 --- [nio-8080-exec-3] s.b.HelloController                      : mesage: Message(text=siosio!)

カスタマイズして使ってみる

デフォルトの構成では何も設定が入っていない状態で使われるため、data classに定義されていない項目がリクエストのJSONにあったりすると400 Bad Requestが返されたりしちゃいます。 例えば、次のリクエストを投げると、Could not read JSON: Unexpected JSON token at offset 21: Encountered an unknown key 'hoge'. なメッセージのログが出力され400エラーとなります。

curl -X POST -H 'content-type: application/json' localhost:8080/hello -d '{"text": "siosio!", "hoge": "fuga"}'

これでは、ちょっと使い勝手が悪いですよね。これは設定をカスタマイズしたkotlinx.serialization用のHttpMessageConverterを登録するだけで解決します。 カスタマイズしたHttpMessageConverterは、WebMvcConfigurerをimplementsしたクラスを作ってconfigureMessageConvertersをオーバライドすることで登録できます。
↓のコードのように、KotlinSerializationJsonHttpMessageConverterに設定をカスタマイズしたものを指定し、convertersの先頭に追加します。(末尾に追加してしまうと、デフォルト設定のKotlinSerializationJsonHttpMessageConverterが使われちゃうので注意です。)

@Configuration
class WebConfig : WebMvcConfigurer {

    override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        converters.add(0, KotlinSerializationJsonHttpMessageConverter(Json {
            ignoreUnknownKeys = true
        }))
    }
}

Dartの拡張関数を使ってみる

Dartの拡張関数とは

Kotlinと同じような感じで、既存のクラスに対して関数追加できちゃう感じですね。 ドキュメントはこちら→(Extension methods | Dart)

構文

extension onを使って拡張したい型に対して、拡張関数を定義してあげます。 extensiononの間に任意の名前を指定することで、別名をつけることもできるみたいですね。

extension <extension name> on <type> {
  (<member definition>)*
}

拡張関数を試してみる

パターン1

extension onStringを指定することで、Stringに対して拡張関数を追加できますね。 拡張関数は、通常の関数と同じように呼び出すことができますね。

void main() {
  print("hoge".hello());
}

extension on String {
  String hello() {
    return "hello, ${this}";
  }
}

実行結果

$ dart extension-sample.dart
hello, hoge

パターン2

Generics対応したListに対しても拡張関数を追加できるか確認してみます。 この例だと、List<String>に対しては拡張関数を呼び出せますが、List<int>に対しては呼び出すことができません。

void main() {
  var ints = ['1', '2'].toInt();
  print(ints);

}

extension on List<String> {
  List<int> toInt() {
    return this.map((e) => int.parse(e)).toList();
  }
}

実行結果

$ dart list-extension.dart
[1, 2]

拡張関数名が衝突した場合…

スコープ内に同じ名前の拡張関数が存在知る場合は、こんな感じのビルドエラーとなります。 この例だと、Stringに対して追加した拡張関数のhogeが複数存在しているよって感じですね。

※同一スコープ内で名前衝突が起こるような拡張関数を生やすことって無いと思いますが…

Error: The method 'hoge' is defined in multiple extensions for 'String' and neither is more specific.
Try using an explicit extension application of the wanted extension or hiding unwanted extensions from scope.

衝突を回避するために

extension onじに、別名をつけてあげることで回避できるようです。 拡張関数を使う際には、別名の型に事前に変換してから呼び出す感じになるようです。

void main() {
  String2('').hoge();
}

extension String2 on String {
  void hoge() {
    print('hoge');
  }
}

Elasticsearchで条件にマッチした部分を取得する

Elasticsearchで条件にマッチしたワードをハイライト表示したいなんてことありますよね。 これを実現するために、条件にマッチした部分を検索結果から取得する方法になります。

準備

インデックス内容

フィールドをひとつだけ持つインデックスを作成して確認してみます。

{
  "settings": {
    "number_of_shards": 1,
    "analysis": {
      "analyzer": {
        "kuromoji": {
          "type": "custom",
          "tokenizer": "kuromoji_tokenizer"
        }
      }
    }
  },
  "mappings": {
    "_doc": {
      "properties": {
        "name": {
          "type": "text",
          "analyzer": "kuromoji"
        }
      }
    }
  }
}

データ投入

JavaのClientを使って、2つのドキュメントを投入します。

fun main() {
    createClient().use { client ->

        val bulkRequest = BulkRequest().apply {
            add(IndexRequest("test", "_doc").apply {
                source(mapOf("name" to "あいうえお かきくけこ"))
            })
            add(IndexRequest("test", "_doc").apply {
                source(mapOf("name" to "さしすせそ たちつてと"))
            })
        }
        val response = client.bulk(bulkRequest, RequestOptions.DEFAULT)
        println(response.status())
    }

}

検索条件にマッチした部分を取得してみる

検索クエリー

検索クエリーを投げる際の、リクエストボディにhighlightを追加してあげます。 また、highlightにはハイライト表示したい(条件にマッチした部分を知りたい)フィールド名を指定してあげます。

{
  "query": {
    "bool": {
      "filter": {
        "terms": {
          "name": ["あい", "たち"]
        }
      }
    }
  },
  "highlight": {
    "fields": {
      "name": {
      }
    }
  }
}

検索結果

検索結果の各ドキュメントごとに、highlightから検索条件にマッチした部分を取得できます。 デフォルトでは、emタグでマッチした部分が囲まれて返ってきます。

{
  "took": 8,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 0.0,
    "hits": [
      {
        "_index": "test",
        "_type": "_doc",
        "_id": "_3tmSnoBBFTrBATTDpDa",
        "_score": 0.0,
        "_source": {
          "name": "あいうえお かきくけこ"
        },
        "highlight": {
          "name": [
            "<em>あい</em>うえお かきくけこ"
          ]
        }
      },
      {
        "_index": "test",
        "_type": "_doc",
        "_id": "AHtmSnoBBFTrBATTDpHa",
        "_score": 0.0,
        "_source": {
          "name": "さしすせそ たちつてと"
        },
        "highlight": {
          "name": [
            "さしすせそ <em>たち</em>つてと"
          ]
        }
      }
    ]
  }
}

JavaのClientで試してみる

検索用のリクエスト(SearchRequest)のhighlighterにハイライト表示したいフィールド名を指定してあげます。 検索結果からは、検索にヒットしたドキュメントごとhighlightFieldsからハイライト表示されたフラグメントが取得できます。

コード

fun main() {
    createClient().use { client ->

        val searchRequest = SearchRequest("test").apply {
            types("_doc")
            source(SearchSourceBuilder().apply {
                query(QueryBuilders.boolQuery().apply {
                    filter(QueryBuilders.termsQuery("name", "あい", "かき", "たち"))
                })
                highlighter(HighlightBuilder().apply {
                    field("name")
                })
            })
        }
        val response = client.search(searchRequest, RequestOptions.DEFAULT)
        println(response.status())
        response.hits.hits.forEach { 
            it.highlightFields.forEach {
                println("it = ${it.value}")
            }
        }
    }
}

実行結果

APIを直接呼び出したときと同じように、ハイライト表示対応されたフラグメントが取得できていることが確認できます。

it = [name], fragments[[<em>あい</em>うえお <em>かき</em>くけこ]]
it = [name], fragments[[さしすせそ <em>たち</em>つてと]]

kotlinx.serializationつかってJSONのシリアライズ・デシリアライズしてみたよ

kotlinx.serialization を使って、JSONシリアライズとデシリアライズを試してみたよ。

シリアライズ&シリアライズ

JSONに対応するdata classを作成する

JSONに対応するdata classには、@Serializableを設定してあげます。ネストオブジェクトに対応するクラスにも同様に@Serializableが必要になります。

@Serializable
data class Test(
    val str: String,
    val int: Int,
    val double: Double,
    val nullable: String?,
    val defaultValue: String = "default",
    val lists: List<Nest>,
)

@Serializable
data class Nest(
    val value: String
)

シリアライズを試してみる

コード

Json.encodeToStringシリアライズが行えます。

val test = Test("str", 100, 9.9, null, "value", listOf(Nest("1"), Nest("2")))
val json = Json.encodeToString(test)
println("json = ${json}")

実行結果

ネストオブジェクト構造やnullもシリアライズできていることが確認できます。

json = {"str":"str","int":100,"double":9.9,"nullable":null,"defaultValue":"value","lists":[{"value":"1"},{"value":"2"}]}

シリアライズを試してみる

Json.decodeFromStringでデシリアライズが行なえます。JSONに対応するdata classは型パラメータとして指定してあげます。

コード

val test = Json.decodeFromString<Test>(
    """
        {
        "str": "str",
        "int": 100,
        "double": 99.99,
        "nullable": null,
        "lists": [
              {"value": "1"},
              {"value": "2"}
            ]
        }
    """.trimIndent()
)
println("test = ${test}")

実行結果

ネストオブジェクト構造もデシリアライズできていることが確認できます。 また、デフォルト値をしているdefaultValueプロパティは、JSON内にキーが存在していないため自動的にdefaultで指定されている値が設定されていることも確認できます。

test = Test(str=str, int=100, double=99.99, nullable=null, defaultValue=default, lists=[Nest(value=1), Nest(value=2)])

設定をカスタマイズしてみる

data classに未定義のキーが存在していた場合の動作を変更する

コード

ignoreUnknownKeystrueに設定することで、data class上に未定義のキーがJSONに存在していても無視して動作するようになります。 なお、この設定値をfalse(デフォルト値がfalse)にした場合は、実行時にkotlinx.serialization.json.internal.JsonDecodingExceptionが送出されます。 (例外メッセージの中で、ignoreUnknownKeystrueにしなよと親切に教えてくれます。)

@Serializable
data class Test(val value: String)

fun main() {
    val json = Json {
        ignoreUnknownKeys = true
    }
    val test = json.decodeFromString<Test>("""{"value": "a", "unknown": null}""")
    println("test = ${test}")
}

実行結果

data class上に存在しているキーのみデシリアライズされていることが確認できます。

test = Test(value=a)

JSONのキー名を指定する

コード

シリアライズ時のキー名は、@SerialNameで指定します。デシリアライズ時のキー名は@JsonNamesで指定します。

@Serializable
data class Test(
    @JsonNames("key") @SerialName("key") val value: String)

fun main() {
    val jsonString = Json.encodeToString(Test("ほげ"))
    println("json = ${jsonString}")
    val test = Json.decodeFromString<Test>(jsonString)
    println("test = ${test}")
}

実行結果

プロパティ名ではなくアノテーションで指定したキー名でやり取りできていることが確認できます。

json = {"key":"ほげ"}
test = Test(value=ほげ)

Date and Time APIのクラスを使ってみる

デフォルトでは、Date and Time APIのクラス群の変換には対応していないので、Serializerを作ることで対応してあげる必要があります。

Serializerの実装

KSerializer を実装して、シリアライズとデシリアライズの実装をしてあげます。 この例では、LocalDateを文字列変換してシリアライズし、デシリアライズではその逆を行っています。

object LocalDateSerializer: KSerializer<LocalDate> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): LocalDate {
        return LocalDate.parse(decoder.decodeString(), DateTimeFormatter.ISO_DATE)
    }

    override fun serialize(encoder: Encoder, value: LocalDate) {
        encoder.encodeString(value.format(DateTimeFormatter.ISO_DATE))
    }

}

シリアライズ・デシリアライズの実装

Serializerを、serializersModuleに登録してあげます。登録時には、変換対象のKClassとそれに対応するSerializer形式で登録します。

@Serializable
data class DateTime(
    @Contextual
    val date: LocalDate
)

fun main() {

    val json = Json {
        serializersModule = SerializersModule {
            contextual(LocalDate::class, LocalDateSerializer)
        }
    }
    val jsonString = json.encodeToString(DateTime(LocalDate.now()))
    println("jsonString = ${jsonString}")

    val dateTime = json.decodeFromString<DateTime>(jsonString)
    println("dateTime = ${dateTime}")
}

実行結果

自作したSerializerを使ったシリアライズとデシリアライズが動いていることが確認できますね。

jsonString = {"date":"2021-06-16"}
dateTime = DateTime(date=2021-06-16)

IntelliJ IDEA2021.1からGitのコミットメッセージテンプレートに対応したのが便利

現時点ではBeta版のIntelliJ IDEAの2021.1からGitのコミットメッセージテンプレートに対応してくれたのが便利ですね。 これを使うことでサードパーティ製のプラグインなど入れなくても、テンプレートを元にメッセージのプレフィックス的なもの入れたりするのも簡単にできますね。

使い方

使い方はgit config commit.templateを使ってテンプレートのメッセージを登録しておくだけですね。 私は、こんな感じに絵文字のリストをコメントとして登録しておいて、コミット時に必要な絵文字をコメントからコピって入力するようにしています。

#{id}

# :tada:        Initial commit
# :bookmark:    Version tag
# :sparkles:    New feature
# :bug:    Bugfix
# :card_index:    Metadata
# :books:    Documentation
# :bulb:    Documenting source code
# :racehorse:    Performance
# :lipstick:    Cosmetic
# :rotating_light:    Tests
# :white_check_mark:    Adding a test
# :heavy_check_mark:    Make a test pass
# :zap:    General update
# :art:    Improve format/structure
# :hammer:    Refactor code
# :fire:    Removing code/files
# :green_heart:    Continuous Integration
# :lock:    Security
# :arrow_up:    Upgrading dependencies
# :arrow_down:    Downgrading dependencies
# :shirt:    Lint
# :alien:    Translation
# :pencil:    Text
# :ambulance:    Critical hotfix
# :rocket:    Deploying stuff
# :apple:    Fixing on MacOS
# :penguin:    Fixing on Linux
# :checkered_flag:    Fixing on Windows
# :construction:    Work in progress
# :construction_worker:    Adding CI build system
# :chart_with_upwards_trend:    Analytics or tracking code
# :heavy_minus_sign:    Removing a dependency
# :heavy_plus_sign:    Adding a dependency
# :whale:    Docker
# :wrench:    Configuration files
# :package:    Package.json in JS
# :twisted_rightwards_arrows:    Merging branches
# :hankey:    Bad code / need improv.
# :rewind:    Reverting changes
# :boom:    Breaking changes
# :ok_hand:    Code review changes
# :wheelchair:    Accessibility
# :truck:    Move/rename repository
# :wastebasket: remove unnecessary files
# :memo: memo memo

テンプレートを設定後、IntelliJさん側でコミットをしようとするとこんな感じでテンプレートのメッセージが適用されるようになります。

f:id:sioiri:20210310094235p:plain

会社用のみテンプレートを分けたいケース

私の場合はghqを使っているので、会社用のリポジトリが特定のディレクトリ配下に集まっています。 なので、includeIfを使って特定ディレクトリ配下のみ特定のテンプレートを適用する設定を入れています。

.gitconfigの設定はこんな感じにしています。(xxxxは組織名が入る感じですね)

[includeIf "gitdir:~/src/github.com/xxxx/"]
  path = ~/.xxxx-gitconfig

~/.xxxx-gitconfigに、コミットメッセージのテンプレートを指定する感じになります。

[commit]
  template = ~/xxxxCommitTemplate.txt

これで、会社用リポジトリのみテンプレートが適用できるようになりますね。