GitKV¶
gitkv lets you use a git repo as a key-value store using
open-like semantics.
>>> import gitkv
>>> # ... test setup ...
>>> import tempfile
>>> tmpdir = tempfile.TemporaryDirectory()
>>> # The repo url can be anything that git recognizes
>>> # as a git repo
>>> repo_url = tmpdir.name # Here it is a local path
>>> gitkv.Repo.git_init(repo_url, bare=True)
>>> # ... /test isetup ...
>>> with gitkv.open(repo_url, 'yourfile', 'w') as f:
... f.write('Your content.')
13
>>> # When exiting the with block, a commit is created.
>>> # Later...
>>> with gitkv.open(repo_url, 'yourfile') as f:
... f.read()
'Your content.'
>>> # Multiple reads and writes can happen within one with block,
>>> # to avoid creating multiple commits,
>>> # by using the Repo class
>>> with gitkv.Repo(repo_url) as repo:
... with repo.open('yourfile', 'a') as f:
... f.write('Additional content.')
... data = repo.open('yourfile').read()
... data = data.replace('Your', 'My')
... with repo.open('yourfile', 'w') as f:
... f.write(data)
... with repo.open('anotherfile', 'w') as f:
... f.write('something')
... # The commit message may be specified before the block closes
... repo.commit_message = 'Multiple edits'
19
30
9
>>> with gitkv.open(repo_url, 'yourfile') as f:
... f.read()
'My content.Additional content.'
>>> # A repo's history is accessible with the git_log() function
>>> repo = gitkv.Repo(repo_url)
>>> [repo.message(c).strip() for c in repo.git_log()]
['Multiple edits', 'GitKV: yourfile', 'GitKV: initial commit']
>>> # Which can be called on a file to get its specific history
>>> with gitkv.open(repo_url, 'anotherfile') as f:
... [f.repo.message(c).strip() for c in f.git_log()]
['Multiple edits']
>>> # show the plain content of a file in a commit
>>> with gitkv.open(repo_url, 'yourfile') as f:
... commit = [c for c in f.git_log()
... if f.repo.message(c).strip() == 'GitKV: yourfile'][0]
... f.show_blob(commit)
'Your content.'
-
class
gitkv.open(url, filename, *args, **kwargs)¶ Open a file in a repository.
It is usually instanciated as a context manager.
This method clones the repo in a local temporary directory.
When
close()is called on the returned object (e.g. when one exits from the with block), an automatic commit is added to our clone, and is then pushed to the repo aturl.-
__init__(url, filename, *args, **kwargs)¶ Return the file-like object
Parameters: - url – git repository where you want to open a file.
It can be anything that is accepted by git, such as a relative
or absolute path, a http or https url, or a
user@host:repotype url, etc. SeeRepo.__init__() - filename – the file you want to open
- kwargs (args) – all other arguments are passed as is to the
‘true’
openfunction
Returns: a stream-like object
- url – git repository where you want to open a file.
It can be anything that is accepted by git, such as a relative
or absolute path, a http or https url, or a
-
close()¶ Close our FileInRepo instance and our Repo instance.
see
Repo.close()andFileInRepo.close()
-
-
class
gitkv.Repo(url=None)¶ A git repository.
It is usually instanciated as a context manager.
The provided repo is cloned. Upon the exiting the context, a commit is created and pushed to the original repo.
An PushError exception will be raised when gitkv can’t push on the remote because of a conflict.
Some calls can be made within the context of the cloned repo, with automatic module importing:
>>> import gitkv >>> with gitkv.Repo() as repo: ... # Instead of ... # import os ... # os.makedirs(repo.path+'example/') ... # one can write: ... repo.os.makedirs('example/') ... # it works as expected ... import os ... os.path.exists(repo.path+'example/') ... # one could have written: ... repo.os.path.exists('example/') ... True True
The lookup is dynamic, any call that is not understood by
Repodirectly will lead to a call with theRepo‘s path attribute prepended to the first argument. Anything between the Repo and the function is interpreted as a module name.-
__init__(url=None)¶ Return the context manager.
Parameters: url – git repository where you want to open a file. It can be anything that is accepted by git, such as a relative or absolute path, a http or https url, or a user@host:repotype url, etc.If
urlis a directory with a non bare git repo in it, please configure your git repository beforehand:git config receive.denyCurrentBranch ignoreYou will be able to checkout your changes with:
git checkout -fIf
urlis None, an empty git repository is created in a temporary directory.
-
static
git_clone(url, path)¶ Clone the remote repo at url in path.
-
git_commit(message=None)¶ Create a commit.
-
static
git_init(path, bare=False)¶ “Initialize an empty repo at path.
-
git_log(*options, custom_filter=<function Repo.<lambda>>)¶ Return the list of commits in reverse chronological order
Parameters: - options – String array, will be passed as arguments to git log
- custom_filter – func, optional, filter commits according to an arbitrary criterion
Returns: list of commits,
-
git_pull()¶ Pull from remote repository.
-
git_push()¶ Push to remote repository.
-
initial_commit_if_empty()¶ Commit an empty .gitignore file if the given repo is empty
-
is_empty()¶ Return True self is an empty repo
-
list_files(id_commit='HEAD')¶ List all files in repo in a commit.
-
message(commit='HEAD')¶ Return the commit message of the given commit.
-
open(filename, *args, **kwargs)¶ Open a file in this Repo
Param: filename: the file you want to open. Param: *args, **kwargs: all other arguments are passed as is to the ‘true’ openfunction.Returns: a stream-like object
-
remote_sync()¶ Create a commit of our changes and push it to the remote repo.
-
-
class
gitkv.FileInRepo(filename, repo, *args, **kwargs)¶ Manage a file in a git repository.
Some calls can be made within the context of the FileInRepo object, with automatic module importing:
For example with json module:
>>> # ... test setup ... >>> # For exemple, a content type json as content_json >>> import json >>> content_json = json.loads('{"success" : "ok"}') >>> # ... /test setup ... >>> >>> # call functions of module json: >>> import gitkv >>> with gitkv.Repo() as repository: ... # call function dump of json ... with repository.open('jsonfile', mode='w') as jsonfile: ... jsonfile.json.dump(content_json) ... # call function loads of json ... with repository.open('jsonfile', mode='r') as jsonfile: ... result_json_load = jsonfile.json.load() >>> result_json_load['success'] 'ok'
This mechanism works with any module, such as the csv module, for example.
-
__init__(filename, repo, *args, **kwargs)¶ Return the context manager.
This class should be instanciated with py:func:gitkv.open or py:func:Repo.open, and not directly.
-
close()¶ Close the stream object
-
git_commit(message=None)¶ Create a commit
-
git_log(*options)¶ Return a list of all commits that modified this instance’s file, sorted from most recent to most ancient.
Parameters: options – String array, will be passed as arguments to git log Returns: list of commits. >>> # ... test setup ... >>> import tempfile >>> import gitkv >>> import time >>> tmpdir = tempfile.TemporaryDirectory() >>> # The repo url can be anything that git recognizes >>> # as a git repo >>> repo_url = tmpdir.name # Here it is a local path >>> gitkv.Repo.git_init(repo_url, bare=True) >>> # Simulate a conflict with 2 clones >>> repo_a = gitkv.Repo(repo_url) >>> with repo_a.open('myfile','w') as f: ... f.write('Create myfile') 13 >>> repo_a.git_commit('Create myfile') >>> repo_a.remote_sync() >>> repo_b = gitkv.Repo(repo_url) >>> with repo_b.open('myfile','w') as f: ... f.write('\nB write') 8 >>> repo_b.git_commit('B write') >>> with repo_b.open('otherfile','w') as f: ... f.write('Create otherfile') 16 >>> repo_b.git_commit('Create otherfile') >>> repo_b.remote_sync() >>> with repo_a.open('myfile','w') as f: ... f.write('\nA write') 8 >>> time.sleep(1) >>> repo_a.git_commit('A write') >>> # Error because a conflict >>> repo_a.remote_sync() Traceback (most recent call last): ... gitkv.PushError: Conflict when pushing >>> # Resolve this conflict >>> with repo_a.open('myfile','w') as f: ... f.write('Create myfile\nA write\nB write') 29 >>> repo_a.git_commit('Merge') >>> repo_a.remote_sync() >>> # ... /test setup ...
>>> # A commit history like this >>> # 0 <- Master Merge >>> # |__ >>> # 0 | A write >>> # | 0 Create otherfile >>> # | | >>> # | 0 B write >>> # |__| >>> # 0 Create myfile >>> # | >>> # 0 GitKV: Initial commit
>>> # Call git log of myfile >>> with gitkv.open(repo_url,'myfile') as f: ... [f.repo.message(c).strip() for c in f.git_log('--date-order')] ['Merge', 'A write', 'B write', 'Create myfile']
-
show_blob(commit='HEAD')¶ Return the contents of self at a commit
Parameters: id_commit – type str, commit hex. Returns: this file’s data >>> import gitkv >>> repo = gitkv.Repo() >>> repo.os.makedirs('dossier') >>> with repo.open('dossier/afile', 'w') as f: ... f.write('Initial') 7 >>> repo.remote_sync() >>> with repo.open('dossier/afile', 'w') as f: ... f.write('Edit') 4 >>> repo.remote_sync() >>> with repo.open('dossier/afile') as f: ... for cid in f.git_log(): ... print(f.show_blob(cid)) Edit Initial
-
-
exception
gitkv.PushError¶ Raised when gitkv can’t push in a remote repository because a conflict.
Installation¶
# pip3 install gitkv