自分の全ての GitHub リポジトリを横断的に検索・文字列置換する

はてなブログを HTTPS 配信に切り替えたので、はてなブログへのリンク URL が http:// から https:// に変わった。

自分は GitHub リポジトリの README.md などにはてなブログへのリンクを貼っているので、全ての GitHub リポジトリを横断的に検索し、http:// でリンクを記述している箇所を特定して https:// に置換したいと考えた。今日はその記録をお届けする。

目次

前提

Ubuntu 18.04 で作業した。grep や sed などの各種コマンドを使うが、MacOS でやる場合は GNU sed を使うよう注意。

全ての GitHub リポジトリの情報を取得する

自分のアカウントで持っている GitHub リポジトリの数は、現時点で106個。

GitHub API を使えばリポジトリ情報が色々と拾えるが、ページャ制限があり、1回の API コールで100件分までの情報しか取得できない。そのため、106個あるリポジトリの全情報を取得するには、ページャを移動してデータを取得し、それらをマージする必要があった。

それを行うのが以下のコード。

# リポジトリの情報を100件ずつ取得する
$ curl -s 'https://api.github.com/users/Neos21/repos?per_page=100&page=1' > repos-1.json
$ curl -s 'https://api.github.com/users/Neos21/repos?per_page=100&page=2' > repos-2.json

# jq を使ってマージする
$ jq -s add repos-1.json repos-2.json > repos.json

# 一時ファイルを消す
$ rm repos-1.json repos-2.json

コレで、repos.json に106個のリポジトリの情報が取得できた。GitHub リポジトリの情報は色々と有用なモノがあるので一部紹介しよう。

GitHub リポジトリの「ホームページ URL」を確認する

今回、はてなブログの URL を書きそうな場所として homepage 部分もありそうなので、以下のように検索しておこう。

$ jq -r 'map(select(.homepage | startswith("http://neos21"))) | map(.clone_url)[]' repos.json

.homepagehttp://neos21 から始まる要素をフィルタし、その要素の .clone_url を出力した。コレでいくつかヒットしたので、出力された URL にアクセスして「ホームページ URL」を書き換えた。

リポジトリ内のファイルを grep し置換する

続いてリポジトリ内のファイルを編集していく。GitHub API だけで各リポジトリを参照、編集したりするのは大変だし、置換が失敗していたりするのに気付かなかったりするので、一旦全リポジトリをローカルに git clone し、一括置換してやろうと思う。

一括 git clone する

というワケで、まずは全リポジトリをローカルに git clone するためのコマンドを作っていこう。

$ jq -r 'map("git clone --depth 1 " + .clone_url + "  # " + (.fork|tostring))[]' repos.json

# 出力例
git clone --depth 1 https://github.com/Neos21/hoge.git  # false
git clone --depth 1 https://github.com/Neos21/fuga.git  # false

例えばこんな風にすると、git clone コマンドの形で全リポジトリの情報を出力できる。フォークしたリポジトリについては扱いを別にしようかな、とか思ったので、コメントで .fork の情報を出力してある。一旦コレをテキストエディタにコピペし、明らかに git clone しなくても良さそうなリポジトリは除外している。

微調整が終わったら、そのファイルに Shebang #!/bin/bash を書き足し、そのまま Bash スクリプトとして保存して実行してしまおう。どんどん git clone されていく。

なお、コマンドに付与した --depth 1 とは、取得するコミットを直近のモノだけにするオプションで、コレによりリポジトリ全体をダウンロードせずに済むので、サクサクダウンロードできるだろう。

grep 結果に対し一括 sed 置換する

全リポジトリをローカルに落とせたら、次のようなコマンドを組み立てる。

$ grep --exclude-dir='.git' --exclude-dir='node_modules' --exclude='*.ttf' -inr 'http://neos21' ./

grep--exclude で拡張子除外、--exclude-dir でディレクトリ除外を指定している。この辺は各自のリポジトリの傾向を見てお好みで。

-i は検索文字列の大文字・小文字を無視。-n はファイル名と行番号を表示する。-r で指定ディレクトリの配下を再帰的に検索する。

このようなコマンドで、まずは置換したい対象を把握しておこう。バイナリファイルなどがヒットしたり、Linux 環境でキレイに置換できなさそうな .bat ファイルなどは目視で確認しておく。

置換対象が確認できたら、コマンドを次のように直して、実際に置換をする。

$ grep --exclude-dir='.git' --exclude-dir='node_modules' --exclude='*.ttf' -ilr 'http://neos21' ./ | xargs sed -i -e 's@http://neos21@https://neos21@g'

変更したのは、-inr と書いていたオプション部分を -ilr とした。-l オプションは、ヒットしたファイル名のみを出力するモノだ。

対象のファイル名のみを取得し、xargs と GNU sed を使って、文字列置換を実施している。-i (--in-place) オプションで元ファイルに上書きをするが、-e より手前に、分けて書くこと。-e -i 'コマンド' のように書くとコマンドが失敗する。

$ grep -ilr 'HOGE' ./ | xargs sed -i -e 's/HOGE/FUGA/g'

↑ コレをイディオムとして暗記してしまおう。

自分のブログ URL にのみヒットするように、http://neos21 を検索文字列とし、コレを https://neos21 と置換しているワケだ。

置換が終わったら、再度 grep のみを実施してみて、キレイに置換できていないところや、手作業で直した方が良い箇所を見つけて手直しする。

コミットとプッシュは面倒だが手作業でやると良いだろう。次のようなコマンドを作っても良い。

# 各リポジトリのディレクトリに移動したら、次のコマンドをコピペ実行する
$ git add . && git commit -m "Update URL" && git push && cd ../

コレで置換完了

というワケで置換完了。リポジトリ数が増えてきて大変なのでもうやりたくないが、自分の作業をこうやって自動化・効率化できるのは楽しい。