Bitbucket API を使って複数のリポジトリからファイルをダウンロードしてきて比較してみる
Bitbucket という、GitHub ライクでプライベートリポジトリが持てるホスティングサービスを使っている。
この Bitbucket にも API が用意されていて、今回やりたかったことに使えそうだったので、試しにやってみた。
目次
Bitbucket API
Bitbucket API はバージョン1系とバージョン2系があるらしいのだが、自分の環境は諸事情によりバージョン1系を使っているので、以下あたりの説明を見ていた。
コレによると、複数のリポジトリをまとめた「プロジェクト」は以下のような URL で取得できる。
http://my-bitbucket.com/rest/api/1.0/projects
この要領でリポジトリを絞り込み、さらにそのリポジトリ内を参照できるようだ。
http://my-bitbucket.com/rest/api/1.0/projects/my_project/repos/my_repository
http://my-bitbucket.com/rest/api/1.0/projects/my_project/repos/my_repository/branches
こんな感じ。凄い。
リポジトリからファイルを取得する
今回、ワケあって複数のリポジトリにある package.json
を比較調査したかったのだが、全てのリポジトリを git clone
してきて、package.json
だけコピーして作業フォルダに置いて…といった作業が面倒くさかった。
そこで、この Bitbucket API を利用して、各リポジトリからファイルを取得してみようと思う。
リポジトリ内のファイルを参照するには、以下のような URL になる。
http://my-bitbucket.com/rest/api/1.0/projects/my_project/repos/my_repository/browse/package.json
コレだと master ブランチの最新のファイルしか取れないが、at
パラメータでコミット ID かブランチ名を指定すれば、過去のファイルや別ブランチのファイルも取得できる。
http://my-bitbucket.com/rest/api/1.0/projects/my_project/repos/my_repository/browse/package.json?at=97f57fe3c2b58749180415b634069dd5c78d606d
http://my-bitbucket.com/rest/api/1.0/projects/my_project/repos/my_repository/browse/package.json?at=refs%2Fheads%2Fmaster
今回 package.json
は、リポジトリの直下にあったが、もし子ディレクトリにあるファイルを参照する場合は、…/browse/src/app/some-file.md
というように、スラッシュ /
を URL にそのまま付与して掘り下げられる。
at
パラメータでブランチ名を指定する場合は、refs/heads/
を先頭に付け、記号などは encodeURIComponent()
でエンコードしておく必要がある。だから /
は %2F
に変換され、refs%2Fheads%2Fmaster
となっている。
試しに curl
でデータを取得してみる。Basic 認証は -u
オプションで行えば良い。取得した JSON は lines
プロパティに当該ファイルの行ごとのデータが入っているので、コレを jq
でパースする。
$ curl -u '【ユーザ名】:【パスワード】' 'http://my-bitbucket.com/rest/api/1.0/projects/my_project/repos/my_repository/browse/package.json?at=refs%2Fheads%2Fmaster' | jq -r '.lines[].text'
よきよき。
複数のリポジトリからファイルを取得して保存・整形する
Bitbucket API の使い方が分かったので、あとはリポジトリごとの情報を控えておき、Bitbucket API を順に叩いては対象ファイルを書き出す Node.js スクリプトを書いてみる。
Node.js 組み込みの http
モジュールを使っても良いけど、request
というパッケージが便利なので、コレを使ってみた。
const request = require('request');
const fs = require('fs');
const os = require('os');
// 設定事項
const username = '【ユーザ名】';
const password = '【パスワード】';
const bitbucketURL = 'http://my-bitbucket.com';
// Basic 認証文字列
const basicAuth = `Basic ${new Buffer.from(`${username}:${password}`).toString('base64')}`;
// 取得したいデータを一覧にする
const dataList = [
{ project: 'my_project' , repository: 'my_repository' , branch: 'master' , filePath: 'package.json' },
{ project: 'second_project', repository: 'second_repository', branch: 'develop' , filePath: 'package.json' },
{ project: 'other_project' , repository: 'other_repository' , branch: 'feat/other', filePath: 'package.json' }
];
// 出力先ディレクトリ
const destDir = './package-json-files';
// ディレクトリを作る
if(!fs.existsSync(destDir)) {
fs.mkdirSync(destDir);
}
// 順に取得 → ファイル保存を行う
dataList.reduce((promiseChain, current, index) => {
return promiseChain
.then(() => {
return getFile(current.project, current.repository, current.branch, current.filePath);
})
.then((result) => {
// ファイル名を作る : 今回は JSON ファイルを扱うのでココだけ拡張子ベタ書き…
// ファイル終端の改行がなくなるので、os.EOL を使って改行コードを入れてみた
const num = `0${index + 1}`.slice(-2);
fs.writeFileSync(`${destDir}/${num}-${current.repository}.json`, `${result.fileData}${os.EOL}`);
})
.catch((error) => {
console.error('Fail', error);
});
}, Promise.resolve())
.then(() => {
console.log('Finished');
});
/**
* Bitbucket より指定のファイルを取得する
*
* @param project プロジェクト名
* @param repoository リポジトリ名
* @param branch ブランチ名
* @param filePath ファイルまでのフルパス
* @return repository・branch・filePath および fileData プロパティとして取得したファイルの中身を返す
*/
function getFile(project, repository, branch, filePath) {
return new Promise((resolve, reject) => {
// at オプションでブランチを指定する・at オプションに渡す文字列は URI エンコード ('/' → '%2F' など) する
request.get(`${bitbucketURL}/rest/api/1.0/projects/${project}/repos/${repository}/browse/${filePath}?at=${encodeURIComponent(`refs/heads/${branch}`)}`, {
headers: {
Authorization: basicAuth
}
}, (error, _response, body) => {
if(error) {
console.error('Request Error', error);
return reject(error);
}
const parsedBody = JSON.parse(body);
// API からエラーが返ってきている場合はココで判定する
if(parsedBody.errors) {
console.error('Bad Request', parsedBody.errors);
return reject(parsedBody.errors);
}
// lines プロパティの中身のみをまとめ直す
const fileData = parsedBody.lines.map((line) => {
return line.text;
}).join('\n');
resolve({
repository: repository,
branch: branch,
filePath: filePath,
fileData: fileData
});
});
});
}
定数 dataList
に、リポジトリとブランチ名、ファイルパスを列挙していく。正常に動かせれば、./package-json-files/
ディレクトリ配下に
01-my_repository.json
02-second_repository.json
03-other_repository.json
という名前で、各リポジトリの package.json
が出力されていることだろう。
request
パッケージで Basic 認証を行う方法は以下を参考にした。
headers
に Authorization
というキーを用意し、
const basicAuth = `Basic ${new Buffer.from(`${username}:${password}`).toString('base64')}`;
こんな書式で認証用の文字列を用意する。
ファイルを Bitbucket API から取得すると、ファイル終端の改行コードがなくなるので、os.EOL
を使ってファイル書き出し時に改行コードを付与してみた。この辺雑だけど動いてるからいいや…。
コレでファイルをかき集めることができたので、あとはよしなに整形して利用すれば良い。Bitbucket API 便利!