概要
開発をしていて、ブランチ間の比較を行い差分ファイルを作成したいときがあるだろう
差分だけであれば以下のコマンドで得ることができる
# masterブランチとdevelopブランチを比較する場合
git diff --name-only master develop > difflist.txt
出力したファイルを取得したいこともあるだろう
上記のコマンドで出力した結果をもとにxcopyなどでコピーはできるが、自動化できたらもっと楽になる
開発
開発といっても今の時代生成AIさんに発注すると短時間でプロトタイプを作ってもらうことができる
今回も生成AIに画面仕様と処理概要を事細かくmarkdown形式の仕様書を食わせて、プロトタイプを作成し、必要な機能追加などを行った
コメントにプロトタイプ作成時のプロンプトを公開しました
開発言語
Python3
GUIはtkinterを利用
データベースはsqliteを利用(設定を書き込むためのデータベース)
開発環境
Windows11 VSCode
機能概要
- 初回起動時にワークスペース名を選択または入力するウィンドウを表示(初回は入力)
- ワークスペース名を選択したらメインウィンドウに遷移し、gitローカルリポジトリのフォルダの選択と差分を出力するフォルダを選択する
- この時.gitが存在しないフォルダを選択したときエラーとする
- ローカルのブランチは更新ボタンを押下すると取得できる
- 2つのブランチを選択して実行ボタンを押すと比較を行う
- 比較結果はテキストファイルに出力
- テキストファイルの出力結果をもとにブランチからファイルをコピーする
- フォルダ名はブランチ名と時刻で自動生成
Pythonでgitコマンドを実行する方法
-
Pythonにはsubprocessというモジュールが存在します
subprocess.run(["コマンド(カンマ区切り)"], オプション)
差分の取得
-
gitコマンドでは冒頭でも触れたとおり以下で取得できる
# masterブランチとdevelopブランチを比較する場合 git diff --name-only master develop > difflist.txt -
これをPythonで行うと以下のようになる
# コンボボックスから各値を取得 self.repo_path = self.git_folder_entry.get() branch1, branch2 = self.branch1_combo.get(), self.branch2_combo.get() # ファイル名に使える文字列に置換 safe_branch1 = re.sub(r'[^\w.-]', '-', branch1) safe_branch2 = re.sub(r'[^\w.-]', '-', branch2) # 差分一覧ファイル名を生成(比較するブランチ名より生成) diff_file = os.path.join(self.diff_dir, f"diff_{safe_branch1}_{safe_branch2}.txt") # 重要なのは以下の1文のみ subprocess.run(["git", "diff", "--name-only", branch1, branch2], cwd=self.repo_path, text=True, stdout=open(diff_file, "w"))
コマンドであれば単純に>を使うことでリダイレクトできますが、stdout=open(ファイル名, "w")で書き込み先ファイルを指定する必要があります
ブランチからコピーの方法
-
ブランチからファイルをコピーするには以下のコマンドで行うことができる
# readme.mdファイルをmasterブランチからコピー(取得結果をリダイレクトで書き込み) git show master:readme.md > master/readme.md -
これをPythonで行うと以下のようになる
# ※branch_srcはブランチ名:ファイルパスが格納されている # ※destはコピー先のファイルパスが格納されている subprocess.run(["git", "show", branch_src], cwd=self.repo_path, text=True, stdout=open(dest, "w"))
そのほか、コピーの際にブランチ間の差分として存在の差分があるファイルもある(新しいブランチで追加や削除が行われた場合)
その時に、コピー処理を通らないようにgit ls-treeで存在をチェックする処理も行った
# ブランチに指定のファイルが存在しているか確認
ret = (subprocess.run(["git", "ls-tree", "--name-only", branch, "--", rel_path], cwd=self.repo_path, capture_output=True, text=True).stdout or "").strip()
if ret == rel_path:
ディレクトリの生成とファイルの取得と書き出し処理
else:
スキップ
コマンドの実行結果を取得するには以下の要素が必要でやや苦労した
- オプション
- capture_output=True
- text=True
- 文字列での取得
- .stdoutとすることで結果を取得できる(オプションのおかげ)
- or ""とつけることで結果がないときに空文字を与える
- .strip()で不要な文字を削除する(ほかの言語でのtrimに相当する)
結果をrel_pathと比較しているがrel_pathはgitリポジトリから取得したファイルのパスであり、該当ブランチにファイルが存在していた時はretにはファイルパスを返すので同じ結果であれば存在していると判断できる
もともとの仕様
最初思い浮かんだ仕様は、差分取得→ブランチ切り替え→ファイルコピー→ブランチ切り替え→ファイルコピーであった
ただ、作業中の場合、ブランチは切り替えられないときがある
そんな時git showでカレントブランチ以外からファイルを取得できることを思い出した
思いついたらすぐに実装してみたら、何度かエラーを出したものの無事想定通りの挙動まで持ってこれました
とりあえずgithubに公開
まだまだ荒いので、versionは0.0.2
ファイル一式をダウンロードしてrun.batをダブルクリックすると実行できるようにしています
もちろん以下コマンドでも実行できます(※ファイル名は変える可能性あり)
python3 ./difftool.py
※Windows以外は試していないです