※当サイトの記事には、広告・プロモーションが含まれます。

pygit2は結局のところlibgit2に習熟している人向けのライブラリっぽい気はする...

gigazine.net

自分の作品がAIの学習に許可なく使用されたかどうかを簡単に確認できるようにするための法案「人工知能ネットワークの透明性と責任(TRAIN)法」がアメリカで提出されました。この法案が実現すれば、著作権者は学習の記録にアクセスできるようになり、自分の作品が使用されたかどうかを確認できるようになるとのことです。

自分の創作物がAI学習に使用されたかどうかを確認できるようになる「AIの透明性と責任に関する法案」がアメリカで提出される - GIGAZINE

TRAIN法は、著作権者が「自分の作品がモデルのトレーニングに使用された」という「誠実な信念(good faith belie)」を宣言できれば、著作権者は生成AIモデルのトレーニング記録の開示を請求できると定めるものです。

自分の創作物がAI学習に使用されたかどうかを確認できるようになる「AIの透明性と責任に関する法案」がアメリカで提出される - GIGAZINE

法案では、AIの開発者が反論するには、著作権者の著作物が使用されたかどうかを特定するのに十分な資料を明らかにするだけでよいとされています。明らかにしない場合、法的には、開発者は著作権で保護された作品を使用していると仮定されます。

自分の創作物がAI学習に使用されたかどうかを確認できるようになる「AIの透明性と責任に関する法案」がアメリカで提出される - GIGAZINE

⇧『著作権者の著作物が使用されたかどうかを特定するのに十分な資料を明らかにするだけでよい』って簡単に言ってくれていますけど、「学習データ」が「ネット上の情報」とかも含んでいる場合、「著作物」が使用されたかどうかの明確な判定は不可能に近い気がするですけど...

「ネット上の情報」に対して、事前に許可申請とか無理な気がするんだが...

エンジニアの負担が増えていく一方ですな...

pygit2とは

公式のGitHubに上がっている情報によると、

github.com

Bindings to the libgit2 shared library, implements Git plumbing. Supports Python 3.10 to 3.13 and PyPy3 7.3+

https://github.com/libgit2/pygit2

⇧ とあり、「libgit2」というライブラリの「Python」でラッパーしたライブラリということらしい。

「libgit2」のREADMEによると、

github.com

libgit2 is a portable, pure C implementation of the Git core methods provided as a linkable library with a solid API, allowing to build Git functionality into your application. Language bindings like Rugged (Ruby), LibGit2Sharp (.NET), pygit2 (Python) and NodeGit (Node) allow you to build Git tooling in your favorite language.

https://github.com/libgit2

⇧『a portable, pure C implementation of the Git core methods provided as a linkable library with a solid API, allowing to build Git functionality into your application』とあるので、「C言語」に習熟している人向けという気がする。

一応、

git-scm.com

A2.2 Appendix B: Embedding Git in your Applications - Libgit2

Libgit2

Another option at your disposal is to use Libgit2. Libgit2 is a dependency-free implementation of Git, with a focus on having a nice API for use within other programs. You can find it at https://libgit2.org.

https://github.com/libgit2

⇧「Git」の公式のドキュメントで、「libgit2」が紹介されている。

2024年12月8日(日)時点で、「libgit2」のREADMEによると、

github.com

Language Bindings

Here are the bindings to libgit2 that are currently available:

https://github.com/libgit2

⇧ 28個のプログラミング言語で利用できるようになっている。

実際には、「Delphi」、「Java」、「R」が2つずつライブラリが存在するらしいので、31個のライブラリが用意されていることになるっぽい。

No プログラミング言語 ライブラリ名 URL
1 C++ libqgit2, Qt bindings

https://projects.kde.org/projects/playground/libs/libqgit2/repository/

2 Chicken Scheme chicken-git

https://wiki.call-cc.org/egg/git

3 D dlibgit

https://github.com/s-ludwig/dlibgit

4 Delphi GitForDelphi

https://github.com/libgit2/GitForDelphi

