しおメモ

雑多な技術系ブログです。ニッチな内容が多いです。

git push前にrebaseをリマインドする

あとからコミットメッセージ書きたいとか、rebase前提でとりあえず細かくcommit切りたいときに、雑にtempとかWIPとか書いて忘れそうになるので、hookspre-pushに警告をいれてみることにしました。

やり方

master pushを防ぐのとほぼ同じです。

.git/hooks/pre-pushに書いておくことで、それっぽいコミットメッセージに反応して、pushを中断します。

while read local_ref local_sha1 remote_ref remote_sha1
do
  MESSAGES=`git log --oneline origin/${remote_ref##refs/heads/}..HEAD | awk '{print $2}'`
  if [[ $MESSAGES =~ (WIP|wip|temp|tmp) ]]; then
    echo "💪rebaseしろよな!"
    exit 1
  fi
done

とりあえず、自分を律するスタイルでいってみます。

ターミナルからワンライナーでGitHubのもろもろを開く

ターミナルからGitHubのもろもろを開く

git管理下のディレクトリにいる時に、もろもろのよく使うページを開くワンライナーでエイリアスを作ってみました。

はじめに

説明に便利なので、ブラウザの立ち上げにmacのopenコマンドをつかっていますが、他のOSの場合、適宜直接Chromeに渡したりする必要があります。

originを開く

基本形です。

alias ogr='git remote get-url origin | sed -e "s#:#/#" -e "s#git@#https://#" -e "s#\.git$##" | xargs open'

SSHの場合が多いので、sedで置換してブラウザで開けるアドレスに変えます。

issueを立てる

最後の.git/issues/new/にする。

alias ogri='git remote get-url origin | sed -e "s#:#/#" -e "s#git@#https://#" -e "s/\.git$/\/issues\/new\/" | xargs open'

Pull Requestを立てる

引数をみてそのブランチと現在のブランチの差分のページに飛ばします。(ex. /compare/master...develop)
現在のブランチはgit rev-parse --abbrev-ref HEAD 2>/dev/nullで確認します。

ogrp(){ git remote get-url origin | sed -e "s#:#/#" -e "s#git@#https://#" -e "s/\.git$//" | xargs -I@ open @/compare/$1...`git rev-parse --abbrev-ref HEAD 2>/dev/null` }

実際に使っているzshの無名関数版はこちらです。

alias ogrp='(){git remote get-url origin | sed -e "s#:#/#" -e "s#git@#https://#" -e "s/\.git$//" | xargs -I@ open @/compare/$1...`git rev-parse --abbrev-ref HEAD 2>/dev/null`}'

割と便利です。(個人の感想(´・ω・`))

UIView.animateをメソッドチェーンで書く

UIView.animateは引数が多く、animationcompletionで2つクロージャーを引数に取り、ネストも深くなりやすいため、メソッドチェーンで書けるように改良してみました。

サンプル

f:id:scior:20190430230201g:plain

ViewAnimator.start()
    .delay(1.0)
    .duration(0.8)
    .animate {
        self.subview.transform = self.subview.transform.translatedBy(x: 0, y: 200.0)
    }
    .duration(0.8)
    .animate {
        self.subview.transform = self.subview.transform.translatedBy(x: 200.0, y: 0)
    }
    .duration(0.8)
    .animate {
        self.subview.transform = self.subview.transform.translatedBy(x: 0, y: -200.0)
    }
    .duration(0.8)
    .animate {
        self.subview.transform = self.subview.transform.translatedBy(x: -200.0, y: 0)
    }
    .resolve()

実装

難しいことはせず、Builderパターンと再帰を利用しています。

AnimationBuilder

アニメーションのパラメーターを拾うBuilderです。

typealias AnimationHandler = () -> Void
typealias ResolvingAnimation = (@escaping AnimationHandler) -> Void

final class AnimationBuilder {
    var delay: TimeInterval? = nil // (1)
    var options: UIView.AnimationOptions? = nil // (1)
    let resolvingAnimation: ResolvingAnimation? // (2)

    init(resolvingAnimation: ResolvingAnimation?) {
        self.resolvingAnimation = resolvingAnimation
    }

    func delay(_ value: TimeInterval) -> AnimationBuilder {
        delay = value
        return self
    }
    func duration(_ value: TimeInterval) -> AnimationExecutor { // (3)
        return AnimationExecutor(resolvingAnimation: resolvingAnimation, duration: value, delay: delay, options: options)
    }
    func options(_ value: UIView.AnimationOptions) -> AnimationBuilder {
        options = value
        return self
    }
    func resolve() { // (4)
        resolvingAnimation?({})
    }
}
  1. UiView.animateのオプショナルな設定値の例として、delayをプロパティとしています。
  2. 再帰的に、completionハンドラを解決するため、途中までのアニメーションのブロックを(() -> Void) -> Voidで保持しています。
  3. durationは必須の値なので、こちらが終端でAnimationExecutorをbuildするようなインターフェースにしました。
  4. 最後にresolveを呼ぶとアニメーションを実行します。

AnimationExecutor

メソッドチェーンを実現するため、Builderのdurationの直後のみanimate出来るようにしています。

final class AnimationExecutor {
    let resolvingAnimation: ResolvingAnimation?
    let delay: TimeInterval?
    let duration: TimeInterval
    var options: UIView.AnimationOptions? = nil

    init(resolvingAnimation: ResolvingAnimation?, duration: TimeInterval, delay: TimeInterval? = nil, options: UIView.AnimationOptions?) {
        self.resolvingAnimation = resolvingAnimation
        self.duration = duration
        self.delay = delay
        self.options = options
    }

    func animate(handler: @escaping AnimationHandler) -> AnimationBuilder {
        let resolving: (@escaping AnimationHandler) -> Void = { [duration, delay, options] completion in // (1)
            let animation = { UIView.animate(
                withDuration: duration,
                delay: delay ?? TimeInterval.zero,
                options: options ?? [],
                animations: handler,
                completion: { _ in completion() }
            )}

            if let resolvingAnimation = self.resolvingAnimation {
                resolvingAnimation(animation)
            } else {
                animation() // (2)
            }
        }
        return AnimationBuilder(resolvingAnimation: resolving)
    }
}
  1. 弱参照では解放されてしまうため、強参照と値キャプチャを利用します。
  2. 最初はresolvingAnimationnilなので、単純にブロックを実行します。

アニメーション開始用のstaticメソッド

アニメーション開始用にstatic factoryを用意します。

final class ViewAnimator {
    static func start() -> AnimationBuilder {
        return AnimationBuilder(resolvingAnimation: nil)
    }
}

サンプルソース

こちらです。