RedmineでGitを連携させる

このドキュメントではRedmineでGitを連携させる方法について記述する。

環境

OS:Debian7
Redmine version 2.3.1.stable
git version 1.7.10.4

apt-getでRedmineとgitをインストールしている。

gitリポジトリの作成してSmartHTTP経由参照できるようにする。

リポジトリの作成

Webサーバからアクセスできる権限で、リポジトリを作成する。

$mkdir -p /var/git/test.git
$cd /var/git/test.git
$git --bare init
$git update-server-info
$chown -R www-data .

パスワードファイルの作成

$htdigest -c /var/git/git.htdigest Git admin

Apacheの設定

以下のファイルにgitリポジトリの情報を追加して、git-http-backendが動作するようにする。

/etc/apache2/sites-available/default

/etc/apache2/sites-available/default

        SetENV GIT_PROJECT_ROOT /var/git
        SetENV GIT_HTTP_EXPORT_ALL
        ScriptAlias /git "/usr/lib/git-core/git-http-backend"
        <Location /git>
            AuthType Digest
            AuthDigestProvider file
            AuthName "Git"
            AuthUserFile /var/git/git.htdigest
            Require valid-user
            Order allow,deny
            Allow from all
        </Location>

クライアントで確認

以上の設定でHTTP経由でclone,pushが行えるようになる。

$git clone http://debian/git/test
# ローカルレポジトリの操作
$ git push origin master
Username for 'http://debian': admin
Password for 'http://admin@debian':
Counting objects: 5, done.
Writing objects: 100% (3/3), 263 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To http://debian/git/test
   2eb5859..62f3a48  master -> master

Redmineとリポジトリの関連

プロジェクトにリポジトリの追加

任意のプロジェクトで「設定」を選択して「リポジトリ」タブを選ぶ。
redminegit1.png

「新しいリポジトリ」を押下すると次の画面が表示される。

redminegit2.png

下記のように設定をする。

項目 設定
バージョン管理システム Git
メインリポジトリ チェックON
識別子 test ... 任意の値
リポジトリのパス 先に設定したGitへのパス
/var/git/test.git
パスのエンコーディング ブランク
※デフォルトでUTF-8
ファイルとディレクトリの最新コミットを表示する ON

ここまで行うと、もしリポジトリにコミットが存在すれば、各プロジェクトのリポジトリ画面には下記のように表示される。

redminegit3.png

チケットとの連携

「管理」→「設定」のリポジトリタブを選択する。

redminegit4.png

ここで以下の設定を行う。

項目 設定
コミットを自動取得する チェックON
リポジトリ管理用のWebサービスを有効にする チェックON
APIキー のちに使用するので、キーの生成を行いAPIキーを控えておく
参照用キーワード コミットログにチケットを関連付けるためのキーワード。以下のようにコミットログに記述すると関連づく
refs #12
修正用キーワード コミット時にステータスを遷移させるためのキーワード。
適用されるステータスを「終了」にしておく

次に、pushが発生した場合にリポジトリを即更新するようにする。これをおこなわないと、リポジトリ画面を開くまで、チケットとリポジトリが関連づかない。

gitのリポジトリ中のpost-receiveフックを作成する。

/var/git/test.git/hooks/post-receive

# !/bin/sh
wget  http://localhost/redmine/sys/fetch_changesets?key=作成したAPIキー

このとき、スクリプトのオーナーがwww-data等のWebサーバーからアクセスできるものになっていること。

以降、クライアントからpushを行った場合に、「refs #123」「fixes #123」等のコミットログが存在していれば下記のようにチケットとリポジトリが関連づく
redminegit4.png

チケット番号のないpushを拒否するようにする。

下記のupdateフックスクリプトを利用する。
https://github.com/bleis-tift/Git-Hooks

このupdateスクリプトは、push時のコミットで、一行目がチケット番号でないものがあったら、拒否するようになっている。

以下はTortoiseGITでpushが失敗した例である。
redminegit3.png

コミットログの修正方法

直前のコミットログの修正方法

下記のコマンドを入力して、コミットログを修正する。

git commit --amend

特定のコミットログを修正する

rebaseを使用する。

2個前のコミットを直す場合は次のようにする。

git rebase -i HEAD~2 -i

次のような画面が表示される。
redminegit3.png

修正したいコミットのpickをeditに変更する。
redminegit3.png

以下のコマンドを実行してコミットをし直し、rebaseを続ける。

$ git commit --amend -m "refs #12"
$ git rebase --continue

これで履歴が変わるのでpushを再度実行する。

参考

Redmine と Git の連携設定
http://easyramble.com/connect-redmine-with-git.html

[Git][Deiban] Debian GNU/Linux で Git を Smart HTTP Transport で使う
http://d.hatena.ne.jp/next49/20130415/p2

Redmine Guide日本語訳 > リポジトリ
http://redmine.jp/guide/RedmineRepositories/

Redmine とリポジトリの同期設定
http://d.hatena.ne.jp/yun_kichi/20100127/1264593665

SQLServerのテーブルやプロシージャーのメタ情報を取得する方法

SQLServerはSQL Server Management Studioという強力なGUIツールが提供されているため忘れがちになるが、スクリプトからSQLサーバーについての様々な情報を取得することができる。

SQLServerのメタ情報の取得方法

オブジェクト カタログ ビューの説明

ここではSQLSERVERのオブジェクトの情報を格納しているオブジェクト カタログ ビューについて説明をする。このビューをSQLで取得することにより、様々なオブジェクトの情報を閲覧できる。

テーブル名 説明
sys.objects データベース内で作成されるユーザー定義のスキーマ スコープ オブジェクトごとに 1 行のデータを格納する
http://msdn.microsoft.com/ja-jp/library/ms190324.aspx
sys.columns ビューやテーブルなど、列を持つオブジェクトの列ごとに 1 行のデータを返す。
http://msdn.microsoft.com/ja-jp/library/ms176106.aspx
sys.types システム型とユーザー定義の型ごとに 1 行のデータを格納
http://msdn.microsoft.com/ja-jp/library/ms188021.aspx
sys.indexes テーブル、ビュー、テーブル値関数など、テーブル オブジェクトのインデックスまたはヒープごとに 1 行のデータを格納。
http://msdn.microsoft.com/ja-jp/library/ms173760.aspx
sys.index_columns sys.indexes インデックスまたは順序付けられていないテーブル (ヒープ) の一部である列ごとに 1 つの行を含む
http://msdn.microsoft.com/ja-jp/library/ms175105.aspx
sys.triggers TR トリガーまたは TA トリガーであるオブジェクトごとに、1 行のデータを格納。
http://msdn.microsoft.com/ja-jp/library/ms188746.aspx
sys.parameters パラメーターを受け入れるオブジェクトのパラメーターごとに 1 行のデータを保持する。 オブジェクトがスカラー関数の場合、戻り値を説明する単一行も含まれる。 この行には parameter_id 値 0 が設定される
http://msdn.microsoft.com/ja-jp/library/ms176074.aspx

情報スキーマービュー

ISO 標準定義の INFORMATION_SCHEMA に従って、内部の情報を返すビュー。

テーブル名 説明
INFORMATION_SCHEMA.TABLES 現在のユーザーが権限を持つ、現在のデータベース内のテーブルごとに 1 行のデータを返す。
http://msdn.microsoft.com/ja-jp/library/ms186224.aspx
INFORMATION_SCHEMA.COLUMNS 現在のデータベースの現在のユーザーがアクセスできる列ごとに 1 行のデータを返す
http://msdn.microsoft.com/ja-jp/library/ms188348.aspx
INFORMATION_SCHEMA.ROUTINES 現在のデータベースの現在のユーザーがアクセスできるストアド プロシージャと関数ごとに、1 行のデータを返す
現在のデータベース内の、現在のユーザーがアクセスできるユーザー定義の関数またはストアド プロシージャのパラメーターごとに 1 行のデータを返す.
http://msdn.microsoft.com/ja-jp/library/ms188757.aspx
INFORMATION_SCHEMA.PARAMETERS http://msdn.microsoft.com/ja-jp/library/ms173796.aspx

