GitPythonを使う

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

```