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]: ''