5 Delphi libgit2-delphi

https://github.com/todaysoftware/libgit2-delphi

6 Erlang Geef

https://github.com/carlosmn/geef

7 Go git2go

https://github.com/libgit2/git2go

8 GObject libgit2-glib

https://wiki.gnome.org/Projects/Libgit2-glib

9 Guile Guile-Git

https://gitlab.com/guile-git/guile-git

10 Haskell hgit2

https://github.com/jwiegley/gitlib

11 Java Jagged

https://github.com/ethomson/jagged

12 Java Git24j

https://github.com/git24j/git24j

13 Javascript / WebAssembly WASM-git

https://github.com/petersalomonsen/wasm-git

14 Julia LibGit2.jl

https://github.com/JuliaLang/julia/tree/master/stdlib/LibGit2

15 Lua luagit2

https://github.com/libgit2/luagit2

16 .NET libgit2sharp

https://github.com/libgit2/libgit2sharp

17 Node.js nodegit

https://github.com/nodegit/nodegit

18 Objective-C objective-git

https://github.com/libgit2/objective-git

19 OCaml ocaml-libgit2

https://github.com/fxfactorial/ocaml-libgit2

20 Parrot Virtual Machine parrot-libgit2

https://github.com/letolabs/parrot-libgit2

21 Perl Git-Raw

https://github.com/jacquesg/p5-Git-Raw

22 Pharo Smalltalk libgit2-pharo-bindings

https://github.com/pharo-vcs/libgit2-pharo-bindings

23 PHP php-git2

https://github.com/RogerGee/php-git2

24 Python pygit2

https://github.com/libgit2/pygit2

25 R gert

https://docs.ropensci.org/gert

26 R git2r

https://github.com/ropensci/git2r

27 Ruby Rugged

https://github.com/libgit2/rugged

28 Rust git2-rs

https://github.com/rust-lang/git2-rs

29 Swift SwiftGit2

https://github.com/SwiftGit2/SwiftGit2

30 Tcl lg2

https://github.com/apnadkarni/tcl-libgit2

31 Vala libgit2.vapi

https://github.com/apmasell/vapis/blob/master/libgit2.vapi

とは言え、ネット上で上記のライブラリを使ってみた的な情報が少ないところを見るに、あまり知名度は高くないのかもしれない...

話を「pygit2」に戻すと、公式のドキュメントに、

www.pygit2.org

⇧ 対応表が掲載されておりました。

pygit2は結局のところlibgit2に習熟している人向けのライブラリっぽい気はする...

悲報...

stackoverflow.com

⇧ stackoverflowを見た感じ、「pygit2」の公式のドキュメントで利用方法についての説明は無いということで、手軽に利用できるライブラリとは言い難い...

C言語」に習熟していない人でも利用できるように「Python」でラッパーしたライブラリであるはずの「pygit2」なのだが、肝心の利用方法については、「libgit2」の仕組みについて把握している必要があるという...

まぁ、ネット上に利用してみたの情報がほとんど見当たらないので、何となく嫌な予感はしていたのですが、「pygit2」を利用するには人柱になる必要があるということですな...

とは言え、「Python」初心者であり、「Git」についても習熟しているわけではない我輩にとっては、公式のドキュメントのAPIリファレンスを一から読解するとうな時間も無く、有識者の実装例をお手本としたく、ネットの情報を漁っていたところ、

pygit 2 snippets - most examples assume using a bare repository.

https://gist.github.com/danielmt/81ae37541270714c2605b8f3d574d993

⇧ 上記サイト様が「pygit2」で「bare repository」を扱う実装例を公開して下さっていた、圧倒的感謝。

pygit2でGitのbare repositoryに対してGit操作を行う実装をしてみる

ほぼ、

pygit 2 snippets - most examples assume using a bare repository.

https://gist.github.com/danielmt/81ae37541270714c2605b8f3d574d993

⇧ 上記サイト様の実装を流用させていただき、「private repository」を「git clone --bare」する部分を追加した感じ。

