2017年 11月の投稿を表示しています

ExtractContent を PHP で書き換えた

いや Ruby でええやん、 Python でええやん、みたいな話だとは思うのだが、やっぱり PHP でやりたいよね、という一定の需要がギョームで発生してしまったので、 Ruby のコードを見ながら PHP に書き換えた。 Packagist にも登録してあるので composer からどうぞ。

sters/extract-content - Packagist

$url = 'http://labs.cybozu.co.jp/blog/nakatani/2007/09/web_1.html';
$extractor = new \ExtractContent\ExtractContent(file_get_contents($url));
$result = $extractor->analyse();
file_put_contents(__DIR__ . '/result', $result);

// // 抽出結果
// Webページの自動カテゴライズ の続き。
// 前回書いたとおり、パストラックで行っている Web ページのカテゴライズでは、Web ページの本文抽出がひとつの鍵になっています。今回はその本文抽出モジュールを公開しつつ、使っている技法をざっくり解説などしてみます。
// 本モジュールの利用は至極簡単。require して analyse メソッドに解析したい html を与えるだけ。文字コードは UTF-8 です。
// ...

ExtractContent は Cybozu の Nakatani Shuyo さんが2009年に作成したもので、正規表現を主として Web 記事上の本文に関するちょっとの知見が加わることで成り立っている。以下の記事へ。

Webページの本文抽出 (nakatani @ cybozu labs)

今回、移す際に参考にしたのは、もともとのものではなく、Ruby 1.9 に対応したソース。というのもこっちの記事を見つけたのは後で、 Github 上で ExtractContent を先に見つけていたので、まあいっか、と。

mono0x/extractcontent: ExtractContent for Ruby 1.9+

Ruby はチョットヨメルので inject とかわからないメソッドだけドキュメント見つつ脳内補完して、同じような感じの処理になるよう PHP へ書き直した。工夫したところはとくになく、クラスで扱うようにした、チョットテスト書いたくらいで、ほぼそのまま移してきた。Wikipedia と Medium、はてなブログあたりで試してみたところで、おおよそうまくいっているように見えたので、たぶん大丈夫。

ただ、試していて、本文っぽいと判断されるのが 2 つ以上あるような 1 記事ページ(例えば記事中に section タグがあってーとか、そいういう)ではうまく取り出せず、スコアが高くなったほうのみ抽出されてしまう。本文しきい値のようなものを設けてそれを超えていたら、結合して出す、とかしないといけないなあと思う。とはいえ、正確に本文が欲しいのか、その記事中の重要な部分にフォーカスするのか、などなど要件にもよるので、とりあえずはいいんじゃないかなの気持ち。

ちなみに PHP による実装もいたのだが、おそらく上記のようなところで、元々のものには無いオプションが増えていたりでチョットわからなかったので、一から書いた次第。

aoiaoi/ExtractContent: extract content from HTML

なお記事解析、本文抽出について Packagist を調べると他にも 3 つのライブラリが出てくる。ざっくり紹介するとこんな感じ。

今回 PHP に書き換えてみた ExtractContent も含めて、これらの比較は次の記事でやりたい。

faker を使ってダミーデータを生成する

これこれ、このライブラリ。

fzaninotto/Faker: Faker is a PHP library that generates fake data for you

ざっくり翻訳しつつ、日本語データでの使い方へ。

faker とは

faker は PHP のライブラリで、偽のデータを提供する。データベースの初期化や XML ドキュメントの生成、ストレステスト、本番データの匿名化、などに活用できる。faker は Perl の Data::Faker と Ruby の Faker に影響を受けている。

faker の使い方

インストール方法

Pakagist にて提供されているので composer を使ってインストールできる。

composer require fzaninotto/faker

基本的な使い方

$faker = \Faker\Factory::create();
// faker オブジェクトの生成

echo $faker->name;
// 名前。例えば 'Lucy Cechtelar'

