GitPythonを使う

  • 7
    Like
  • 0
    Comment

GitPythonを使う

この記事は Git AdventCalendar 2016 17日目の記事です。

GitPythonはGitの操作を行うためのPythonのライブラリです。
今回はよく行うGitの操作をGitPythonで行います。

環境は以下を使います。

  • macOS-0.12.1
  • Python-3.5.2
  • GitPython-2.0.10.dev0

インストール

$ pip install GitPython

このドキュメント作成時にはGitPythonの内部を知りたかったのでGithubからソースをcloneしてeditable install しました。

$ git clone git@github.com:gitpython-developers/GitPython.git
$ cd GitPython
$ pip install -e .

pip install -e .-e オプションはエディタブルインストールをするもので、
これを実行するとPyPIからパッケージを取得するのではなく、
指定したディレクトリにあるソースを使用してライブラリを利用できます。開発時に便利です。

内部を見るために

GitPythonはgitコマンドをsubprocessで実行する実装でした(一部そうじゃないところもあります)。
subprocess.Popen()にコマンドを渡す前にprintで表示すれば、
挙動がわかりやすそうだったのでソースに手を加えました。

diff --git a/git/cmd.py b/git/cmd.py
index 1481ac8..8f47895 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -564,6 +564,7 @@ class Git(LazyMixin):
         log.debug("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s)",
                   command, cwd, universal_newlines, shell)
         try:
+            print('[EXECUTE] {}'.format(' '.join(command)))
             proc = Popen(command,
                          env=env,
                          cwd=cwd,

この状態でGitPythonのコマンドを実行すると以下のようになります。

In [2]: git.Repo.init('fish')
[EXECUTE] git init
Out[2]: <git.Repo "/working/git-python-example/fish/.git">

[EXECUTE] の所は実際に実行されたgitコマンドですね。今回はこれで行きます。

repo.git

repo.gitを使ってCLI操作と同等のことができます。

git reset --hard HEAD は以下のようになります。

In [62]: repo.git.reset('--hard', 'HEAD')
[EXECUTE] git reset --hard HEAD
Out[62]: 'HEAD is now at 72a4b4c add b'

git checkout -b fish は以下のようになります。

In [63]: repo.git.checkout('-b', 'fish')
[EXECUTE] git checkout -b fish
Out[63]: ''

cherry-pick は名前に - が付いていてどうやって指定すればいいのかと思いますが
(Pythonでは関数名に - は使えないですよね)、cherry_pickとすれば良いです。

In [65]: repo.git.cherry_pick('7465612')
[EXECUTE] git cherry-pick 7465612
Out[65]: '[master d7382d6] add d\n Date: Sat Dec 17 15:10:40 2016 +0900\n 1 file changed, 0 insertions(+), 0 deletions(-)\n create mode 100644 D'

ならば全てこのやり方で良いように思えます。
ただ、repo.indexなどが持つメソッドには意味のある引数名が付いていたり、
--dry-run を事前に実行するものがあったりと、
素でやるよりも考慮されているところがあります。

よく使う操作

ここからは repo.git でオプションを直接渡す以外の方法がどのようなコマンドを
発行するのかみて行きます。

git init

In [2]: repo = git.Repo.init()
[EXECUTE] git init

git add

In [3]: !touch README

In [4]: repo.index.add(['README'])
Out[4]: [(100644, e69de29bb2d1d6434b8b29ae775ad8c2e48c5391, 0, README)]

git commit

In [6]: repo.index.commit('initial commit')
[EXECUTE] git cat-file --batch-check
[EXECUTE] git cat-file --batch
Out[6]: <git.Commit "c1f08a997733cea1124bceefeef67f8bbfdcdd0a">

git reset

In [14]: repo.index.reset()
[EXECUTE] git read-tree --index-output=/working/git-python-example/repo2/.git/06yx3zdq HEAD
Out[14]: <git.index.base.IndexFile at 0x104959458>

git checkout

In [21]: repo.index.checkout(['README'], force=True)
[EXECUTE] git checkout-index --index --force --stdin
Out[21]: ['README']

git checkout -b

ブランチを作成するのはrepo.indexだと難しかったので、repo.gitを使いました。

In [28]: repo.git.checkout('master', b='test')
[EXECUTE] git checkout -b test master
Out[28]: ''

git merge

In [35]: repo.index.merge_tree('test').commit('merged')
[EXECUTE] git read-tree --aggressive -i -m test
Out[35]: <git.Commit "13b98e27f44bd5a40524fe9c8573c40cc7d71168">

git pull

In [36]: repo.create_remote('origin', 'git@github.com:TakesxiSximada/testing.git')
[EXECUTE] git remote add origin git@github.com:TakesxiSximada/testing.git
Out[36]: <git.Remote "origin">

git push

In [72]: remote.push('master')
[EXECUTE] git push --porcelain origin master
Out[72]: [<git.remote.PushInfo at 0x104a04eb8>]

--porcelain は出力を解析しやすい形式にするためのオプションですね。

git clone

cloneはgit.Git.clone())を使います。

In [13]: git.Git().clone('git@github.com:TakesxiSximada/testing.git')
[EXECUTE] git clone git@github.com:TakesxiSximada/testing.git
Out[13]: ''

This post is the No.17 article of Git Advent Calendar 2016