9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Gaiax GroupAdvent Calendar 2018

Day 6

GASでGitHubにある放置されたままのブランチを通知する

Posted at

この記事の内容

GASを利用して、GitHubから放置されたままのブランチを探してSlackへ通知を行う実装を行いました
その過程と知見とかをまとめてます
完成版のコードは後半にまとめてますのでそちらを参照してください

はじめに

最近になってGitHubのbranchesというURLがあることに気がつきました
開発をしていると何となく実装してpushはしたけどPRできずに腐っているブランチというのはいくつかあると思います
以下はVuejsのリポジトリのbrachesですが、このようにactiveなものslateなものなどが可視化されていてとてもわかりやすいです

  • Active: 最後のコミットが最初にあるブランチから順に、過去3か月以内にコミットしたすべてのブランチが表示される
  • Slate: 過去3カ月以内に誰もコミットしていないすべてのブランチが表示される
    2018-12-07-050704_1025x735_scrot.png

see: Viewing branches in your repository - User Documentation

腐っているブランチがあるかどうかはここを見にいけばよいのですが、どうせならSlackに通知してほしい
ぱっと探した感じ、GitHubではそれを通知するような機能は提供していなかったようなので、どうせならGASの入門ついでに通知Botを作ってみようということになりました

やったこと

GASでSlackにメッセージを送信する

【初心者向け】GASを使ってSlackへ自動通知 - Qiita を参考に実装しました
詳しいことはリンク先にあるので参照してください

GitHubからブランチの情報を取得してみる

github の grachqlを使ってデータを取得します

今回取得したいデータは、リポジトリにあるすべてのブランチとそれに紐づいた最後のコミット、さらに誰がコミットしたのか分かれば十分です

GraphQL API Explorer | GitHub Developer Guide でqueryを試すことができます!便利!!
ドキュメントはこちら GitHub GraphQL API v4 | GitHub Developer Guide

queryは以下のような感じになりました

query { 
  repository(owner: "Teamup-inc", name: "teamup360") { 
    name,
    refs(first: 10, refPrefix:"refs/heads/") {
      nodes {
        name
        target {
          ... on Commit {
            history(first: 1) {
              nodes {
                messageHeadline
                committedDate
                url
                author {
                  name
                }
              }
            }
          }
        }
      }
    }
  }
}

branchesがrefsになってますね、GitObjectに寄った名前になってるんですね
branchのすぐ下にcommitsがあるのかな?と思って試行錯誤していたので結構つまりました...
targetがあって ... on Commit でアクセスして、そこのhistoryを参照するという結構面倒な感じでした
(... on Commit って最初 ...なので何か省略しているのかな?と思い込んでスルーしてエラーになったのはないしょ)

その他は以下を参考に実装
Google Apps ScriptでGithub GraphQL API v4を叩く - Qiita
Branches on Repository - GraphQL API - GitHub Platform Forum
GitHub GraphQL repo commit history query

ブランチの情報からSlateなものを絞り込む

取得したデータのCommitDateを参照して3ヶ月以上経過したブランチを判別してみます

データ構造.js
{
  "name": "branch_name",
  "target": {
    "history": {
      "nodes": [
        {
          "messageHeadline": "コミットメッセージ",
          "committedDate": "2018-04-03T08:18:10Z",
          "url": "コミットのURL",
          "author": {
            "name": "Author Name"
          }
        }
      ]
    }
  }
}

3ヶ月以上経過したかどうかはcommitedDateを参照すれば判別できそうです
ただ、jsで日付の比較を行うのは面倒です...
Moment.js使えないのかな?と思って調べたところ普通に外部ライブラリとして存在していたので導入してみました
日付&時刻の便利ライブラリ「Moment.js」をGoogle Apps Scriptで使う方法

ざっくりとこんな感じのコードで判定はできそうです

判別.js
  this.formatBranch = function(branch){
    const commits = branch.target.history.nodes
    return {
      name: branch.name,
      lastCommit: commits[0]
    }
  };
  this.slateFilter = function(branch) {
    // 最後のコミットから3ヶ月以上経過しているもの
    // を取得する
    const moment = Moment.moment
    const today = moment()
    const commitDate = moment(branch.lastCommit.committedDate)
    const elapsedDate = today.diff(commitDate, 'days')
    return elapsedDate > 30 * 3
  }

Slackのメッセージを加工する

