はじめに
本記事は以下ブログ記事の転記です。
記事で書かれているpoetry、git-hook、git-tagの説明は割愛します。
詳細は公式ドキュメントを確認してください。
補足情報
2024/10/09
既存の不満点を改善するため、GitHub Actionsによるワークフローで更新する方法を検討しました。
【GitHub Actions】pyproject.tomlのversionキーとgit tagを同じバージョン指定で更新してreleaseを作成するworkflowについて の記事をブログに投稿しましたので、こちらも参考にしてください。
2023/09/06
しばらく運用していて、コミットごとにgit tag
するのは冗長だと感じることが増えてきました。
コミットごとにpyproject.toml
とgit tag
を更新(インクリメント)するのであれば良いですが、毎回更新しなくてよい場合は、後述する.pre-commit-config.yaml
のupdate-pyproject
フックを一式コメントアウトしてください。
動作としては以下の通りです。
-
pyproject.toml
のversion
キーを更新する場合は、コミットするとpyproject.toml
のversion
キーを取得して、git tag
が実行される。 -
pyproject.toml
のversion
キーを更新しない場合は、git tag
は実行されない。
要約
-
git commit
をトリガーにpyproject.toml
のtool.poetry
テーブルのversion
キーとgit tag
を同時に更新する - git hookでバージョン更新用のスクリプトをフックに設定する
- git hookとしてpre-commitとpost-commitの2つを使用する
- post-commitでは最後に
git push
を実行する
作成したもの
以下のスクリプトをgit hookで実行します。
1. update_pyproject_version.py
- pyproject.tomlのバージョンを更新するためのPythonスクリプト
2. run_git_tag_base_pyproject.py
-
pyproject.toml
のtool.poetry
テーブルのversion
キーを取得してgit tag
を実行するためのPythonスクリプト
3. .pre-commit-config.yaml
- git フックスクリプトをセットアップするためのコンフィグファイル
4. pre-commitフック
- poetryコマンド、静的解析パッケージ、
update_pyproject_version.py
を含む、pre-commitで実行するカスタムスクリプト -
.pre-commit-config.yaml
作成後にpoetry run pre-commit install
コマンドで作成します。
5. post-commitフック
-
run_git_tag_base_pyproject.py
をgit commit
後に実行するカスタムスクリプト -
create_post-commit.sh
を実行することで作成します。
実行例
以下のプロジェクトを例に上記スクリプトの実行例を示します。
1.コミット前の情報
0.2.8までバージョンが追加されている状態です。
1.1. ローカルタグ情報
video-grid-merge % git tag
v0.2.1
v0.2.2
v0.2.3
v0.2.4
v0.2.5
v0.2.6
v0.2.7
v0.2.8
1.2. リモートタグ情報
% git ls-remote --tags origin
0ae7fa7be0457a3512c51c7a231f24e724570ffd refs/tags/v0.2.1
197a04bcf528e4c0ef5c297dff061d4d34ffea2b refs/tags/v0.2.2
01f88f943ee8330034530c14fb417bf45c5e651b refs/tags/v0.2.3
4212bfa0733f2c2c809f9db37333096b752a451e refs/tags/v0.2.4
52b72640df2f31edbef5c21e0fec149d67eafe04 refs/tags/v0.2.5
6de440811f4adbbd614f622ebed74450dfeaa951 refs/tags/v0.2.6
03b75ea8f4688c3e3b8e02c285093b9736a38d24 refs/tags/v0.2.7
054237a84bbe9233099c55af4ff2443145fbb4d8 refs/tags/v0.2.8
1.3. pyproject.toml
[tool.poetry]
name = "video-grid-merge"
version = "0.2.8"
description = "This project allows you to use FFmpeg to arrange video files stored in a specified folder in an NxN grid layout and generate the output."
authors = ["7rikaz_h785 <7rikaz.h785.stat2ltas41lcijad@gmail.com>"]
readme = "README.md"
license = "MIT"
packages = [{include = "video_grid_merge"}]
[tool.poetry.scripts]
#video-grid-merge = "video_grid_merge.__main__:main"
#delete-temporaliy-files = "video_grid_merge.local_code.delete_files:main"
[tool.taskipy.tasks]
vgmrun = "python video_grid_merge"
vgmrn = "python video_grid_merge/rename_files.py"
vgmrm = "python video_grid_merge/delete_files.py"
vgmtest = "pytest -s -vv --cov=video_grid_merge --cov-branch --cov-report term-missing --cov-report html"
isort = "poetry run isort video_grid_merge tests"
black = "poetry run black video_grid_merge tests"
flake8 = "poetry run flake8 video_grid_merge tests"
mypy = "poetry run mypy"
[tool.poetry.dependencies]
python = "^3.10"
mdformat = "^0.7.16"
tomlkit = "^0.11.8"
[tool.poetry.group.dev.dependencies]
flake8 = "^6.0.0"
black = "^23.3.0"
isort = "^5.12.0"
mypy = "^1.3.0"
pytest = "^7.3.1"
flake8-pyproject = "^1.2.3"
taskipy = "^1.11.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.10.0"
pre-commit = "^3.3.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.black]
target-version = ['py310']
[tool.isort]
[tool.flake8]
ignore = ["E402","E501","W503"]
[tool.mypy]
files = ["video_grid_merge","tests"]
python_version = "3.10"
strict = true
warn_return_any = false
ignore_missing_imports = true
scripts_are_modules = true
[tool.pytest.ini_options]
testpaths = ["tests",]
2. プロジェクトのインストール
git clone https://github.com/7rikazhexde/video-grid-merge.git
cd video-grid-merge
poetry install
プロジェクトは以下のようになります。
run_git_tag_base_pyproject.py
とupdate_pyproject_version.py
はciという名前のディレクトリに格納されています。
video-grid-merge % tree -L 2
.
├── LICENSE
├── README.md
├── ci
│ ├── run_git_tag_base_pyproject.py
│ └── update_pyproject_version.py
├── create_post-commit.sh
├── poetry.lock
├── pyproject.toml
├── requirements-dev.txt
├── requirements.txt
├── tests
│ ├── __init__.py
│ ├── test_data
│ └── test_main.py
└── video_grid_merge
├── __init__.py
├── __main__.py
├── delete_files.py
├── local_code
├── media
└── rename_files.py
3. pre-commitスクリプトとpost-commitスクリプトの作成
.git/hooks以下は下記の通りです。
video-grid-merge % tree -L 2 .git/hooks
.git/hooks
├── applypatch-msg.sample
├── commit-msg.sample
├── fsmonitor-watchman.sample
├── post-update.sample
├── pre-applypatch.sample
├── pre-commit.sample
├── pre-merge-commit.sample
├── pre-push.sample
├── pre-rebase.sample
├── pre-receive.sample
├── prepare-commit-msg.sample
├── push-to-checkout.sample
└── update.sample
poetry install後はpre-commitスクリプトとpost-commitスクリプトは存在しないため、下記コマンドを実行してインストールします。(一部記載を環境変数に置き換えています。)
3.1. pre-commit作成
video-grid-merge % poetry run pre-commit install
Configuration file exists at $HOME/Library/Preferences/pypoetry, reusing this directory.
Consider moving TOML configuration files to $HOME/Library/Application Support/pypoetry, as support for the legacy directory will be removed in an upcoming release.
pre-commit installed at .git/hooks/pre-commit
3.2. post-commit作成
video-grid-merge % chmod +x ./create_post-commit.sh
video-grid-merge % ./create_post-commit.sh
[video-grid-mergeのディレクトリパス]/.git/hooks/post-commit created with execution permission.
再度.git/hooks以下を確認するとpre-commitとpost-commitが追加されていることを確認できます。
video-grid-merge % tree -L 2 .git/hooks
.git/hooks
├── applypatch-msg.sample
├── commit-msg.sample
├── fsmonitor-watchman.sample
├── post-commit # 追加
├── post-update.sample
├── pre-applypatch.sample
├── pre-commit # 追加
├── pre-commit.sample
├── pre-merge-commit.sample
├── pre-push.sample
├── pre-rebase.sample
├── pre-receive.sample
├── prepare-commit-msg.sample
├── push-to-checkout.sample
└── update.sample
4. git commit例
4.1. 失敗例
作成したgit hookはgit commit
をトリガーに実行されますが、pre-commit
では.pre-commit-config.yaml
のプラグインオプションであるfail_fast
により、各hook処理が成功(=Passed)しない場合には実行を停止させています。これはupdate-pyprojectフック(update_pyproject_version.py
)で失敗した情報を取得できないため、エラーの場合も実行させないことで、バージョンの更新もさせないようにしています。
次に失敗時の例として、.pre-commit-config.yaml
ファイルで改行を増やして、git add
後にgit commit
した場合、trailing-whitespaceフックにより指摘箇所は修正されますが、pre-commitフック
の実行は停止されます。
> git -c user.useConfigOnly=true commit --quiet
trim trailing whitespace.................................................Failed
- hook id: trailing-whitespace
- exit code: 1
- files were modified by this hook
Fixing README.md
4.2. 成功例
ここで再度、git commit
を実行するとpyproject.toml
とgit tag
を0.2.9で更新してgit push
していることが確認できます。(実行結果の表示について、vscodeのコミットボタン押下でも問題なく動作しますが、デフォルトではログ出力しないため、git commitコマンドを実行しています。)
% git commit -m "docs(README): Changed installation description"
trim trailing whitespace.................................................Passed
fix end of files.........................................................Passed
mixed line ending........................................................Passed
check toml...........................................(no files to check)Skipped
check yaml...........................................(no files to check)Skipped
poetry-check.........................................(no files to check)Skipped
- hook id: poetry-check
poetry-lock..............................................................Passed
- hook id: poetry-lock
- duration: 2.39s
Configuration file exists at $HOME/Library/Preferences/pypoetry, reusing this directory.
Consider moving TOML configuration files to $HOME/Library/Application Support/pypoetry, as support for the legacy directory will be removed in an upcoming release.
Updating dependencies
Resolving dependencies... (0.9s)
poetry-export........................................(no files to check)Skipped
- hook id: poetry-export
poetry-export........................................(no files to check)Skipped
- hook id: poetry-export
isort................................................(no files to check)Skipped
black................................................(no files to check)Skipped
flake8...............................................(no files to check)Skipped
mypy.................................................(no files to check)Skipped
mdformat.................................................................Passed
Update pyproject.toml version............................................Passed
Configuration file exists at $HOME/Library/Preferences/pypoetry, reusing this directory.
Consider moving TOML configuration files to $HOME/Library/Application Support/pypoetry, as support for the legacy directory will be removed in an upcoming release.
v0.2.1
v0.2.2
v0.2.3
v0.2.4
v0.2.5
v0.2.6
v0.2.7
v0.2.8
v0.2.9
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 4 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 451 bytes | 451.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To https://github.com/7rikazhexde/video-grid-merge.git
054237a..c6f792c main -> main
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/7rikazhexde/video-grid-merge.git
* [new tag] v0.2.9 -> v0.2.9
.git/hooks/post-commit end!!!
[main c6f792c] docs(README): Changed installation description
2 files changed, 5 insertions(+), 5 deletions(-)
4.3. git log(コミット後)
video-grid-merge % git log --stat
commit c6f792c39cebb9ee6c21c6ae7c040573f4ddd995 (HEAD -> main, tag: v0.2.9, origin/main, origin/HEAD)
Author: 7rikaz_h785 <7rikaz.h785.stat2ltas41lcijad@gmail.com>
Date: Fri Jun 9 21:53:03 2023 +0900
docs(README): Changed installation description
README.md | 8 ++++----
pyproject.toml | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
4.4. ローカルタグ情報(コミット後)
video-grid-merge % git tag
v0.2.1
v0.2.2
v0.2.3
v0.2.4
v0.2.5
v0.2.6
v0.2.7
v0.2.8
v0.2.9 # 追加
4.5. リモートタグ情報(コミット後)
% git ls-remote --tags origin
0ae7fa7be0457a3512c51c7a231f24e724570ffd refs/tags/v0.2.1
197a04bcf528e4c0ef5c297dff061d4d34ffea2b refs/tags/v0.2.2
01f88f943ee8330034530c14fb417bf45c5e651b refs/tags/v0.2.3
4212bfa0733f2c2c809f9db37333096b752a451e refs/tags/v0.2.4
52b72640df2f31edbef5c21e0fec149d67eafe04 refs/tags/v0.2.5
6de440811f4adbbd614f622ebed74450dfeaa951 refs/tags/v0.2.6
03b75ea8f4688c3e3b8e02c285093b9736a38d24 refs/tags/v0.2.7
054237a84bbe9233099c55af4ff2443145fbb4d8 refs/tags/v0.2.8
c6f792c39cebb9ee6c21c6ae7c040573f4ddd995 refs/tags/v0.2.9 # 追加
注意事項
git commit
時にコミットキャンセルした場合はpre-commit
は正常終了していますので、update-pyprojectフック(update_pyproject_version.py
)は動作します。
結果、pyproject.toml
のtool.poetryテーブル
のversionキー
は更新されますので、その状態で再度コミットするとさらに0.0.1分加算します。なので、この場合はversion
部分を手動で変更前の値に戻す必要があります。現状はコミットキャンセル処理を考慮していないのでこのようになっています。
また、update_pyproject_version.py
ではv[major].[minor].[pathch]
でバージョン定義していますが、major
は0以上、[minor]と[pathch]
は0から999にしています。もし、0.1.0から0.2.0にminorバージョンを更新するためには0.1.999とする必要があります。
それぞれ、今後改善したい部分ですが、対応は未定です。
コード補足
コードはGistにコミットしていますが、下記リポジトリにもコミットしています。
試験用のため更新は非定期で、将来的にはリポジトリを変更するかもしれませんが、利用したい方は自己責任の範囲でフォークやDLをして使用してください。
まとめ
本記事では、git hookでpythonプロジェクトのバージョン管理を自動化する記事を紹介しました。
pre-commitではバージョン更新用のフック以外にも静的解析ツールのフックも利用しています。ツールに信頼を置くことが前提になりますが、コードの品質を上げるためには必要だと思います。
一方でツールが増えると管理が複雑になるため、一元管理し、人による操作はなるべく減らすことが必要だと感じています。DockerやGitHub Action等、便利なツールは他にもありますが、個人的には使用経験が浅いので、今後はgit hook以外にもツールを活用して、より効率的なコーディングができるようにしていきたいと思っています。
最後に、本記事がどなたかの参考になれば幸いです。
参考
以下の記事を参考にさせていただきました。
以上です。