■/home/ts0818/work/app/python/app/src/main/py/api/service/pygit2/pygit2_service.py

"""app/src/main/py/api/service/pygit2/pygit2_service.py."""

import email.utils
import logging
from urllib.parse import urlparse

import pygit2

from src.main.py.enum.pygit2.repo_status import RepoStatus
from src.main.py.enum.pygit2.token_type import TokenType


class Pygit2Service:
    """pygit2のAPIを実行するクラス."""

    _logger = logging.getLogger(__name__)
    _repo: pygit2.Repository = None

    def __init__(self, git_repo_dir_path: str, bare_repo_path: str, access_token: str, token_type: TokenType, username: str) -> None:
        """初期化."""
        self._repo: pygit2.Repository = self._open_git_repo(git_repo_dir_path, bare_repo_path, access_token, token_type, username)

    @classmethod
    def git_push(cls, access_token: str, username: str = "x-access-token", refs: tuple[str] = ["refs/heads/main"]) -> bool:
        """Git push."""
        try:
            cls._logger.info("[start]git_push")

            # git push
            callback = pygit2.RemoteCallbacks(pygit2.UserPass(username, access_token))
            cls._repo.remotes["origin"].push(refs, callback)
            cls._logger.info("Success git push")
            return True

        except Exception:
            cls._logger.exception("Error: git_push")
            return False
        finally:
            cls._logger.info("[finish]git_push")

    @classmethod
    def git_commit_with_add_blob(cls, file_name: str, file_path: str, username: str, useremail: str, reference_name: str, commit_message: str) -> bool:
        """ファイルを追加してコミットする."""
        try:
            cls._logger.info("[start]git_commit_with_add_blob")

            # Gitリポジトリが、新規か既存かチェックする
            repo_status: RepoStatus = cls._check_git_repo_for_new_or_exists()
            if repo_status is RepoStatus.NEW:
                cls._logger.info("new local git bare repository")

            elif repo_status is RepoStatus.EXISTING:
                cls._logger.info("existing local git bare repository")

            # blobを作成する
            if not cls._git_add_blob_from_file(file_path):
                cls._logger.warning("Failed: _git_add_blob_from_file")
                return False

            # blobを追加する
            if not cls._git_add_tree_from_blob(file_name):
                cls._logger.warning("Failed: _git_add_tree_from_blob")
                return False

            # indexに追加する
            if not cls._git_add_index():
                cls._logger.warning("Failed: _git_add_index")
                return False

            # commitするauthorを設定する
            if not cls._git_config_commit_auther(username, useremail):
                cls._logger.warning("Failed: _git_config_commit_auther")
                return False

            # commitする
            if not cls._git_commit(reference_name, commit_message):
                cls._logger.warning("Failed: _git_commit")
                return False

            cls._logger.info("Success git commit")
            return True

        except Exception:
            cls._logger.exception("Error: git_commit_with_add_blob")
        finally:
            cls._logger.info("[finish]git_commit_with_add_blob")

    # private method
    def _open_git_repo(self, git_repo_dir_path: str, bare_repo_path: str, access_token: str, token_type: TokenType, username: str = "x-access-token") -> pygit2.Repository:
        """Gitリポジトリを扱えるようにする."""
        local_repository_path: str = pygit2.discover_repository(git_repo_dir_path)
        if local_repository_path is None:
            if not self._git_clone_bare(git_repo_dir_path, bare_repo_path, access_token, token_type, username):
                self._logger.warning("Failed _open_git_repo")
        return pygit2.Repository(local_repository_path)

    def _git_clone_bare(self, git_repo_dir_path: str, bare_repo_path: str, access_token: str, token_type: TokenType, username: str = "x-access-token") -> bool:
        """Git clone --bare."""
        try:
            parsed_url = urlparse(bare_repo_path)
            clone_url: str = f"https://{username}:{access_token}@{parsed_url.netloc}{parsed_url.path}.git"
            # git clone --bare by auth Personal Access Token(PAT)
            if token_type == TokenType.GITHUB_PERSONAL_ACCESS_TOKEN:
                self._logger.info("git clone --bare by auth personal access token")

            # git clone --bare by auth GitHub App Installation Token
            if token_type == TokenType.GITHUB_APP_INSTALLATION_TOKEN:
                self._logger.info("git clone --bare by auth GitHub App Installation Token")

            self._logger.info("git clone url: %s", clone_url)
            callbacks: pygit2.RemoteCallbacks = pygit2.RemoteCallbacks(pygit2.UserPass(username, access_token))
            self._repo = pygit2.clone_repository(url=bare_repo_path, path=git_repo_dir_path, bare=True, callbacks=callbacks)
            return self._repo is not None

        except Exception:
            self._logger.exception("Error: _git_clone_bare")
            return False

    # private method
    def _check_git_repo_for_new_or_exists(self) -> RepoStatus:
        """Gitリポジトリが新規かどうかチェックする."""
        if self._repo.head_is_unborn:
            self._tree = self._repo.TreeBuilder()
            self._parent = []
            return RepoStatus.NEW
        self._tree = self._repo.TreeBuilder(self._repo.head.peel().tree.id)
        self._parent = [self._repo.head.target]
        return RepoStatus.EXISTING

    def _git_add_blob_from_file(self, file_path: str) -> bool:
        """ファイルを元にblobを作成する."""
        try:
            self._blob_id: pygit2.Oid = self._repo.create_blob_fromdisk(file_path)
            return self._blob_id is not None

        except Exception:
            self._logger.exception("Error: _git_add_blob_from_file")
            return False

    # private method
    def _git_add_blob_from_data(self, file_contents: bytes) -> bool:
        """ファイルの内容を元にblobを作成する."""
        try:
            self._blob_id: pygit2.Oid = self._repo.create_blob(file_contents)
            return self._blob_id is not None

        except Exception:
            self._logger.exception("Error: _git_add_blob_from_data")
            return False

    # private method
    def _git_add_tree_from_blob(self, file_name: str) -> bool:
        """blobをtreeに追加する."""
        try:
            self._tree.insert(file_name, self._blob_id, pygit2.GIT_FILEMODE_BLOB)
            self._commit_tree: pygit2.Oid = self._tree.write()
            return self._commit_tree is not None

        except Exception:
            self._logger.exception("Error: _git_add_tree_from_blob")
            return False

    # private method
    def _git_add_index(self) -> bool:
        """indexに追加する."""
        try:
            self._repo.index.write()
            return True
        except Exception:
            self._logger.exception("Error: _git_add_index")
            return False

    def _git_config_commit_auther(self, username: str, useremail: str) -> bool:
        """commitのautherを設定する."""
        name: str
        addr: str
        name, addr = email.utils.parseaddr(useremail)
        if not email.utils.validate_email(addr):
            self._logger.warning("Warning: Email format invalid. before parse: %s . after parse: %s %s", useremail, name, addr)
            return False
        self._author = pygit2.Signature(username, useremail)
        return self._author is not None

    # private method
    def _git_commit(self, reference_name: str, commit_message: str) -> bool:
        """Git commit."""
        try:
            self._commit_hash = self._repo.create_commit(reference_name, self._author, self._author, commit_message, self._tree, self._parent)
            return self._commit_hash is not None
        except Exception:
            self._logger.exception("Error: _git_commit")
            return False

⇧ のような感じの実装になりました。

「pygit2」での「bare repository」に対する実装を調べるのに疲弊して、動作確認はできていません...

本当は、「Ruby」製のライブラリ「Oxidized」を利用している環境なので、「Oxidized」の内部で「Rugged」という「libgit2」の「Ruby」ラッパーによって、「git commit」部分までは済んでいる状態なので、「git push」部分だけ実現できれば良いのだけど。

とりあえず、「pygit2」について、「bare repository」に対する処理のAPIは、どれを利用すれば良いかについては公式のドキュメントに実装例を載せて欲しいですな...

毎度モヤモヤ感が半端ない…

今回はこのへんで。