echo $faker->address;
// 住所。例えば
// "426 Jordy Lodge
// Cartwrightshire, SC 88120-6700"

echo $faker->text;
// 文章…というよりは Lorem 。例えば。
// Dolores sit sint laboriosam dolorem culpa et autem. Beatae nam sunt fugit
// et sit et mollitia sed.
// Fuga deserunt tempora facere magni omnis. Omnis quia temporibus laudantium
// sit minima sint.

faker には嬉しいことに、日本語を含む色々な言語のダミーデータをサポートしている。ここでは日本語での扱いを試してみる。

// 引数に言語を指定すると利用してくれる
$faker = \Faker\Factory::create('ja_JP');

echo $faker->name;
// 例えば '大垣 直子'

echo $faker->address;
// 例えば '2636573  滋賀県浜田市西区笹田町渡辺1-1-10'

echo $faker->realText;
// text は Lorem なので realText がある
// 例えば 'オン燈と、もうすっかりがきの音にすきっと思いなやさで伝つたわ」「なんだ」カムパネルラはこち見ていたの...'

どういうプロパティが用意されているか

英語プロパティは README に全てあって、n - m の範囲で乱数とか、「Dr.」などの敬称を使えるとか、日時とか、まあなんか色々とあるのでそっちを見ると良さそう。

Faker/readme.md at master · fzaninotto/Faker

日本語のだけ、ソースとにらめっこしないと分からないところがあったので、取り上げる。(網羅できてないかも)

country 国名
prefecture 都道府県
city
ward
streetAddress 町以下
postcode 郵便番号
secondaryAddress マンション名
company 会社名
userName ユーザ名
domainName ドメイン名
email メールアドレス
name 名前(姓 + 名)
lastName
firstName
firstNameMale 名(男性)
firstNameFeMale 名(女性)
kanaName 名前カナ
lastKanaName 姓カナ
firstKanaName 名カナ
firstKanaNameMale 名カナ(男性)
firstKanaNameFemale 名カナ(女性)
phoneNumber 電話番号
realText 日本語文章

ORMとの連携

faker は CakePHP や Laravel の ORM と連携することができる(!?)
試してみたところ、連携というよりは ORM を通して、実際に DB に値を格納してくれるっぽい。

例えば CakePHP3 で使うならこんなふうに。

$faker = \Faker\Factory::create('ja_JP');
$populator = new \Faker\ORM\CakePHP\Populator($faker);
$populator->addEntity('Users', 5, [
    'name'       => function() use ($faker) { return $faker->name; },
    'prefecture' => function() use ($faker) { return $faker->prefecture; },
    'created'    => null,
    'modified'   => null,
]);
$inserted = $populator->execute();

$Users = \Cake\ORM\TableRegistry::get('Users');
debug($Users->find('all')->toList());
// 例としてこのような出力になる
// [
//     (int) 0 => object(App\Model\Entity\User) {
//         'id' => (int) 1,
//         'name' => '津田 裕樹',
//         'prefecture' => '栃木県',
//         ....
//         'created' => object(Cake\I18n\FrozenTime) {
//             'time' => '2017-11-20T13:47:32+09:00',
//             'timezone' => 'Asia/Tokyo',
//             'fixedNowTime' => false
//         },
//         'modified' => object(Cake\I18n\FrozenTime) {
//             'time' => '2017-11-20T13:47:32+09:00',
//             'timezone' => 'Asia/Tokyo',
//             'fixedNowTime' => false
//         },
//     },
//     ...
// ] 

特に指定をしなかったカラムについては DB の形を見て、結構いい感じに適当に faker な値で埋めてくれる。指定はクロージャで、 faker を使ってランダムなデータを作ることも出来るし、固定値にすることもできる。また、例としては 1 エンティティしか作らなかったが、addEntity を複数呼んで、 まとめて execute することで複数のエンティティも作ることができる。

シード値を設定する

テストで使うときやっぱりランダムだとつらいよね、というときのためにシード値を設定することができる。