メタ情報取得のための関数やプロシージャ

関数名 説明
OBJECT_NAME ( object_id ) オブジェクトIDからオブジェクト名に変換する関数
OBJECT_ID(object_name) オブジェクト名からオブジェクトIDを取得する関数
OBJECT_DEFINITION(object_id ) 指定したオブジェクトの定義の Transact-SQL ソース テキストを返す。たとえばストアドのオブジェクトIDを指定するとその内容が取得できる
EXEC sp_depends 'dbo.テーブル名' テーブル名に依存する関数をすべてぬきだす

使用例

これまでに説明したビューやテーブルを操作して各種のメタ情報を取得するサンプルを以下に示す。
すべてSQLで実行できるので、任意のスクリプト言語でSQLを実行すればよい。

以下はVBScriptで行った例は下記に示す。
https://github.com/mima3/SqlServerScript

ストアドプロシージャと関数の内容を取得するサンプル

SELECT specific_name,object_definition(object_id(specific_name)) FROM INFORMATION_SCHEMA.ROUTINES

トリガーの一覧と、その内容を取得するサンプル:

SELECT name,object_definition( object_id) FROM sys.triggers

プロシージャのパラメータを取得するサンプル:

select 
    sys.objects.name,
    sys.parameters.name, 
    sys.parameters.user_type_id,
    systypes.name 
  from
    sys.objects
    INNER JOIN sys.parameters ON sys.parameters.object_id = sys.objects.object_id
    INNER JOIN systypes ON sys.parameters.user_type_id = systypes.xtype
  WHERE sys.objects.type in ('TR','IF','P','FN')
  ORDER BY sys.objects.name, sys.parameters.parameter_id

テーブルの列情報を表示する例

SELECT 
  TABLE_NAME, 
  COLUMN_NAME,
  DATA_TYPE, 
  COLUMN_DEFAULT
FROM 
  INFORMATION_SCHEMA.COLUMNS
ORDER BY
  TABLE_NAME, ORDINAL_POSITION

インデックスのあるテーブルと列を表示する例

  select 
    sys.indexes.name AS INDEX_NAME,  
    sys.objects.name AS TABLE_NAME,
    sys.columns.name AS COLUMN_NAME 
  from 
    sys.indexes 
    inner join sys.index_columns on sys.indexes.object_id = sys.index_columns.object_id
    inner join sys.columns on sys.columns.column_id = sys.index_columns.column_id 
           and sys.columns.object_id = sys.index_columns.object_id
    inner join sys.objects on sys.indexes.object_id = sys.objects.object_id
  where
    sys.objects.type = 'U'
  order by sys.indexes.object_id, sys.indexes.name,sys.index_columns.column_id

特定のテーブルに依存しているオブジェクトを表示する例

EXEC sp_depends 'dbo.Table_1'

まとめ

SQLServerのメタ情報はSQLで取得できる。このことは、簡単なスクリプト言語でそれらが扱えることを意味する。
これを利用することで、様々なことが可能になる。
たとえば、2つのデータベースに対してスキーマーやプロシージャが一致するか検査するスクリプトを記述したり、テーブルのスキーマ情報をWiki形式に出力して、RedmineやTracに登録したりすることができる。

Node.jsでSQLSERVERを操作する方法

この記事ではNode.jsを用いてSQLSERVERを操作する方法について紹介する

node-mssql

node-mssql を使用すれば、Windows以外のOSからSQLSERVERを操作できる。
https://github.com/patriksimek/node-mssql

以下のnode-sqlserverでもSQLSERVERを操作できるようだが、Windowsでないと動作しない。
https://github.com/Azure/node-sqlserver

インストール方法

npm install mssql

使用方法

接続方法

もっとも単純な接続方法について以下に示す。
mssql.connectで接続して、不要になったらcloseを行う

var mssql = require('mssql');
var config = {
  user: 'sa',
  password: 'XXXX',
  server: 'ホスト名\\SQLEXPRESS', // You can use 'localhost\\instance' to connect to named instance
  database: 'Sample001',
  stream: true, // You can enable streaming globally

  options: {
    encrypt: true // Use this if you're on Windows Azure
  }
}
mssql.connect(config, function(err) {
  console.log(err);
  mssql.close();
});

SQLの発行方法

connectが完了したらRequestオブジェクトを生成して発行する
SQLSERVERの場合、複数のレコードセットが返ってくる場合があるので、recordsetイベントでそれを検知すること。

    var request = new mssql.Request(); // or: var request = connection.request();
    request.query('select * from T01Prefecture');
    request.on('recordset', function(columns) {
       // レコードセットを取得するたびに呼び出される
       console.log(columns);
    });
    request.on('row', function(row) {
       // 行を取得するたびに呼ばれる
       console.log(row);
    });

    request.on('error', function(err) {
       // エラーが発生するたびによばれる
       console.log(err);
    });

    request.on('done', function(returnValue) {
        // 常時最後によばれる
        console.log(returnValue);
    })

PreparedStatement

PreparedStatement を使用するにはinputメソッドでキーになる文字列と値を関連付ける。
VARCHARを使用する場合は、mssql.NVarCharを設定しておかないと文字化けが発生するので注意。

  var request = new mssql.Request();
  request.input('id', mssql.Int, 102);
  request.input('name', mssql.NVarChar, 'ロール'); // VARCHARだろうがNVarCharにしとかないと化ける
  request.query('INSERT INTO t01prefecture(PREF_CD,PREF_NAME) VALUES(@id,  @name)');

トランザクション

mssql.Transaction()を用いてトランザクションを作成できる。
Request時にそのトランザクションを引数とする。
クエリー実行後、commitまたはrollbackを行えばよい。

    var tran = new mssql.Transaction();
    tran.begin(function(err) {
    var request = new mssql.Request(tran); // or: var request = connection.request();
      request.query('select * from T01Prefecture');
      request.on('done', function(returnValue) {
          // 常時最後によばれる
          console.log(returnValue);
          tran.commit(function(err, ret) { // or rollback
             // TODO
             console.log('Commit');
          });
      })
    });

サンプルコード

Pythonで色々なデータベースを操作すると同様のサンプルを動かす

var mssql = require('mssql');
var async = require('async');
var util = require('util');

var config = {
  user: 'sa',
  password: 'sa',
  server: 'hostname\\SQLEXPRESS', // You can use 'localhost\\instance' to connect to named instance
  database: 'Sample001',
  stream: true, // You can enable streaming globally

  options: {
    encrypt: true // Use this if you're on Windows Azure
  }
}
var tasks = [];

// 接続
tasks.push(function(next) {
  mssql.connect(config, function(err) {
    next(err);
  });
});

// TESTデータ削除
tasks.push(function(next) {
  var request = new mssql.Request();
  request.input('from', mssql.Int, 100);
  requestSql(request, 'DELETE FROM T01Prefecture WHERE PREF_CD >= @from', function(errors, ret) {
    console.log(util.inspect(ret,false,null));
    next(errors);
  });
});

// PREPARESTATEMENTを用いたSELECT
tasks.push(function(next) {
  var request = new mssql.Request();
  request.input('from', mssql.Int, 45);
  request.input('to', mssql.Int, 999);
  requestSql(request, 'SELECT * FROM T01Prefecture WHERE PREF_CD BETWEEN @from AND @to', function(errors, ret) {
    console.log(util.inspect(ret,false,null));
    next(errors);
  });
});

////////////////////////////////////////
// コミットの試験
////////////////////////////////////////
// トランザクション開始
tasks.push(function(next) {
  var transaction = new mssql.Transaction();
  transaction.begin(function(err) {
    next(err, transaction);
  });
});

