I was exploring the python-git package and how the version differences between the version of python-git you get using apt-get install python-git on Ubuntu 12.04 and 13.10 seems to break a program I use. In my investigation I was trying to sort out how the library worked. I found a new python idiom that I’m having a hard time accepting as a “good thing” or a path to chaos.
- ubuntu 12.04 will put version 0.1.6 of the library in your python lib. The project is hosted at:”firstname.lastname@example.org:git-python/git-python.git” With version “9a4b1d4 Bumped version 0.1.6”
- ubuntu 13.04 will put version 0.3.2-rc1 from github remote : “https://github.com/gitpython-developers/GitPython.git” With version “0e9eef4 Bumped version to 0.3.2 RC1”
- It looks like the development moved to gitbub after 96c7ac239a2c9e303233e58daee02101cc4ebf3d — 0.3.1-beta2.
The API change that broke a program I was using between 0.1.6 and 0.3.2-rc1 was “Repo.commits_between” it was removed. After some tracing through the 0.1.6 version of the code I find commits_between calls a function “commit.find_all” and then looking at its implementation I see it call repo.git.rev_list(…) function. And through some grepping I see that there is in fact NO DEFINITION of rev_list in the python code! This was a head scratcher.
After some more digging I see the repo.git is an object defined by cmd.py of class Git. and it has a _call_process member that gets invoked as a side effect of __getattr__ and uses a “dashify” trick to change any attempts to call a git command using the convention of using underscores instead of dashes to map to calling the popen-ing the correct program.
Basically with this trick *every* git API is available as a python call. So repo.git.rev_list maps to a popen call too “git-rev-list”
WRT the loss of “commits_between” function in the library, as this function was a one-liner calling another one-liner it can be replaced with a one-liner “reverse( Repo.git.rev_list(sha1,sha2))” FWIW I do agree with the removal of the trivial one liner abstractions in the older git-python version. Still, I feel abused by the lack of introspection and confusion this implementation has provided.
I don’t know if I should be irritated by this idiom of creating an indeterminate number of callables implicitly or be impressed by the crazy shit I could do with it.
It seems that __getattr__ is the last in a series of python look ups when the interpreter evaluated a statement. So if there is no existing match it eventually falls through to __getattr__ and at this point you can do almost anything.
- commits_between was replaced by iter_commits on version 0.2 (says the project changes.rst file)
- The tutorial.rst calls out how to use git directly and calls out what I just learned in my reverse engineering. Guess I could have read the tutorial first but, what’s the fun in that? I wouldn’t have learned how it works to provide the direct access to git commands if I stopped there.