LoginSignup
36
29

More than 1 year has passed since last update.

日本語原稿の git コミット間の編集距離を調べる

Posted at

本記事の対象

日本語原稿を git で管理し、各コミットの文字数で進捗を評価しているが、推敲した回は字数が増えないので、サボったみたいで気に入らない人。

やること

git のコミット間の編集距離(レーベンシュタイン距離)を比較する Python3 スクリプトを書く。

git コマンドでの比較

まず以下のような原稿がある。

HEAD^
 むかしむかし、あるところにおじいさんとおばあさんが住んでいました。
 おじいさんは山へ芝刈りに、おばあさんは川へ選択に行きました。

2行目に誤字があるのでこれを修正(芝→柴、選択→洗濯)し、再度コミットする。

HEAD
 むかしむかし、あるところにおじいさんとおばあさんが住んでいました。
 おじいさんは山へ柴刈りに、おばあさんは川へ洗濯に行きました。

変更箇所を知りたい場合は git diff を使う。

$ git diff HEAD^ HEAD momotaro.txt
...
  むかしむかし、あるところにおじいさんとおばあさんが住んでいました。
- おじいさんは山へ芝刈りに、おばあさんは川へ選択に行きました。
+ おじいさんは山へ柴刈りに、おばあさんは川へ洗濯に行きました。

編集した行数を知りたいときは git diff--numstat オプションをつければわかる。1行追加し1行削減している。

$ git diff --numstat HEAD^ HEAD
1       1       momotaro.txt

だが現実的に言って、日本語原稿で1行単位は大雑把すぎる。文字単位での diff をとりたい場合は --word-diff-regex オプションをつける。

$ git diff --word-diff-regex='.' HEAD^ HEAD momotaro.txt
...
 むかしむかし、あるところにおじいさんとおばあさんが住んでいました。
 おじいさんは山へ[-芝-]{+柴+}刈りに、おばあさんは川へ[-選択-]{+洗濯+}に行きました。

なお一部環境ではこの結果が文字化けするので、その場合はオプションを以下のように修正する。(手元の Debian 環境ではうまくいくが macOS では修正が必要)

$ git diff --word-diff-regex=$'[^\x80-\xbf][\x80-\xbf]*' HEAD^ HEAD momotaro.txt

ただこの --word-diff-regex--numstat を併用する方法が見当たらなかった(行単位の変化が出てしまう)ので、仕方なく Python で編集距離を測るスクリプトを作ることにした。

Python で編集距離を比較する

使うのは Levenshtein というモジュール。まずこれを入れる。

$ pip install python-Levenshtein

これ自体の使用方法はきわめて簡単。

levenshtein.py
#!/usr/bin/env python3

import sys
import Levenshtein

print(Levenshtein.distance(sys.argv[1], sys.argv[2]))
$ ./levenshtein.py 三島由紀夫 三波春夫
3

次に Python スクリプト内で git コマンドを実行する。GitPython というライブラリがあるらしいけど、内部で subprocess を走らせてるだけらしいので、直接 subprocess でいいや。

gitleven.py
#!/usr/bin/env python3

import sys
import subprocess
from subprocess import PIPE
import Levenshtein

commit1 = sys.argv[1]
commit2 = sys.argv[2]

# 3個目の引数があればそのファイルを、なければ編集した全ファイルを比較
try:
    filenames = [ sys.argv[3] ]
except IndexError:
    proc0 = subprocess.run(["git", "diff", "--name-only", commit1, commit2], stdout=PIPE, stderr=PIPE, text=True)
    filenames = proc0.stdout.split()

print("{}\t{}\tdiff\tLeven".format(commit1, commit2))

total1 = 0
total2 = 0
total_leven = 0

for filename in filenames:
    proc1 = subprocess.run(["git", "show", "{}:{}".format(commit1, filename)], stdout=PIPE, stderr=PIPE, text=True)
    text1 = proc1.stdout
    proc2 = subprocess.run(["git", "show", "{}:{}".format(commit2, filename)], stdout=PIPE, stderr=PIPE, text=True)
    text2 = proc2.stdout

    leven = Levenshtein.distance(text1, text2)
    l_text1 = len(text1)
    l_text2 = len(text2)

    total1 += l_text1
    total2 += l_text2
    total_leven += leven

    print("{}\t{}\t{}\t{}\t{}".format(l_text1, l_text2, l_text2-l_text1, leven, filename))

print("{}\t{}\t{}\t{}\t{}".format(total1, total2, total2-total1, total_leven, "TOTAL"))
$ ./gitleven.py HEAD^ HEAD
HEAD^   HEAD    diff    Leven
66      66      0       3       momotaro.txt
66      66      0       3       TOTAL

これで総字数の変化がなくても3文字の進捗があったことがわかり、桃太郎も後顧の憂いなく鬼退治に集中できる。

参考資料

こちらの記事を参考に、Perl がわからんので Python で書いた。
https://stackoverflow.com/questions/23974443/how-to-get-an-edit-distance-between-two-commits

36
29
1

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
36
29