tasks.push(function(transaction, next) {
  var request = new mssql.Request(transaction);
  request.input('id', mssql.Int, 100);
  request.input('name', mssql.NVarChar, 'モテモテ国'); // VARCHARだろうがNVarCharにしとかないと化ける
  requestSql(request, 'INSERT INTO t01prefecture(PREF_CD,PREF_NAME) VALUES(@id,  @name) ', function(errors, ret) {
    next(errors, transaction);
  });
});

tasks.push(function(transaction, next) {
  var request = new mssql.Request(transaction);
  request.input('id', mssql.Int, 101);
  request.input('name', mssql.NVarChar, '野望の国'); // VARCHARだろうがNVarCharにしとかないと化ける
  requestSql(request, 'INSERT INTO t01prefecture(PREF_CD,PREF_NAME) VALUES(@id,  @name) ', function(errors, ret) {
    next(errors, transaction);
  });
});

tasks.push(function(transaction, next) {
  var request = new mssql.Request(transaction);
  request.input('from', mssql.Int, 45);
  request.input('to', mssql.Int, 999);
  requestSql(request, 'SELECT * FROM T01Prefecture WHERE PREF_CD BETWEEN @from AND @to', function(errors, ret) {
    console.log('コミット前---------------');
    console.log(util.inspect(ret,false,null));
    next(errors, transaction);
  });
});

tasks.push(function(transaction, next) {
  transaction.commit(function(err, ret) {
     next(err);
  });
});

tasks.push(function(next) {
  var request = new mssql.Request();
  request.input('from', mssql.Int, 45);
  request.input('to', mssql.Int, 999);
  requestSql(request, 'SELECT * FROM T01Prefecture WHERE PREF_CD BETWEEN @from AND @to', function(errors, ret) {
    console.log('コミット後---------------');
    console.log(util.inspect(ret,false,null));
    next(errors);
  });
});

////////////////////////////////////////
// ロールバックの試験
////////////////////////////////////////
tasks.push(function(next) {
  var transaction = new mssql.Transaction();
  transaction.begin(function(err) {
    next(err, transaction);
  });
});

tasks.push(function(transaction, next) {
  var request = new mssql.Request(transaction);
  request.input('id', mssql.Int, 102);
  request.input('name', mssql.NVarChar, 'ロール'); // VARCHARだろうがNVarCharにしとかないと化ける
  requestSql(request, 'INSERT INTO t01prefecture(PREF_CD,PREF_NAME) VALUES(@id,  @name) ', function(errors, ret) {
    next(errors, transaction);
  });
});

tasks.push(function(transaction, next) {
  var request = new mssql.Request(transaction);
  request.input('from', mssql.Int, 45);
  request.input('to', mssql.Int, 999);
  requestSql(request, 'SELECT * FROM T01Prefecture WHERE PREF_CD BETWEEN @from AND @to', function(errors, ret) {
    console.log('ロールバック前---------------');
    console.log(util.inspect(ret,false,null));
    next(errors, transaction);
  });
});

tasks.push(function(transaction, next) {
  transaction.rollback(function(err, ret) {
     next(err);
  });
});

tasks.push(function(next) {
  var request = new mssql.Request();
  request.input('from', mssql.Int, 45);
  request.input('to', mssql.Int, 999);
  requestSql(request, 'SELECT * FROM T01Prefecture WHERE PREF_CD BETWEEN @from AND @to', function(errors, ret) {
    console.log('ロールバック後---------------');
    console.log(util.inspect(ret,false,null));
    next(errors);
  });
});

// ストアドプロシージャの試験
tasks.push(function(next) {
  console.log('単一のレコードセットを返すストアドプロシージャの試験');
  var request = new mssql.Request();
  request.input('from', mssql.Int, 1);
  request.input('to', mssql.Int, 10);
  requestSql(request, 'exec test_sp @from , @to', function(errors, ret) {
    console.log(util.inspect(ret,false,null));
    next(errors);
  });
});

// 複数のレコードセットを返すストアドプロシージャの試験
tasks.push(function(next) {
  console.log('複数のレコードセットを返すストアドプロシージャの試験');
  var request = new mssql.Request();
  request.input('from', mssql.Int, 1);
  request.input('to', mssql.Int, 10);
  requestSql(request, 'exec test_sp2 @from , @to', function(errors, ret) {
    console.log(util.inspect(ret,false,null));
    next(errors);
  });
});

async.waterfall(tasks, function(err) {
  if(err) {
    console.log(err);
    process.exit();
  }
  mssql.close();
});

function requestSql(request, sql, callback) 
{
  var errors = [];
  var result = [];
  var records = [];
  request.query(sql);
  request.on('recordset', function(columns) {
    // Emitted once for each recordset in a query
    //console.log(columns);
    var rec = {
      columns:columns,
      records: []
    };
    result.push(rec);
  });

  request.on('row', function(row) {
    // Emitted for each row in a recordset
    result[result.length - 1].records.push(row);
  });

  request.on('error', function(err) {
    // May be emitted multiple times
    errors.push(err);
  });

  request.on('done', function(returnValue) {
    console.log(returnValue);
    // Always emitted as the last one
    if (errors.length == 0) {
      callback(null, result);
    } else {
      callback(errors, result);
    }
  });
}

PythonでSubversionのコミットログを解析する

この記事ではPythonでpython-svnを用いてSubversionのコミットログを解析する方法について解説する。

ここでは2.7でおこなっているが、Python 3.3, 3.2 と2.7のライブラリが提供されている。

http://pysvn.tigris.org/

インストール

以下のいずれかの方法でバイナリ―をインストールする

Unix系

sudo apt-get install python-svn

Windows、MACOS
下記からダウンロード
http://pysvn.tigris.org/project_downloads.html

ログの取得のサンプルと説明

以下のサンプルは指定のSubversionのレポジトリの履歴を表示するものである。

import pysvn
import time
import sys
from collections import defaultdict

class SvnRepController:
  def __init__(self,url_or_path,user,passwd):
    self.client = pysvn.Client()
    self.url_or_path = url_or_path
    self.user = user
    self.passwd = passwd
    self.client.callback_get_login = self.get_login

  def get_login(self, realm, username, may_save):
    return True, self.user,self.passwd, False

  def log(self):
    return self.client.log(self.url_or_path, discover_changed_paths=True)

if __name__ == '__main__':
  argvs = sys.argv
  argc = len(argvs)
  if(argc != 4):
    sys.stderr.write( 'Usage:\n python %s url_or_path user pass' % argvs[0] )
    quit()
  url_or_path = argvs[1]
  user = argvs[2]
  passwd = argvs[3]
  client = SvnRepController(url_or_path,user,passwd)

  logs = client.log()
  for log in logs:
    print ("RevNo:%d" % (log.revision.number))
    print ("Author:%s date:%s" % (log.author, time.ctime(log.date)))
    print (log.message)
    for p in log.changed_paths:
      print ("  %s" % dict(p))

以下のようにして実行できる。

python svnlog.py http://svn.sourceforge.jp/svnroot/simyukkuri/ "user名" "パスワード"

手順を説明する。

(1) pysvn.Client()を生成する。

(2)client.callback_get_loginに認証用の関数を指定する。
 この関数は認証が必要になった場合にコールバックされるものである。
 戻り値に認証情報を指定する必要がある。

(3)client.log()を行うことでPySvnLogの配列が取得できる。

(4)PySvnLogPysvnDictBaseを継承している。PysvnDictBaseはDirectoryと同様に操作できる。つまり次のようにすれば必要なプロパティが取得できる。

  logs = client.log()
  for log in logs:
    dict(log)

ここで取得できた主な項目の説明を下記に示す。

PySvnLogの内容:

名称 説明
revision.number リビジョン番号
author 作者
date コミット時刻.数値で表現されている。
message コミットメッセージ
changed_paths PysvnLogChangedPathの一覧

PysvnLogChangedPathの内容:

名称 説明
action 操作の種別をあらわす
http://svnbook.red-bean.com/en/1.7/svn.ref.svn.c.update.html
path 操作を行ったパス
copyfrom_path コピー元のパス
copyfrom_revision コピー元のリビジョン

(5)あとは必要な集計を行う。

応用

以下に応用例を示す

ファイルの更新履歴から同時に更新される頻度の高いファイルのパターンを分析することも可能である。
以下の例では単純な出現頻度であるファイルを修正した場合に発生する影響度を分析している。

import pysvn
import time
import sys
from collections import defaultdict

class FilePair:
  def __init__(self,path1,path2):
    self.path1 = path1
    self.path2 = path2
    self.count = 0
    self.reliability1 =0
    self.reliability2 =0

class SvnRepController:
  def __init__(self,url_or_path,user,passwd):
    self.client = pysvn.Client()
    self.url_or_path = url_or_path
    self.user = user
    self.passwd = passwd
    self.client.callback_get_login = self.get_login

  def get_login(self, realm, username, may_save):
    return True, self.user,self.passwd, False

  def log(self):
    return self.client.log(self.url_or_path, discover_changed_paths=True)

  def getSimpleLogicalCoupling(self):
    files = defaultdict(int)
    ret = defaultdict(FilePair)
    logs = self.log()
    for log in logs:
      for i in range(0,len(log.changed_paths)-1):
        path1 = log.changed_paths[i]
        if path1.action != "M":
          # 変更以外の場合は無視
          continue

        files[path1.path] += 1

        for j in range(i+1,len(log.changed_paths)-1):
          path2 = log.changed_paths[j]
          if path2.action != "M":
            # 変更以外の場合は無視
            continue
          if path1.path == path2.path:
            continue

          key = "%s %s" % (path1.path , path2.path)
          if( ret.has_key(key) == False ):
            key = "%s %s" % (path2.path , path1.path)
            if( ret.has_key(key) == False ):
              ret[key] = FilePair(path1.path,path2.path)
          ret[key].count += 1

    for k,v in ret.items():
      v.reliability1 = float(v.count) / files[v.path1]
      v.reliability2 = float(v.count) / files[v.path2]

    return ret

if __name__ == '__main__':
  argvs = sys.argv
  argc = len(argvs)
  if(argc != 6):
    sys.stderr.write( 'Usage:\n python %s url_or_path user pass min_count min_reliability' % argvs[0] )
    quit()
  url_or_path = argvs[1]
  user = argvs[2]
  passwd = argvs[3]
  min_count = argvs[4]
  min_reliablility = float(argvs[5])
  client = SvnRepController(url_or_path,user,passwd)
  list = client.getSimpleLogicalCoupling()
  print '"Path A","Path B","Count","Count/count of A","Count/count of B","reliability"'
  for k,v in sorted(list.items(), key=lambda x:x[1].count, reverse=True):
    if (v.reliability1 > float(min_reliablility) or v.reliability2 > float(min_reliablility)) and v.count > int(min_count):
      print '"%s","%s","%d","%f","%f","%f"' % (v.path1,v.path2,v.count,v.reliability1,v.reliability2,v.reliability1 if v.reliability1>v.reliability2 else v.reliability2)

SampleProjectのリポジトリに対してUser:admin,パスワードadminでログインをして、発生頻度が5回以上で、該当ファイルを変更する場合、最低、80%の確率で更新されているファイルの組み合わせを取得するには以下のようにすればよい。

python svnSimpleLogicalCoupling.py http://127.0.0.1/svn/SampleProject admin admin 5 0.8 > out.csv

また次のような事ができると思われる。
・コミットログを解析してどのような修正があるか分析する。
・RedmineとTracのチケット番号と関連づいている変更履歴を抜き出し、ファイル毎のバグの発生率を求める。
・人物毎のコミットの頻度

以上、変更履歴を解析することでさまざまな情報が取得できるものと期待できる。

Windowsのトラブルシューティング用の63のツール「Sysinternal Suite」

Sysinternals Suite

Sysinternals SuiteはWindowsのトラブルシューティングを行うツールをまとめたもので下記からダウンロードできる。

http://technet.microsoft.com/ja-jp/sysinternals/bb842062.aspx

以下のツールが含まれる。

