├── .gitignore ├── README.md ├── archivy_git └── __init__.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | build/ 3 | archivy_git.egg-info/ 4 | __pycache__/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `archivy-git` allows users to use version control with their [Archivy](https://archivy.github.io) instance. 2 | 3 | It is an official extension developed by [archivy](https://github.com/archivy/) 4 | 5 | ## Install 6 | 7 | You need to have `archivy` already installed. 8 | 9 | Run `pip install archivy_git`. 10 | 11 | ## Usage 12 | 13 | ```bash 14 | $ archivy git --help 15 | Usage: archivy git [OPTIONS] COMMAND [ARGS]... 16 | 17 | Options: 18 | --help Show this message and exit. 19 | 20 | Commands: 21 | pull Pulls changes from remote to local repository. 22 | push Pushes local changes to the remote. 23 | setup Creates and sets up git repository. 24 | ``` 25 | 26 | Use the `setup` command to create and configure a new, empty git repository. If you connect your repo to a private GitHub repository, keep in mind you can no longer login with your username / password and you need to create a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). 27 | 28 | You can also just clone an existing repository, in which case you don't need to run setup 29 | 30 | Then you can periodically `pull/push` through the command line. **The plugin is not yet compatible with the web interface.** 31 | 32 | However, it can also be useful to automatically push changes when you make an edit or create a new note / bookmark. To do this, you'll need to configure a [Hook](https://archivy.github.io/reference/hooks). 33 | 34 | These are events that Archivy exposes and that you can configure. 35 | 36 | To do so, run `archivy hooks` to edit the file and create it if it doesn't exist. 37 | 38 | We can use the `sync_dataobj` `archivy-git` method to sync changes when they are made. 39 | 40 | Example: 41 | 42 | ```python 43 | from archivy.config import BaseHooks 44 | class Hooks(BaseHooks): 45 | 46 | def on_edit(self, dataobj): 47 | from archivy_git import sync_dataobj 48 | sync_dataobj(dataobj) # syncs / pushes changes 49 | 50 | def on_dataobj_create(self, dataobj): 51 | # the same for creation 52 | from archivy_git import sync_dataobj 53 | sync_dataobj(dataobj) 54 | ``` 55 | -------------------------------------------------------------------------------- /archivy_git/__init__.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | import click 4 | import git as gitpython 5 | 6 | from archivy import app 7 | from archivy.models import DataObj 8 | 9 | # https://gitpython.readthedocs.io/en/stable/reference.html#git.remote.PushInfo 10 | ERROR_CODES = [1024, 8, 32, 16] 11 | 12 | 13 | def check_errored(flags): 14 | # gitpython returns flags to be checked by doing an AND with specified codes 15 | return any([flags & error_code for error_code in ERROR_CODES]) 16 | 17 | 18 | def get_repo(): 19 | with app.app_context(): 20 | return gitpython.Repo(app.config["USER_DIR"]) 21 | 22 | 23 | @click.group() 24 | def git(): 25 | pass 26 | 27 | 28 | @git.command() 29 | def setup(): 30 | """Creates and sets up git repository.""" 31 | with app.app_context(): 32 | click.echo(f"Creating new git repo in {app.config['USER_DIR']}...") 33 | repo = gitpython.Repo.init(app.config["USER_DIR"]) 34 | branch = click.prompt("Main branch", type=str, default="main") 35 | repo.index.add("data/") 36 | repo.index.commit("Initial commit") 37 | repo.active_branch.rename(branch) 38 | 39 | while True: 40 | remote_url = click.prompt( 41 | "Enter the url of the remote you'd like to sync to. " 42 | "Ex: https://github.com/archivy/archivy", 43 | type=str, 44 | ) 45 | username = click.prompt("Enter your username", type=str) 46 | password = click.prompt( 47 | "Enter your personal access token", type=str, hide_input=True 48 | ) 49 | remote_url = remote_url.replace( 50 | "https://", f"https://{username}:{password}@" 51 | ) 52 | origin = repo.create_remote("origin", remote_url) 53 | if origin.exists(): 54 | break 55 | click.echo("Remote does not exist.") 56 | origin.push(branch) 57 | origin.fetch() 58 | repo.active_branch.set_tracking_branch(getattr(origin.refs, branch)) 59 | click.echo("Successfully setup repository.") 60 | 61 | 62 | @git.command() 63 | @click.argument("paths", type=click.Path(), nargs=-1) 64 | def push(paths): 65 | """Pushes local changes to the remote.""" 66 | repo = get_repo() 67 | if not paths or "." in paths: 68 | repo.git.add(all=True) 69 | else: 70 | with app.app_context(): 71 | prefixed_paths = [ 72 | os.path.join(app.config["USER_DIR"], path) for path in paths 73 | ] 74 | repo.index.add(prefixed_paths) 75 | repo.index.commit("Sync local changes to remote git repo.") 76 | push_event = repo.remotes.origin.push()[0] 77 | if check_errored(push_event.flags): 78 | click.echo(push_event.summary) 79 | else: 80 | click.echo("Successfully pushed changes to remote!") 81 | 82 | 83 | @git.command() 84 | def pull(): 85 | """Pulls changes from remote to local repository.""" 86 | repo = get_repo() 87 | pull_event = repo.remotes.origin.pull()[0] 88 | if check_errored(pull_event.flags): 89 | click.echo(f"Error during pull. {pull_event.note}") 90 | else: 91 | click.echo("Sucessfully pulled changes from remote!") 92 | 93 | 94 | def sync_dataobj(dataobj: DataObj): 95 | """Helper method that adds and pushes changes to a single data object.""" 96 | repo = get_repo() 97 | origin = repo.remotes.origin 98 | 99 | repo.index.add([dataobj.fullpath]) 100 | repo.index.commit(f"Changes to {dataobj.title}.") 101 | origin.push() 102 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | archivy 2 | click 3 | gitpython 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | with open('requirements.txt', encoding='utf-8') as f: 7 | all_reqs = f.read().split('\n') 8 | install_requires = [x.strip() for x in all_reqs] 9 | 10 | setup( 11 | name='archivy_git', 12 | version='0.1.4', 13 | author="Uzay-G", 14 | author_email="halcyon@disroot.org", 15 | description=( 16 | "Archivy extension to integrate your knowledge base as a syncable git repository with version control." 17 | ), 18 | long_description=long_description, 19 | long_description_content_type="text/markdown", 20 | classifiers=[ 21 | "Programming Language :: Python :: 3", 22 | "License :: OSI Approved :: MIT License" 23 | ], 24 | packages=find_packages(), 25 | install_requires=install_requires, 26 | entry_points=''' 27 | [archivy.plugins] 28 | git=archivy_git:git 29 | ''' 30 | ) 31 | --------------------------------------------------------------------------------