$faker = \Faker\Factory::create();
$faker->seed(1234);

echo $faker->name;
// 何度コードを流しても以下の順番で流れる
// 'Miss Lorna Dibbert'
// 'Litzy Emard'
// 'Odessa Collins'
// ...

ランダムに提供される部分はそのとおりなのだが、日付については引数を指定しないと now() な値が使われるのでシードは同じでも出力される値が変わってしまうので、なんでもいいが毎回同じ値を入れる必要がある。

$faker = \Faker\Factory::create();
$faker->seed(1234);
echo $faker->dateTime();
// ランダムになる

$faker = \Faker\Factory::create();
$faker->seed(1234);
echo $faker->dateTime('2017/01/01 12:00:00');
// 固定パターンになる

faker には乱数を提供する関数もあり、シード値を設定することで、こういったものたちも固定パターン化される。便利ちゃん。

$faker = \Faker\Factory::create();
$faker->seed(1234);
echo $faker->numberBetween(0,100);
// 固定パターンになる
// 81, 50, 62, 18, 56, ...

faker の内部、プロバイダの話

何の気無しに \Faker\Factory::create() を呼び出していたが、実は内部でこんなような処理をしている。

$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\en_US\Person($faker));
$faker->addProvider(new \Faker\Provider\en_US\Address($faker));
$faker->addProvider(new \Faker\Provider\en_US\PhoneNumber($faker));
$faker->addProvider(new \Faker\Provider\en_US\Company($faker));
$faker->addProvider(new \Faker\Provider\Lorem($faker));
$faker->addProvider(new \Faker\Provider\Internet($faker));

※実際のコードはこのあたり。
Faker/Factory.php at master · fzaninotto/Faker

プロバイダの土台として \Faker\Provider\Base がいるので、これを継承した適当なクラスを作り、 public なメソッドを作って return すれば良い。そのプロバイダクラスを new して addProvider すれば使えるようになる。

class Book extends \Faker\Provider\Base
{
  public function title($nbWords = 5)
  {
    $sentence = $this->generator->sentence($nbWords);
    return substr($sentence, 0, strlen($sentence) - 1);
  }

  public function ISBN()
  {
    return $this->generator->ean13();
  }
}


$faker->addProvider(new Book($faker));

echo $faker->ISBN;

faker を使ってみて

説明にもあるが、テストコードにおいて、いや、そこはなんでも良いんだけど、それっぽい住所や名前をよろしく入力して欲しい、みたいな時。あるいは、DBの初期化時に、多いに力を発揮しそうな感触。

テストコードとして利用する場合、値が毎回ランダムになって使い所がイマイチわかりにくい気もちょっとするが、それで詰まるときってそんなに無いのでは…。値が被って~~~うあああ!!!!みたいなことはあると思う。

話はそれるが最近見かけた property-based testing という方法にはめちゃんこ合っていると思う。

Property-Based Testing for Godly Tests

そもそも PHPUnit なら Data Provider の仕組みがあるし、あるいは CakePHP なら Fixture が、 Laravel には Factory の仕組みがそれぞれにある(きっと他のフレームワークにもあるんじゃないかな、調べてない。。)ので、そういうこともやりやすいと思う。
ちなみに Laravel には faker が既に含まれていて、 Factory の仕組みを使うとガンガンにテストデータを作ってくれる。スゴイ。

Database Testing - Laravel - The PHP Framework For Web Artisans

ORMとの連携はできるとはいえ、既に書いたとおり、各フレームワークがそういう仕組みを提供しているので、それとバッティングするなあという気持ちも。その中で使っていく、みたいなイメージなのかな。。ちょっとわからないや。。

CakePHP3 で論理削除をする

CakePHP3 ではデフォルトでは論理削除の機能は実装されていない。調べると1番上にいい感じに利用できるプラグインがあるので、これを利用するとよい。

PGBI/cakephp3-soft-delete