名前 説明
AccessChk ファイル、ディレクトリ、レジストリ キー、グローバル オブジェクト、Windows サービスなどのリソースに対して持つアクセス権を調べるためのコマンドラインツール
http://technet.microsoft.com/ja-jp/sysinternals/bb664922
AccessEnum ファイル、ディレクトリ、レジスターのアクセス権の一覧をGUIに表示する。
http://technet.microsoft.com/ja-jp/sysinternals/bb897332
AdExplorer Active Directory (AD) のビューアーおよびエディター。未検証。
http://technet.microsoft.com/ja-jp/sysinternals/bb963907
AdRestore 削除された Active Directory オブジェクトを復元するツール。未検証
http://technet.microsoft.com/ja-jp/sysinternals/bb963906
Autologon 指定のユーザで再起動時に自動ログオンするようにできる。
http://technet.microsoft.com/ja-jp/sysinternals/bb963905
Autoruns Windowsの起動時に自動実行するプログラムの一覧を表示する。autorunsc.exeはCUIベースで、autoruns.exeはGUIになっている。
http://technet.microsoft.com/ja-jp/sysinternals/bb963902
BgInfo BgInfoは壁紙にCPUやIPなどの情報を表示する。壁紙に書き込むので無駄なプロセスは使用されない
http://technet.microsoft.com/ja-jp/sysinternals/bb897557
CacheSet システム ファイル キャッシュのワーキング セットのサイズを設定できるアプレット.未検証
http://technet.microsoft.com/ja-jp/sysinternals/bb897561
ClockRes システム クロックの精度やアプリケーションが取得できる最大タイマー精度をコンソールで表示する。
http://technet.microsoft.com/ja-jp/sysinternals/bb897568
Contig ディスク上でファイルが連続した状態になるように、単一のファイルを最適化するコマンドラインのツール
http://technet.microsoft.com/ja-jp/sysinternals/bb897428
Coreinfo 論理プロセッサと物理プロセッサの間のマッピング、NUMA ノードと NUMA ノードが存在するソケット間のマッピング、および各論理プロセッサに割り当てられたキャッシュを確認できるコマンド ラインユーティリティ
http://technet.microsoft.com/ja-jp/sysinternals/cc835722
Ctrl2cap CapsLock キーで入力する文字を Ctrl キーの文字に変換する.未検証
http://technet.microsoft.com/ja-jp/sysinternals/bb897578
DebugView DebugView は、ローカル システム、または TCP/IP 経由でアクセスできるネットワーク上の任意のコンピュータにおける、デバッグの出力を監視できるアプリケーション
http://technet.microsoft.com/ja-jp/sysinternals/bb896647
Desktops 最大4つの仮想ディスクトップを切り替えて使用できるようになる。
http://technet.microsoft.com/ja-jp/sysinternals/cc817881
Disk2vhd VirtualPCで使用する物理ディスクのVHDを作成する。コマンドラインから使用できる。未検証。
http://technet.microsoft.com/ja-jp/sysinternals/ee656415
DiskExt ボリュームのパーティションがどのディスク上にあるか、パーティションがディスク上のどこにあるかをコマンドラインで返す。管理者権限が必要。
http://technet.microsoft.com/ja-jp/sysinternals/bb896648
DiskMon ハード ディスクのすべての動作状況をログに記録したり表示したりするアプリケーション。要管理者権限
http://technet.microsoft.com/ja-jp/sysinternals/bb896646
DiskView ディスクのグラフィカルなマップを表示することができ、ファイルの位置を特定したり、クラスターをクリックして、そのクラスターを占有しているファイルを確認したりできる。
http://technet.microsoft.com/ja-jp/sysinternals/bb896650
Disk Usage (DU) 指定したディレクトリのディスク領域の使用量をレポートするコマンドラインツール。
http://technet.microsoft.com/ja-jp/sysinternals/bb896651
EFSDump 暗号化されたファイルにアクセスする権限があるアカウントを表示するコマンドラインツール。未検証
http://technet.microsoft.com/ja-jp/sysinternals/bb896735
FindLinks 指定されたファイルのために存在するファイルインデックスと任意のハードリンクをレポートするコマンドラインツール
http://technet.microsoft.com/en-us/sysinternals/hh290814
Handle システムのプロセスで開かれているハンドルに関する情報を表示するユーティリティ。
http://technet.microsoft.com/ja-jp/sysinternals/bb896655
Hex2dec 10進数、16進数の相互変換を行うコマンドラインツール
http://technet.microsoft.com/ja-jp/sysinternals/bb896736
Junction ジャンクションとよばれるリンクの作成、確認を行うためのツール。
http://technet.microsoft.com/ja-jp/sysinternals/bb896768
LDMDump LDMデータベースに格納されているデータを詳しく調査できるユーティリティ.未検証
http://technet.microsoft.com/ja-jp/sysinternals/bb897413
ListDLLs Windows 9x か Windows NT に読み込まれる DLLのリストを取得するコマンドラインツール
http://technet.microsoft.com/ja-jp/sysinternals/bb896656
LiveKd マイクロソフト カーネル デバッガーの Kd と Windbg を、運用システムでローカルに実行することができる。未検証
http://technet.microsoft.com/ja-jp/sysinternals/bb897415
LoadOrder デバイス ドライバーが読み込まれる順序を表示する。
http://technet.microsoft.com/ja-jp/sysinternals/bb897416
LogonSessions 現在アクティブなログオン セッションを一覧表示するコマンドラインツール。また、-p オプションを指定すると、各セッションで実行中のプロセスの一覧が表示される。
http://technet.microsoft.com/ja-jp/sysinternals/bb896769
PendMovesとMoveFile サービスパックや修正プログラムのように再起動後に移動や削除をおこなうためのMoveFileコマンドと、現在ペンディング中のファイルを表示するPendMovesコマンド
http://technet.microsoft.com/ja-jp/sysinternals/bb897556
NTFSInfo NTFS ボリュームに関する情報を表示するコマンドラインツール。
http://technet.microsoft.com/ja-jp/sysinternals/bb897424
PageDefrag ページング ファイルとレジストリ ハイブの断片化の確認と最適化を行うらしいが、Windows7 64bitでは動作しない。
http://technet.microsoft.com/ja-jp/sysinternals/bb897426
PipeList パイプのディレクトリ一覧を取得するコマンドラインツール
http://technet.microsoft.com/ja-jp/sysinternals/dd581625
PortMon システムのシリアル ポートとパラレル ポートで行われている活動を監視および表示するユーティリティ。未検証
http://technet.microsoft.com/ja-jp/sysinternals/bb896644
ProcDump 条件を指定して、プロセスのダンプを行う。
http://technet.microsoft.com/ja-jp/sysinternals/dd996900
Process Explorer 実行中のプログラムの状態を調べるツール。呼び出し元の親プロセスとの関係をツリーで表現している。
http://technet.microsoft.com/ja-jp/sysinternals/bb896653
Process Monitor プロセスが行った処理(ファイル、レジストリ、プロセス・スレッドの活動)をリアルタイムに表示するツールである。
http://technet.microsoft.com/ja-jp/sysinternals/bb896645
PsExec PsExec は Telnet に代わる軽量のユーティリティで、クライアント ソフトウェアを手動でインストールしなくても、他のシステムでプロセスを実行できるだけでなく、コンソール アプリケーションとの十分な対話性も備わっている。しかしVista以降ではUACをOFFにしないと使用できない。
http://technet.microsoft.com/ja-jp/sysinternals/bb897553
PsFile リモートからアクセスしているファイルやフォルダーの一覧を表示したり、アクセスを終了させるツール
http://technet.microsoft.com/ja-jp/sysinternals/bb897552
PsGetSid SIDの確認ができる。
http://technet.microsoft.com/ja-jp/sysinternals/bb897417
PsInfo ローカルまたはリモートの Windows NT/2000 システムに関する重要な情報を収集するコマンド ライン ツール
http://technet.microsoft.com/ja-jp/sysinternals/bb897550
PsKill ローカルまたはリモートのプロセスを強制終了するツール
http://technet.microsoft.com/ja-jp/sysinternals/bb896683
PsList ローカルまたはリモートのプロセスの一覧を表示する。
http://technet.microsoft.com/ja-jp/sysinternals/bb8966802
PsLoggedOn ローカルにログオンしているユーザーとローカル コンピューターまたはリモート コンピューターのリソースを経由してログオンしているユーザーの両方を表示するアプレット
http://technet.microsoft.com/ja-jp/sysinternals/bb897545
PsLogList リモート システムにログインして、イベントログのメッセージ文字列を取得できる
http://technet.microsoft.com/ja-jp/sysinternals/bb897544
PsPasswd ローカル システムまたはリモート システムのアカウントのパスワードを変更できるツール
http://technet.microsoft.com/ja-jp/sysinternals/bb897543
PsService ローカルまたはリモートのサービスのビューアー兼コントローラー
http://technet.microsoft.com/ja-jp/sysinternals/bb897542
PsShutdowon ローカルまたはリモートをシャットダウンする。
http://technet.microsoft.com/ja-jp/sysinternals/bb897541
PsSuspend ローカル システムまたはリモート システムのプロセスを中断できる。終了ではなく中断なので、後から-rオプションを利用することでプロセスを再開できる。
http://technet.microsoft.com/ja-jp/sysinternals/bb897540
RAMMap 物理メモリの使用状況を詳細に分析できるGUIツール
http://technet.microsoft.com/ja-jp/sysinternals/ff700229
RegDelNull 埋め込まれた null 文字が含まれており、標準のレジストリ編集ツールでは削除できないレジストリ キーを検出して削除できます
http://technet.microsoft.com/ja-jp/sysinternals/bb897448
RegJump ジストリのパスを受け取って、指定のパスが表示された状態で Regedit を開く。
http://technet.microsoft.com/ja-jp/sysinternals/bb963880
RootkitRevealer ルートキット検出ユーティリティ.未検証。Windows XP(32ビット)およびWindows Server2003(32ビット)では動作するらしいがWin7 64bitでは動作
http://technet.microsoft.com/ja-jp/sysinternals/bb897445
SDelete Secure Delete.ディスク上にある、削除されたファイルのデータを上書きすることで、ファイルの復元を防ぐ。
http://technet.microsoft.com/ja-jp/sysinternals/bb897443
ShareEnum でアクセス可能なドメイン内のすべてのコンピューターがスキャンされ、ファイル共有や印刷共有、およびそのセキュリティ設定が表示される.
http://technet.microsoft.com/ja-jp/sysinternals/bb897442
ShellRunas プログラムを別のアカウントで起動する
http://technet.microsoft.com/ja-jp/sysinternals/cc300361.aspx
Sigcheck 画像がデジタル署名されているかどうかを確認して、バージョン情報をダンプする。未検証。PDFの署名は検出しない。
http://technet.microsoft.com/ja-jp/sysinternals/bb897441
Streams 関連付けられたストリームを持つ NTFS ファイルを確認できるツール
ストリームについては下記参考
http://www.atmarkit.co.jp/fwin2k/win2ktips/1363streams/streams.html
http://technet.microsoft.com/ja-jp/sysinternals/bb897440
Strings プログラム中の文字を抜き出す。ただし、日本語は抽出できない
http://technet.microsoft.com/ja-jp/sysinternals/bb897439
Sync ファイル システム データの安定性を確保し、システム障害時にデータが失われないようにするために、すべてのファイル システム データをディスクにフラッシュするようオペレーティング システムに指示する
http://technet.microsoft.com/ja-jp/sysinternals/bb897438
TCPView ネットワークの接続状況を確認するためのツール
http://technet.microsoft.com/ja-jp/sysinternals/bb897437
VMMap プロセスの仮想メモリと物理メモリを分析するユーティリティ。このユーティリティでは、プロセスで使用している仮想メモリの種類と、オペレーティング システムによって各種類に割り当てられた物理メモリ (ワーキング セット) の量の分析結果を表示する。
http://technet.microsoft.com/ja-jp/sysinternals/dd535533
VolumeID ボリューム ID を変更する.未検証
http://technet.microsoft.com/ja-jp/sysinternals/bb897436
Whois 指定されたドメイン名または IP アドレスの登録レコードを表示
http://technet.microsoft.com/ja-jp/sysinternals/bb897435
WinObj NT オブジェクト マネージャーの名前空間の情報にアクセスしてその情報を表示する
http://technet.microsoft.com/ja-jp/sysinternals/bb896657
ZoomIt 画面を拡大したり、画面に注釈を記入したりするツール
http://technet.microsoft.com/ja-jp/sysinternals/bb897434