最後に取得したSlateなブランチをSlackへいい感じに通知しましょう
Slackへはattachmentsを利用することで結構リッチな通知を作成することが可能です
詳しくは以下を参照
SlackのIncoming Webhooksを使い倒す - Qiita
Message Formatting | Slack

環境変数

この手のコードを書くときに困るのがToken情報をハードコーディングしたくない問題です
一般的には環境変数が設定できるので、問題ないのですが、GASには環境変数ってあるの?ってなりました
適当にポチポチ探していたらスクリプトプロパティという項目を発見したので、調べてみるとまさに環境変数でした

【初心者向けGAS】スクリプトプロパティを操作してそのデータを取り出す方法 こちらを参考にToken情報を環境変数に設定してほぼ完了です

完成したもの

こんな感じで通知されます
2018-12-07-050704_1025x735_scrot.png

このスクリプトの実行頻度は週に1度くらいでよさそうなので、トリガーを設定します
GoogleAppsScriptのトリガーを使って自動化をマスターしよう | えびせん

おしまい

完成版のコード

シンタックスハイライトの問題で拡張子をjsにしているので書き写すときは .gsへ変更してください

main.js
function main() {
  const slateBranches = new SlateBranches(PropertiesService.getScriptProperties().getProperty('GitHubAccessToken')).list()
  const slackNotify = new SlackNotify(PropertiesService.getScriptProperties().getProperty('SlackWebhookUrl'))
  const attachments = slateBranches.map(buildAttachment)
  slackNotify.post(attachments)
}

function buildAttachment(branch) {
  const commit = branch.lastCommit
  const author = commit.author
  return {
    "color": "#cc3344",
    "author_name": author.name,
    "title": branch.name,
    "title_link": 'https://github.com/:owner/:repo/compare/' + branch.name,
    "text": commit.messageHeadline,
    "footer": "GitHub last commit:",
    "footer_icon": "https://assets-cdn.github.com/favicon.ico",
    "ts": Moment.moment(commit.committedDate).unix()
  }
}
SlackNotify.js
function SlackNotify(postUrl) {
  this.postUrl = postUrl
  this.username = 'GASBot'
  this.icon = ':robot_face:'
  this._buildOptions = function(payload) {
    const options = {
      method: "post",
      contentType: "application/json",
      payload: payload
    }
    return options
  }
  this.post = function(message) {
    const isAttachmens = Array.isArray(message)
    const payload = JSON.stringify({
      username: this.username,
      icon_emoji: this.icon,
      text: isAttachmens ? '' : message,
      attachments: isAttachmens ? message : []
    })
    const options = this._buildOptions(payload)
    UrlFetchApp.fetch(this.postUrl, options)
  }
}
SlateBranches.js
function SlateBranches(token) {
  this.token = token
  this.endpoint = "https://api.github.com/graphql"
  this.query = 'query { \
    repository(owner: ":owner", name: ":repo") { \
      name, \
      refs(first: 10, refPrefix:"refs/heads/") { \
        nodes { \
          name \
          target { \
            ... on Commit { \
              history(first: 1) { \
                nodes { \
                  oid \
                  messageHeadline \
                  committedDate \
                  url \
                  author { \
                    name \
                  } \
                } \
              } \
            } \
          } \
        } \
      } \
    } \
  }'
  this.list = function() {
    const slateBanches = this._getBranches().
      map(this._formatBranch).
      filter(this._slateFilter)
    return slateBanches
  }
  this._buildRequestOption = function() {
    return {
      method: "post",
      contentType: "application/json",
      headers: {
        Authorization: 'bearer ' + this.token,
      },
      payload: JSON.stringify({ query: this.query }),
    }
  }
  this._getBranches = function(){
    const option = this._buildRequestOption();
    const res = UrlFetchApp.fetch(this.endpoint, option)
    const json = JSON.parse(res.getContentText())
    return json.data.repository.refs.nodes
  };
  this._formatBranch = function(branch){
    const commits = branch.target.history.nodes
    return {
      name: branch.name,
      lastCommit: commits[0]
    }
  };
  this._slateFilter = function(branch) {
    // 最後のコミットから3ヶ月以上経過しているもの
    // を取得する
    const moment = Moment.moment
    const today = moment()
    const commitDate = moment(branch.lastCommit.committedDate)
    const elapsedDate = today.diff(commitDate, 'days')
    return elapsedDate > 30 * 3
  }
}

その他の参考リンク:

9
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?