For some reason I do not have and cannot have git installed on some servers, but I still needed to connect to github. Of course, I was bound to find some pure Python git module. There seems to be a few options, but the main one is Dulwich, which is even referenced in the git book.
Dulwich can do a lot with git (much more than what I needed). It can handle higher and lower level git APIs. The lower level are known as plumbing, the higher level as porcelain (because the porcelain is the interface between the user and the plumbing. If it feels like a toilet joke, it’s because it is.)
One issue I had while using Dulwich was to find documentation, mostly about the use of tokens and proxy with github https access.
Eventually, by reading some examples the code, I ended up finding everything I needed, soI give here a complete commented example of a (basic: clone/add/commit/push) porcelain flow.
You can find the file on my github as well.
from os import environ from pathlib import Path from tempfile import TemporaryDirectory from dulwich import porcelain from dulwich.repo import Repo from urllib3 import ProxyManager ## Configuration settings: # Source url of the repo (Note: https here. ssh would work as well, a bit differently). GITURL = "https://github.com/lomignet/thisdataguy_snippets" # Gihthub token: https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token TOKEN = "12345blah" ## /end of configuration. # If the environment variable https_proxy exists, we need to tell Dulwich to use a proxy. if environ.get("https_proxy", None): pool_manager = ProxyManager(environ["https_proxy"], num_pools=1) else: pool_manager = None with TemporaryDirectory() as gitrootdir: gitdir = Path(gitrootdir) / "repo" print("Cloning...") repo = porcelain.clone( GITURL, password=TOKEN, # Tokens are kinda public keys, no need for a username but it still needs to be provided for Dulwich. username="not relevant", target=gitdir, checkout=True, pool_manager=pool_manager, ) print("Cloned.") # Do something clever with the files in the repo, for instance create an empty readme. readme = gitdir / "readme.md" readme.touch() porcelain.add(repo, readme) print("Committing...") porcelain.commit(repo, "Empty readme added.") print("Commited.") print("Pushing...") porcelain.push( repo, remote_location=GITURL, refspecs="master", # branch to push to password=TOKEN, # Tokens are kinda public keys, no need for a username but it still needs to be provided for Dulwich. username="not relevant", pool_manager=pool_manager, ) # Note: Dulwich 0.20.5 raised an exception here. It could be ignored but it was dirty: # File ".../venv/lib/python3.7/site-packages/dulwich/porcelain.py", line 996, in push # (ref, error.encode(err_encoding))) # AttributeError: 'NoneType' object has no attribute 'encode' # Dulwich 0.20.6 fixed it. print("Pushed.")