補足説明

DebugView

APIのOutputDebugStringで指定した文字列を表示するデバッグ用のツール。
VisualStudioなどをインストールせずに、実行環境でトレースログを出力することができる。
Releaseビルドでも出力されるので、通常のDebug実行などで再現できない不具合を追跡するのに使用する。

DebugView.png

VC++

void CDebugDemoDlg::OnBnClickedButton1()
{
    // TODO: ここにコントロール通知ハンドラ コードを追加します。
    OutputDebugString(L"TEST用の出力");
} 

VisualStudioからプロセスを実行した場合、DebugViewには何も出力されず、VisaulStudioの出力ウィンドウに出力される。

C#

private void button1_Click(object sender, EventArgs e)
{
  Trace.WriteLine("テスト1");
}

System.Diagnostics.Traceを使用する。
こちらはVisualStudioからプロセスを実行した場合でもDebugViewと出力ウィンドウの両方に出力される。

VBA

Declare Sub OutputDebugString Lib "kernel32" Alias "OutputDebugStringA" (ByVal lpOutputString As String)

Public Sub test()
    OutputDebugString "ああdddあtest"
End Sub

一応これで出力できるが、可能ならCOMでラップしてやったほうがいい。

Process Monitor

Process Monitorはリアルタイムで、プロセスのファイルやレジストリに対するアクセスを表示する。

pmon.png

Procmon.exeを管理者権限で動作させる必要がある。

このツールを用いれば、どのプロセスがどのファイルやレジストリを書き込んでいるか、または、指定のパスを操作しているプロセスはどれかを追跡することができる。

すべての情報をトレースすると量が多いので、プロセスによってフィルタをかけたり、パスにフィルタをかけるとよい。

Process Explorer

タスクマネージャの上位互換。
ProcessExp.png

呼び出したプロセスによってツリーを構築している。
ProcessExp.pngアイコンを選択後、ウィンドウズを選択すると、そのウィンドウの所属するプロセスを表示する機能も有している。

PendMovesとMoveFile

このツールの説明の前にリブート時の移動について説明する。

サービスパックや修正プログラムのように再起動後に移動や削除をおこなうためにMoveFileEx APIのフラグにMOVEFILE_DELAY_UNTIL_REBOOTがある。
http://msdn.microsoft.com/ja-jp/library/cc429621.aspx

MoveFileツールは、MoveFileExAPIをMOVEFILE_DELAY_UNTIL_REBOOTとともに実行したものである。
もし、MOVEFILE_DELAY_UNTIL_REBOOTフラグをつけたときに、移動先にブランクを入れた場合は、移動ではなく削除になる。
このフラグをつけて実行した場合、下記のレジストリに、移動情報が格納され、再起動時に実行される。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations

そして、このレジストリの内容を確認するのが、PendMovesコマンドになる。

PsExec~PsSuspendコマンド

これらのコマンドは、リモートの端末に対して操作を行える。
ただし、リモートでこれらのコマンドを実行するにはリモート端末で、いくつかの設定が必要になる。

・ファイアーウォールの設定でポートを開ける
・WindowsXPの場合は、「簡易ファイルの共有を使用する」のチェックを外す
・Vista以降の場合はUACを無効にする必要がある。

これらは、セキュリティー的に不適切な設定なので、すくなくともVista以降で常時使うツールにすべきでない。

Autologon

指定のユーザでリブート時にオートログインにできる。
当然、セキュリティー的にのぞましくない。
しかし、再起動が必要な自動テストを行う場合は必要になる。

例:
OS再起動後、アプリケーションの処理を実行して速度を計測する。
それを10回繰り返すなど。

まとめ

いくつかのツールはWinXP用で動作しないものもあるが、DbgView、ProcessMonitorなどは強力なツールなので、これの有無が生産性を劇的に変えると思われる。

なお、これらは、個別でもダウンロードできる。

PythonでGitのコミットログを解析する

この記事ではPythonのGitPythonを用いてGITのコミットログを解析する方法について解説する。

前提条件として、下記の通り
・Gitがインストールされていること。
・Python2.7系であること。
 Python3.3への互換性は現時点でないようだ(ゴールに設定されている)

開発ソース
https://github.com/gitpython-developers/GitPython

ドキュメント
http://pythonhosted.org/GitPython/0.3.2/

インストール

以下を実行する。

# easy_install GitPython

サンプル

指定のリポジトリのコミットの一覧を列挙する

以下のサンプルは指定のリポジトリのコミットのハッシュIDの一覧と、コミット情報を格納するクラス名を出力するスクリプトである。

# -*- coding: utf-8 -*-
from git import *

repo = Repo("/share/testgit/searchTwitter")
for item in repo.iter_commits('master', max_count=100):
  print(item.hexsha)
  print(item.__class__)

Repoにはローカルのリポジトリへのパスを入力すること。
Subversionのような集中管理システムと違い、GITはローカルに構成管理に必要なすべての情報を保持している。この情報はリポジトリの.gitフォルダの中にすべて格納されている。

このコマンドを実行すると、コミットの情報はgit.objects.commit.Commitに格納されていることがわかる。

Commitの主なプロパティ

名前 説明
author その作業をもともと行った人
authored_date Author の日付時刻
author_tz_offset authorのタイムゾーンのオフセット
committer その作業を適用した人
committed_date committerの日付時刻
committer_tz_offset committerのタイムゾーンのオフセット
message コミットメッセージ
summary コミットメッセージの1行目
stats Diffから作られる統計情報。stats.filesに更新があったファイルの情報が格納されている。
parents 親になるgit.objects.commit.Commitの一覧。親がないのが初回コミットになる。これを利用すればコミットの順番を作成できる。
tree blobを格納するツリー構造のデータ。Treeクラスで定義されている

Treeの主な構造

ツリー構造を表現するデータ。Treeクラスで定義されている。

tree.blobsにはツリーに所属するすべてのBlobが格納されているので、以下のように再帰的に呼び出せば、コミットに紐づくBlobをすべて抜き出せる。

def show_tree(tree, indent):
  """
  Treeの情報を出力
  """
  print ('%shexsha :%s' % (indent, tree.hexsha))
  print ('%spath :%s' % (indent, tree.path))
  print ('%sabspath :%s' % (indent, tree.abspath))
  print ('%smode :%s' % (indent, tree.mode))
  for t in tree.trees:
    show_tree(t, indent + '  ')

  print ('%s[blobs]' % indent)
  for b in tree.blobs:
    show_blob(b, indent + '  ')