とりあえず PGBI/cakephp3-soft-delete を使ってみる

composer でパッケージを追加して。

$ composer require pgbi/cakephp3-soft-delete "~1.0"

プラグインの読み込みを追加して。

// config/bootstrap.php
Plugin::load('SoftDelete');

テーブルで SoftDeleteTrait を読み込んで。

// src/Model/Table/UserTable.php

...

use SoftDelete\Model\Table\SoftDeleteTrait;

class UsersTable extends Table
{
    use SoftDeleteTrait;
    ...

該当のテーブルに deleted というカラムを追加して。

// config/Migrations/20171114120000_CreateUsers.php
...

$table->addColumn('deleted', 'datetime', [
    'default' => null,
    'null' => true,
]);

...

Enjoy!

// コンソール機能を使って動作確認をしてみる
//==============================
$ bin/cake console


// Table の読み込み
//==============================
>>> use Cake\ORM\TableRegistry;
>>> $Users = TableRegistry::get('Users');
=> App\Model\Table\UsersTable {#214
     +"registryAlias": "Users",
     +"table": "users",
     +"alias": "Users",
     +"entityClass": "App\Model\Entity\User",
     +"associations": [],
     +"behaviors": [
       "Timestamp",
     ],
     +"defaultConnection": "default",
     +"connectionName": "default",
   }


// Entity の作成
//==============================
>>> $entity = $Users->newEntity();
=> App\Model\Entity\User {#216
     +"[new]": true,
     +"[accessible]": [],
     +"[dirty]": [],
     +"[original]": [],
     +"[virtual]": [],
     +"[errors]": [],
     +"[invalid]": [],
     +"[repository]": "Users",
   }


// データの保存
//==============================
>>> $Users->save($entity);
=> App\Model\Entity\User {#216
     +"created": Cake\I18n\Time {#232
       +"time": "2017-11-14T12:41:19+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"modified": Cake\I18n\Time {#218
       +"time": "2017-11-14T12:41:19+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"id": 2,
     +"[new]": false,
     +"[accessible]": [],
     +"[dirty]": [],
     +"[original]": [],
     +"[virtual]": [],
     +"[errors]": [],
     +"[invalid]": [],
     +"[repository]": "Users",
   }


// データ保存の確認
//==============================
>>> $Users->get(2);
=> App\Model\Entity\User {#295
     +"id": 2,
     +"created": Cake\I18n\FrozenTime {#286
       +"time": "2017-11-14T12:41:19+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"modified": Cake\I18n\FrozenTime {#285
       +"time": "2017-11-14T12:41:19+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"deleted": null,
     +"[new]": false,
     +"[accessible]": [],
     +"[dirty]": [],
     +"[original]": [],
     +"[virtual]": [],
     +"[errors]": [],
     +"[invalid]": [],
     +"[repository]": "Users",
   }


// SoftDeleteTrait を読み込むと delete の挙動が SoftDelete に変わる
//==============================
>>> $Users->delete($entity);
=> true


// SoftDelete されたデータが検索できないことを確認
//==============================
>>> $Users->get(2);
Cake\Datasource\Exception\RecordNotFoundException with message 'Record not found in table "users"'


// 'withDeleted' をつけることによって SoftDelete されたデータを検索できる
//==============================
>>> $Users->find('all', ['withDeleted'])->where(['id' => 2])->first();
=> App\Model\Entity\User {#521
     +"id": 2,
     +"created": Cake\I18n\FrozenTime {#512
       +"time": "2017-11-14T12:41:19+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"modified": Cake\I18n\FrozenTime {#511
       +"time": "2017-11-14T12:41:19+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"deleted": Cake\I18n\FrozenTime {#522
       +"time": "2017-11-14T12:42:48+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"[new]": false,
     +"[accessible]": [],
     +"[dirty]": [],
     +"[original]": [],
     +"[virtual]": [],
     +"[errors]": [],
     +"[invalid]": [],
     +"[repository]": "Users",
   }


// restore によって SoftDelete されたデータを元に戻せる
//==============================
>>> $Users->restore($entity);
=> App\Model\Entity\User {#216
     +"created": Cake\I18n\Time {#232
       +"time": "2017-11-14T12:41:19+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"modified": Cake\I18n\Time {#229
       +"time": "2017-11-14T12:44:30+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"id": 2,
     +"deleted": null,
     +"[new]": false,
     +"[accessible]": [],
     +"[dirty]": [],
     +"[original]": [],
     +"[virtual]": [],
     +"[errors]": [],
     +"[invalid]": [],
     +"[repository]": "Users",
   }


// restore の結果について確認
//==============================
>>> $Users->get(2);
=> App\Model\Entity\User {#548
     +"id": 2,
     +"created": Cake\I18n\FrozenTime {#539
       +"time": "2017-11-14T12:41:19+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"modified": Cake\I18n\FrozenTime {#538
       +"time": "2017-11-14T12:44:30+09:00",
       +"timezone": "Asia/Tokyo",
       +"fixedNowTime": false,
     },
     +"deleted": null,
     +"[new]": false,
     +"[accessible]": [],
     +"[dirty]": [],
     +"[original]": [],
     +"[virtual]": [],
     +"[errors]": [],
     +"[invalid]": [],
     +"[repository]": "Users",
   }


// hardDelete をすることによって、復元できない削除をする
//==============================
>>> $Users->hardDelete($entity);
=> true


// hardDelete の結果について確認
//==============================
>>> $Users->get(2);
Cake\Datasource\Exception\RecordNotFoundException with message 'Record not found in table "users"'

>>> $Users->find('all', ['withDeleted'])->where(['id' => 2])->first();
=> null

かゆいところに手を伸ばす

デフォルトでは deleted というカラムだが、テーブルにプロパティを追加することで違うカラム名にもできる。

// UserTable.php
...

use SoftDelete\Model\Table\SoftDeleteTrait;

class UsersTable extends Table
{
    use SoftDeleteTrait;

    protected $softDeleteField = 'kokoga_deleted_no_field_desu';

    ..

とはいえ CakePHP3 におけるデフォルトのタイムスタンプのフィールドは created と modified という名前になっているので、そこは deleted という名前で合わせにいくのがわかりやすさも高くて良いと思う。

Google Docs のスプレッドシートで変更したよ!の通知を送りたい

管理シート、複数の人が編集して担当者設定していく〜みたいな使い方をしたいんだけど!!!って言われたので…。

そもそもとして、スプレッドシートには変更したよ!の通知メールを送ることが出来る。

しかしこの機能はドバーっと送るためのもので、例えばこう、担当者が誰になって、このセルが入力されたときに、誰と誰に送る、といった複雑な処理が必要な場合には使えない。そういったときに Google App Script の出番だ。

Google App Script

Apps Script | Google Developers

GAS は端的に言えば現代におけるサーバレス、 AWS Lamda のようなもので、プログラムを Google ドライブ上に置いて、さまざまなトリガーで起動出来る。cron のように定期的に実行したり、 Google Docs と連携して操作が行われたときに実行したり、はたまた Web サイトとして公開することだって出来る。

今回はその中でも、スプレッドシート内のセル値が変更されたとき、に実行する。

というのが無料で出来るんだからグーグルってばすごい。

スプレッドシート x GAS

手順としてはこう。スプレッドシートからスクリプトエディタを開くだけ。

今回やりたい、変更されたとき、という設定はこのように行う。

あとはドキュメントを見ながら格闘して「アクティブなセル(=変更されたセル)がこの A 列だったら」とかそういうコードを書けば勝ち。

Class SpreadsheetApp | Apps Script | Google Developers

GAS から通知を送る

メールなら MailApp というクラスが用意されていて、それを呼ぶだけでメール送信ができる。あるいは Slack などの Webhook な振る舞いを実行するなら UrlFetchApp というクラスを使う。どちらにしても用意されているので簡単だ。

Class MailApp | Apps Script | Google Developers

Class UrlFetchApp | Apps Script | Google Developers

とりあえず、いずれかのセルが変更されたときにメールを送るよ!についてのソースとしてはこんな感じ。

function myFunction() {
  // スプレッドシートを取得
  var activeSheet = SpreadsheetApp.getActiveSheet();
  
  // セル範囲を取得
  var activeRange = activeSheet.getActiveRange();
  
  // メール送信
  var text = '';
  text += activeSheet.getName() + ' の ' + activeRange.getA1Notation() + ' が 変更されました\n';
  text += '新しい値: ' + activeRange.getValue();
  MailApp.sendEmail('....@.....', 'メール通知のテスト', text)  
}

これで設定したセルが変更されたとき、設定された方法で通知を送ることができる。 GAS 便利ちゃん。

便利ちゃんなのだが、メールを送るにあたって注意点がある。自分の持っている、認証されているメールアドレスからのみで送信できる、という話で、適当なメールアドレスからは送れないよというもの。通知としてのメール送信なら Slack 等のチャットの API を実行したら良いんじゃないかなあ。

大きくなってしまった git リポジトリへの対抗策

ギョームで使ってるあるシステムのリポジトリが大きすぎて、クローンめっちゃつらい、というかできないんだけど!となったので調べた。

git リポジトリの肥大化は、長年積み重なったログ、うっかりコミットされた大きなファイル、バイナリファイルの積み重ねが主な原因っぽい。
(バイナリファイルは差分でログにしていくのではなく、フルでログになる)

なので、大きく 2 つの方向があると思う。
1 つめはリポジトリ自体のログをねじ曲げる方向。ログが原因なので、そのログから原因要素を取り除けばいいじゃん!という理解。
もう 1 つはクローンするときに工夫する方向。ログが大きすぎるのでログを最小限にしていく。

方法 1 : ログを書き換える

方法 1-a : リポジトリを作り直す

リポジトリのログをねじ曲げるにあたって、手っ取り早い改善策はリポジトリを作り直すこと。最新の状態を手元に持っておき、リポジトリを捨て、最新の状態でファーストコミットをする。もろもろの再設定の手間をかけられる、ログや Github などのリポジトリに紐づく情報はポイしてもよい、そんな決断ができるなら一番簡単で効果も高い方法です。

ただ、これからクローンするのはを楽にしたいんだけど!という用途にはちょっと違うかも。

方法1-b : filter-branch サブコマンドを使って不要なファイルを歴史から隠蔽する

filter-branchを使うと、ログを遡ってコマンドを適用していけます。これを使ってログに潜む悪のファイルを精査します。ただあんまりつかったことないのでわからんね、かなしみ。

Git - 歴史の書き換え

全コミットからのファイルの削除

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten

こういう使い方で、まあまあ、簡単に捨てられそうですね。

試しに適当なリポジトリで実行してみたら、キレイサッパリとファイルが消え去りました。ちなみにコミットハッシュも変わるようです(そりゃそうだ)

$ git log --oneline --graph
* 94a3acd (HEAD -> master) ...
* c5615d4 ...
* 1b58a6d ...
* 958f4b0 ...
* 6aeccd4 ...
* 2d39eca ...
* b22e882 ...
* 554d930 ...
* d620f96 ...
* d0a5a86 ...

$ git filter-branch -f --tree-filter "rm -f package.json"
Rewrite 94a3acd99880461b586931f4d4a283494d3f9fcc (8/10) (3 seconds passed, remaining 0 predicted)
Ref 'refs/heads/master' was rewritten

$ git log --oneline --graph
* ccd7b67 (HEAD -> master) ...
* 5159356 ...
* 5551da3 ...
* 862c676 ...
* c1f6cb5 ...
* 3f1eb70 ...
* 36bc53c ...
* d7c2fa6 ...
* d620f96 ...
* d0a5a86 ...

ということで、方法1はどちらもログをぶっ壊していくので、コミットハッシュが変わります。自分も含め、既にクローンなどによってリポジトリを追跡してた人は、整合性が取れなくなるので、再度ローカルのリポジトリ状況を刷新する必要がありそうです。 fetch しよう!とかそういう話じゃなくて、過去のログを持ち込まないとか、そういうところ。

方法 2 : クローンするときに工夫する

方法 2-a : Shallow Clone

Shallow Clone の方法の1つとして depth というプロパティが設定でき、ログをどこまで遡ってクローンするかを決められます。


$ git clone --depth ...

これをするとログの遡りが少なくなるので、その分早くクローンをすることができます。かなり大きくなったリポジトリで試したらこれくらいの違いでした。 およそ 1 分かかっていたものが 30 秒程度にまとまって非常にすばらしい。

$ time git clone ...
Initialized empty Git repository in ...
remote: Counting objects: 137514, done.
remote: Compressing objects: 100% (31116/31116), done.
remote: Total 137514 (delta 108680), reused 129419 (delta 102311)
Receiving objects: 100% (137514/137514), 215.85 MiB | 8.55 MiB/s, done.
Resolving deltas: 100% (108680/108680), done.

real    1m1.198s
user    0m19.858s
sys     0m6.843s


$ time git clone --depth 1 ...
Initialized empty Git repository in ...
remote: Counting objects: 10862, done.
remote: Compressing objects: 100% (6934/6934), done.
remote: Total 10862 (delta 4852), reused 8236 (delta 3409)
Receiving objects: 100% (10862/10862), 109.83 MiB | 7.15 MiB/s, done.
Resolving deltas: 100% (4852/4852), done.

real    0m27.406s
user    0m6.650s
sys     0m2.897s

足りないログについては git fetch --depth で値を増やすと補完できるっぽい。

$ git log --oneline | wc -l
1

$ git fetch --depth 10000
remote: Counting objects: 126652, done.
remote: Compressing objects: 100% (22233/22233), done.
remote: Total 126652 (delta 103609), reused 123617 (delta 100851)
Receiving objects: 100% (126652/126652), 106.69 MiB | 5.92 MiB/s, done.
Resolving deltas: 100% (103609/103609), completed with 1886 local objects.

$ git log --oneline | wc -l
13101

Shallow Clone したリポジトリは、そういうリポジトリになるようで、深さを再設定しないと永遠とログが出てこないそうです。ちょっと昔までは push と pull もままならなかったようですが、今は改善されたようです。

いつのまにかGitのshallow cloneが”Push”も"Pull"もできるように超進化していたよ! すごーい! - ブログなんだよもん


とりあえず手元でこのブランチの様子を検証したいんやけど!みたいなものなんかは --depth 1 を積極的に使っていくとよさげたん CI ツールの類もクローンするときは全部これでええやんけ。


そのほか勉強になった記事

[Git] git repository size を削減する | deadwood

巨大なリポジトリ を Git で上手く扱う方法 | Atlassian Blogs

postfix で Gmail に送ると弾かれる件を解消する

某所から送信されるメールが GMail で弾かれてしまい、まったく送らていなかったのでその対応メモ…、メモというほどじゃない。エラー文言で調べたら一発だ。

が、メモってたエラー文言をなくしてしまったのと、サーバにもログがない(なんでやねん)ので実際にでてた文言はわからないけど、多分一緒やろ(震え)

Mail delivery to Gmail account fails: certificate verification failed for *.l.google.com – Plesk Help Center

ca-bundle.key を更新、設定したら良いっていってる。

// OpenSSL, ca-bundle.crt を更新する
$ sudo yum update openssl
..


// 設定値の追加
$ vi /etc/postfix/main.cf

smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt


// postfix の再読込
$ sudo service postfix reload

いじょ。