Blobは実際のファイルの内容を表すもので、自身のサイズと内容から計算される SHA-1 ハッシュによって名付けられる。

また、コミットのtreeに紐づくBlobは変更があったものだけでなく、すべてのファイルが紐づいている。
変更がないものは前回のコミットと同じハッシュ値、変更があったものは違うハッシュ値で格納されている。。

Gitは「ディレクトリのスナップショットを保全している」ことがこれからわかると思う。

最終的なサンプル

リポジトリのコミットを抜き出し、各コミットのBlobまで抜き出すサンプルは以下の通りになる。

# -*- coding: utf-8 -*-
from git import *
import time

def show_blob(b, indent):
  """
  Blobの情報を出力
  """
  print ('%s---------------' %(indent))
  print ('%shexsha:%s' % (indent,b.hexsha))
  print ('%smime_type:%s' % (indent,b.mime_type))
  print ('%spath:%s' %(indent,b.path))
  print ('%sabspath:%s' %(indent,b.abspath))

def show_tree(tree, indent):
  """
  Treeの情報を出力
  """
  print ('%shexsha :%s' % (indent, tree.hexsha))
  print ('%spath :%s' % (indent, tree.path))
  print ('%sabspath :%s' % (indent, tree.abspath))
  print ('%smode :%s' % (indent, tree.mode))
  for t in tree.trees:
    show_tree(t, indent + '  ')

  print ('%s[blobs]' % indent)
  for b in tree.blobs:
    show_blob(b, indent + '  ')

def show_commitlog(item):
  """
  Commitの情報を出力
  """
  print ("hexsha %s" %item.hexsha)
  print (item.author)
  print (item.author_tz_offset)
  print (time.strftime("%a, %d %b %Y %H:%M", time.gmtime(item.committed_date)))
  print (item.committer)
  print (item.committer_tz_offset)
  print (item.encoding)
  print (item.message)
  print (item.name_rev)
  print (item.summary)
  print ('[stats]')
  print (item.stats.total)
  print (item.stats.files)
  print ('[parents]')
  for i in item.parents:
    print('  %s' % i.hexsha)

  print '[Tree]'
  show_tree(item.tree, '  ')

repo = Repo("/share/testgit/searchTwitter")
for item in repo.iter_commits('master', max_count=100):
  print ('================================')
  show_commitlog(item)

まとめ

ローカルにCloneしたGitのリポジトリに対してはGitPythonを用いることで、コミットログを簡単に解析できることを説明した。

これを利用することにより、リポジトリに対するコミットの統計情報を作成し、プロジェクト管理の助けに利用することが期待できる。

参考

GitPython Documentation
https://pythonhosted.org/GitPython/0.3.2/index.html

見えないチカラ
http://keijinsonyaban.blogspot.jp/2011/05/git.html

Git Book
http://git-scm.com/book/ja/

SQLServerでスクリプトのデバッグを行う

このドキュメントではSQLServer 2008以降でストアドプロシージャのデバッグを行う方法について説明する。

手順

1. Microsoft SQL Server Management Studioでストアドプロシージャを右クリックする。
2. [ストアドプロシージャをスクリプト化]⇒[EXECUTE]⇒新しいクエリーエディターウィンドウ
3 以下のようなスクリプトが生成される

USE [Sample001]
GO

DECLARE @RC int
DECLARE @from int
DECLARE @to int

-- 方法: パラメーター値をここに指定します。

EXECUTE @RC = [dbo].[test_sp] 
   @from
  ,@to
GO

4. テスト用のパラメータを入力する

-- 方法: パラメーター値をここに指定します。
SET @from = 10
SET @to = 200

5. スクリプトにブレークポイントを張る
sql.png

6.「デバッグ」⇒「開始」を選択
ALT+F5:次のブレークまで実行
F10:ステップオーバ... 次の行を実行。次が別のストアドの場合、ストアドの中には入らない
F11:ステップイン...次の行を実行。次が別のストアドならストアドの中に入る。
sql.png

注意

・リモート側またはクライアント側がHomeEditionの場合はWindows認証が上手くいかずに、デバッグが実行できない可能性がある。

sql.png

T-SQLデバッグを開始できません。コンピュータ'XXXX'に接続できませんでした。Visual Studio リモート デバッガーは、このWindowsエディションをサポートしていません。

この原因はリモートデバッグにWindows認証が必要で、HomeEditionにはそれがふくまれていないため。
http://social.msdn.microsoft.com/Forums/vstudio/en-US/77689d96-dc35-4a10-96e8-a0f98b33901c/visual-studio-2008-remote-debugging-and-windows-xp-sp2-home-edition?forum=vsdebug

・上記のエラーが発生した場合で、SQLSERVERのあるマシンにMicrosoft SQL Server Management Studioを入れているのなら「localhost\SQLEXPRESS」というようにlocalhostと明示しておけばデバッグが可能。

・SQLSERVER2005以前に対しては、Microsoft SQL Server Management Studioからはデバッグできない。VisualStudio 2008 Professionalのサーバ接続のウィンドウで接続してデバッグする必要がある。

radonを使用したPythonのソースコードのメトリックスの取得

概要

RadonはPythonのメトリックスを計測するプログラムである。
https://github.com/rubik/radon

これにより、Pythonのコードの複雑度や行数を取得することができる。
複雑度を計測することで、ソースコードの潜在的リスクを明確にして、リファクタリングやテストをすべき対象を明示できる。

インストール方法

python2.xまたはPython3.xで下記を実行する。

easy_install easy_install radon

使い方

Cyclomatic Complexityの計測

ccコマンドを使用することでCyclomatic Complexityの計測が行える。Cyclomatic Complexityは制御文が多いほど高くなる。
詳細は下記を参考のこと。
https://radon.readthedocs.org/en/latest/intro.html#cyclomatic-complexity

使用例:

radon cc -s ".\*.py"

無題.png

出力内容:
最初の記号はブロックタイプを表す

記号 説明
F 関数
M メソッド
C クラス

次の数値は計測対象のブロックの開始行列を表す

行:列

次は関数名などのブロック名を表す。

「-」以降はランクとCyclomatic Complexityの値となる

ランクはCyclomatic Complexityの値によって決まる。

CCの値 ランク リスク
1 - 5 A low - 単純なブロック
6 - 10 B low - よく構造化され、安定したブロック
11 - 20 C moderate - 少し複雑なブロック
21 - 30 D more than moderate - より複雑なブロック
31 - 40 E high - 複雑なブロック、憂慮すべき
41+ F very high - エラーが発生しやすい、不安定なブロック

オプション:
ccコマンドで使用できるオプションは次の通り

Option 説明
-s, --show Cyclomatic Complexityを表示する。
-n, --min 表示する最小のランクを設定する。この引数の後に A~Fを指定
-x, --max 表示する最大のランクを設定する。この引数の後に A~Fを指定
-a, --average Cyclomatic Complexityの平均を最後に表示する。この平均は-nや-xによるフィルタに影響をうける
--total-average すべてのデータの平均をうける-aと異なり、フィルタの影響を受けない
-e, --exclude 解析の対象から外すファイルのパスをコンマ区切りで指定する
-i, --ignore 無視するディレクトリをコンマ区切りで指定する。無視したディレクトリ以下は検査しない
-o, --order 並び順を指定する.
SCORE 複雑度順
LINES 行数順
ALPHA ブロック名順
-j, --json 結果をJSONで出力する
--no-assert 複雑度を計算する場合にasser()命令はカウントしない

Maintainability Indexの計測

miコマンドを使用することでMaintainability Indexを計測する。MaintainabilityはCyclomatic Complexityと行数から計算され、100を最高に高いほど、保守しやすい。

詳細は以下を参照
https://radon.readthedocs.org/en/latest/intro.html#maintainability-index

使用例:

radon mi -s ".\*.py"

出力内容:

C:\dev\py33>radon mi -s "test.py"
test.py - A (58.76)

ファイル名、ランク、MI scoreを表示する

ランクについては下記の通り

MI score Rank Maintainability
100 - 20 A 非常に高い
19 - 10 B 中程度
9 - 0 C 非常に低い

オプション:
miコマンドで使用できるオプションは次の通り

Option 説明
-s, --show Maintainability Indexを表示する。
-e, --exclude 解析の対象から外すファイルのパスをコンマ区切りで指定する
-i, --ignore 無視するディレクトリをコンマ区切りで指定する。無視したディレクトリ以下は検査しない
-m, --multi 複数行の文字列をコメントとみなして数えない

rawデータの計測

rawコマンドは以下の数値を出力する。

LOC :総行数
LLOC :Logical LOC。空行とかコメント行を除いたもの
SLOC :Source LOC .空行を除いたもの。
comments :コメント行
multi :複数行の文字列。コメントとみなされることがある
blank : 空行

オプション:
rawコマンドで使用できるオプションは次の通り

Option 説明
-s, --summary 計測した集計結果を表示する
-e, --exclude 解析の対象から外すファイルのパスをコンマ区切りで指定する
-i, --ignore 無視するディレクトリをコンマ区切りで指定する。無視したディレクトリ以下は検査しない
-j, --json 結果をJSONで出力する

プログラムからの利用

radonモジュールをpythonのコードにimportすることでpython上からradonが利用できる。

例:

import radon
from radon import raw
ret = radon.raw.analyze("""
if a==1:
  print (a)
if a==2:
  print (a)
""")
print(ret)

詳細についてはhelpで調べるか下記参照
https://radon.readthedocs.org/en/latest/api.html

RedmineのWikiでUMLを記述する方法

RedmineのWikiにシーケンス図やユースケースなどのUMLを記述する方法について説明する。

前提

・JAVAが動くこと
・Redmineが動作すること
 Apache2でRedmineが動いていたものとする

手順

1.PlantUMLを下記よりダウンロードして任意のフォルダにおく
http://plantuml.sourceforge.net/download.html

この例では下記に配置したものとする.
 /share/plantuml.jar

2. ラッパー用のシェルスクリプトを記述する。
 この例では/usr/bin/plantuml に記述するものとする。

# !/bin/bash
/usr/bin/java -Djava.io.tmpdir=/var/tmp -jar /share/plantuml.jar ${@}

3. Redmine用のプラグインを下記からダウンロードする
https://github.com/cdwertmann/wiki_external_filter

4. 3のファイルをRedmineのpluginsフォルダにコピーする。
フォルダ名はwiki_external_filterとする。
ダウンロードした際のデフォルトのフォルダ名はwiki_external_filter_masterになっているので、名前を修正しておく。

保存先の例
/var/lib/redmine/plugins/wiki_external_filter

5. plugin_assetsフォルダに書き込み権限を与える。
 /var/lib/redmine/public/plugin_assets/

例:
chmod go+w /var/lib/redmine/public/plugin_assets/

6. 解凍したディレクトリに存在するconfig/wiki_external_filter.yml を redmineのconfigにコピーする。
 例:
  /var/lib/redmine/config

7. wiki_external_filter.ymlのplantumlにおけるパスを適切に指定する。
(この例だと修正不要のはず)

8. apache2の起動時のlocaleをutf-8とする。
 これを怠ると、日本語が適切に表示されなくなる。

 /etc/apache2/envvars の下記を修正
 export LANG=ja_JP.UTF-8
 
 なお、下記の戻り値がUTF-8ならば日本語が使えるようになる。
 Encoding.find("locale")

9. apache2を再起動

10.redmineの管理メニューより、キャッシュの保持時間を指定する。
  デフォルトは0であるが、この場合は、キャッシュを保持せず画像が絶対に表示されない。

  管理>プラグイン>Wiki External Filter Plugin の設定

  「Cache expiration time 」に十分大きな数値を入力
b0232065_03141636.png

11. 下記のような文章をWikiに記述する

{{plantuml
ジョニー-> ジャック: 求愛
ジャック-> サラ: 求愛
サラ->ジョニー: 求愛
}}

b0232065_03143326.png

その他メモ

・下記のページに一般的なRedmineのプラグインのインストール方法が記載されている。
http://www.redmine.org/projects/redmine/wiki/Plugins

・正常にプラグインが動作しない場合、ログを出力してデバッグするといい。
必要な箇所に以下のようなコードを挿入する。

Rails.logger.info "executing command: #{out['command']}"

その結果、以下のようなファイルにログが出力される。
/var/lib/redmine/log/production.log

Node.jsでIpv6を取り扱う方法

ここではNode.jsにおいてIpv6をどのように取り扱うか説明する。

ExpressのサーバーでIPv6を受け付けるようにする。

既定の設定でNode.js+Expressでサーバーを立ち上げた場合、下記のようにipv6のアドレスで接続しようとすると失敗する。

http://[fe80::20c:29ff:feb8:6dd1]:3000/

この回避策としてIPを明示する方法が提示されている。
http://code.danyork.com/2011/01/21/how-to-use-node-js-with-ipv6/

var http = require('http');

var handler = function (request, response) {
   response.writeHead(200, {"Content-Type":"text/plain"});
   response.end ("Hello World!n");
   console.log("Got a connection");
};

var server6= http.createServer();
server6.addListener("request",handler);
server6.listen(80,"2001:db8:1111:2222:3333::51");

var server= http.createServer();
server.addListener("request",handler);
server.listen(80,"192.0.2.23");

console.log("Server running on localhost at port 80");

この方法はイーサーネットを複数保持している場合は動作しない。
たとえば、上記でlistenしているアドレスのほかに2001::52のイーサーネットを保持していた場合、2001::52からのアクセスがみとめられない。

保持するIpv6すべてでlistenするには以下のようにする。

    var server = app.listen(app.get('port'), '::0', function() {
      debug('Express server listening on port ' + server.address().port);
    });

Ipv6形式で記述してあるが、ipv4のアドレスも受け付ける。

IPv6の操作

Node.jsでIpv6の有効チェックや数字化、範囲チェックを行うには以下のライブラリを使用するとよい。

https://github.com/beaugunderson/javascript-ipv6

IPv6を数値に変換した場合、JavaScriptの仕様上、double型と扱われ、精度が落ちる。
このライブラリではbigIntegerというライブラリを用い、それを回避している。
もし、IPの範囲の検査で大小を比較する場合は、bigIntegerが提供しているcompairToを用いて行う。
まちがっても自分ですべての配列の大小を比較する車輪の再開発みたいなことはしてはいけない。

以下にその使用例を示す。

var v6 = require('ipv6').v6;
var address1 = new v6.Address('2001:0:ce49:7601:e866:efff:62c3:fffe');
var address2 = new v6.Address('2001:0:ce49:7601:e866:efff:61c3:0002');

// 値がipv6かチェックする
console.log(address1.isValid());

var ngAddress = new v6.Address('1.2.3.4');
console.log(ngAddress.isValid());

var ngAddress = new v6.Address('test.co.jp');
console.log(ngAddress.isValid());

// アドレスの比較をする場合はbigIntegerに変換して、compareToで比較すること
var bi1 = address1.bigInteger();
var bi2 = address2.bigInteger();

// thisがでかければ+
console.log(bi1.compareTo(bi2));
// ひとしければ 0
console.log(bi1.compareTo(bi1));
// 引数のほうが大きければ-
console.log(bi2.compareTo(bi1));

// サブネットマスクを指定して、開始アドレス、終了アドレスも取得できる。
address1.subnetMask = 64;
console.log(address1.startAddress());
console.log(address2.endAddress());

// 省略形で表示も可能
address1 = new v6.Address('2001:0:0:0:0:0:0:1');
// 2001::1 と省略形で
console.log(address1.correctForm());