#!/usr/bin/env python3
#
#  git-incrypt: A git remote helper to encrypt git repositories incrementally
#  Copyright (C) 2025  Robert Schiele <rschiele@gmail.com>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# pylint: disable=invalid-name
'''
Encrypt git branches
'''

import subprocess
import tempfile
import os
import sys
import re
import hashlib
import base64
import argparse
import enum
import pygit2
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend

if not hasattr(pygit2.enums, 'FileMode'):
    class FileMode(enum.IntFlag):
        'definitions missing in older versions of pygit2'
        # pylint: disable=c-extension-no-member disable=protected-access
        TREE = pygit2._pygit2.GIT_FILEMODE_TREE
        # pylint: disable=c-extension-no-member disable=protected-access
        BLOB = pygit2._pygit2.GIT_FILEMODE_BLOB
    pygit2.enums.FileMode = FileMode
if not hasattr(pygit2.enums, 'ObjectType'):
    class ObjectType(enum.IntEnum):
        'definitions missing in older versions of pygit2'
        # pylint: disable=c-extension-no-member disable=protected-access
        COMMIT = pygit2._pygit2.GIT_OBJ_COMMIT
        # pylint: disable=c-extension-no-member disable=protected-access
        TREE = pygit2._pygit2.GIT_OBJ_TREE
        # pylint: disable=c-extension-no-member disable=protected-access
        BLOB = pygit2._pygit2.GIT_OBJ_BLOB
        # pylint: disable=c-extension-no-member disable=protected-access
        TAG = pygit2._pygit2.GIT_OBJ_TAG
    pygit2.enums.ObjectType = ObjectType

CRYPTREADME = '''# 401 Unauthorized

This is an encrypted git repository.  You can clone it, but you will not be
able to see the contents of the commits.  If you have the right key, you can
decrypt the repository using
[git-incrypt](https://github.com/schiele/git-incrypt).
'''


def cipher(key):
    'return cipher to be used'
    return Cipher(algorithms.AES(key[0:32]), modes.CBC(key[32:48]),
                  backend=default_backend())


def pad():
    'return patting to be used'
    return padding.PKCS7(algorithms.AES.block_size)


def encryptdata(data: bytes, key: bytes) -> (bytes, bytes):
    'encrypt raw data'
    e = cipher(key).encryptor()
    p = pad().padder()
    return e.update(p.update(data) + p.finalize()) + e.finalize()


def decryptdata(ciphertext: bytes, key: bytes) -> bytes:
    'decrypt raw data'
    d = cipher(key).decryptor()
    u = pad().unpadder()
    return u.update(d.update(ciphertext) + d.finalize()) + u.finalize()


def encryptrefname(ref, key):
    'encrypt a refname'
    def splitref(s):
        'split the ref from the suffix'
        match = re.search(r'[~^]', s)
        if match:
            index = match.start()
            return s[:index], s[index:]
        return s, ''

    sref = splitref(ref)
    rawname = sref[0].encode('utf-8')
    return 'refs/heads/' + base64.b64encode(encryptdata(
            hashlib.sha1(rawname).digest() + rawname, key),
                                            b'+#').decode('utf-8') + sref[1]


def decryptrefname(ref, key):
    'decrypt a refname'
    try:
        data = decryptdata(base64.b64decode(
            ref.rsplit('/', 1)[-1], b'+#'), key)
        assert hashlib.sha1(data[20:]).digest() == data[0:20], \
            'corrupted reference name'
        return data[20:].decode('utf-8')
    except ValueError:
        return None


class CryptRepo:
    'encrypted repository'
    verbosityflags = ('-q', '-q', '-v', '-vv', '-vvv')

    def __init__(self, clearname, url, init=None, forcetrust=False):
        assert clearname, 'This does not work yet outside a git repository'
        self.settings = {
            'atomic': False,
            'progress': True,
            'verbosity': 1,
            'followtags': False
        }
        hashstr = hashlib.sha1(url.encode('utf-8')).hexdigest()
        self.refprefix = f'refs/incrypt/{hashstr}/'
        if init:
            self.repo = pygit2.init_repository(clearname, bare=True)
            self.repo.remotes.create('incrypt', url)
            template = self._mktemplate(init.name, init.email,
                                        init.date, init.m)
            self.meta = MetaData(self.repo).init(init.keys, template,
                                                 'refs/heads/master')
            self.trust(force=True, sign=True)
            self.clearrepo = pygit2.Repository(clearname)
        else:
            self.clearrepo = pygit2.Repository(clearname)
            name = os.path.join(self.clearrepo.path, 'incrypt', hashstr)
            if not os.path.isdir(name):
                subprocess.run(
                    ['git', 'clone',
                     CryptRepo.verbosityflags[self.settings['verbosity']],
                     '--progress' if self.settings['progress']
                        else '--no-progress',
                     '-o', 'incrypt', '-c', 'fetch.prune=true',
                     '--mirror', url, name],
                    check=True, stdout=sys.stderr)
                subprocess.run(
                    ['git', 'config', 'unset', 'remote.incrypt.mirror'],
                    cwd=name, check=True, stdout=sys.stderr)
            self.repo = pygit2.Repository(name)
            self.meta = MetaData(self.repo).read()
            if forcetrust:
                self.trust(force=forcetrust)

    def _mktemplate(self, name, email, date, messages):
        'create a template commit'
        if not messages:
            messages = ['Encrypted by git-incrypt.',
                        'https://github.com/schiele/git-incrypt']
        env = os.environ.copy()
        if name:
            env['GIT_AUTHOR_NAME'] = name
            env['GIT_COMMITTER_NAME'] = name
        if email:
            env['GIT_AUTHOR_EMAIL'] = email
            env['GIT_COMMITTER_EMAIL'] = email
        if date:
            env['GIT_AUTHOR_DATE'] = date
            env['GIT_COMMITTER_DATE'] = date
        templateid = subprocess.check_output(
            ['git', 'commit-tree'] +
            [x for m in messages for x in ('-m', m)] +
            ['4b825dc642cb6eb9a060e54bf8d69288fbee4904'],
            cwd=self.repo.path, env=env).decode('utf-8').strip()
        template = self.repo.get(templateid).read_raw().split(b'\n', 1)[1]
        return template

    def _progress_for(self, what, iterable, function):
        'iterate over iterable and show progress'
        def update():
            'update progress'
            if self.settings['progress'] and total:
                nonlocal progress
                progress += 1
                sys.stderr.write(f'\rremote: {what}'
                                 f' {progress/total:.0%}'
                                 f' ({progress}/{total})')
                sys.stderr.flush()
        total = len(iterable)
        progress = -1
        for i in iterable:
            update()
            function(i)
        update()
        if self.settings['progress'] and total:
            sys.stderr.write(', done.\n')
            sys.stderr.flush()

    class RevMode(enum.Enum):
        'mode what to retrieve from _revlist'
        BLOB = enum.auto()
        PARENT = enum.auto()
        ALL = enum.auto()

    @staticmethod
    def _revlist(mode, incl, excl=None, cwd=None):
        'run rev-list'
        text = subprocess.check_output(
            ['git', 'rev-list', '--objects', '--no-object-names'] +
            (['--filter-provided-objects', '--filter=object:type=blob']
             if mode == CryptRepo.RevMode.BLOB else
             ['--topo-order', '--reverse', '--filter=tree:0']
             if mode == CryptRepo.RevMode.PARENT else
             ['--no-walk']) +
            ['--stdin'],
            cwd=cwd, text=True, input=''.join(
                [f'{x}\n' for x in incl] +
                ([f'^{x}\n' for x in excl] if excl else [])))
        return text.strip().split('\n') if text else []

    def trust(self, force=False, sign=False):
        'trust this repository'
        if force:
            trusted = True
        else:
            try:
                with open(os.path.join(self.repo.path, 'incrypt-keyhash'), 'r',
                          encoding='utf-8') as file:
                    expectedhash = file.read().strip()
            except FileNotFoundError:
                expectedhash = None
            if expectedhash:
                assert expectedhash == self.meta.keyhash, \
                    f'Key hash is {self.meta.keyhash}, was {expectedhash}'
                trusted = True
            else:
                trusted = self.meta.checksigs()
        assert trusted, 'Key is not trusted'
        if sign:
            self.meta.sign()
        with open(os.path.join(self.repo.path, 'incrypt-keyhash'), 'w',
                  encoding='utf-8') as file:
            file.write(self.meta.keyhash)

    def getrefs(self):
        'list all cleartext references'
        def decryptobject(oid):
            'decrypt an object'
            raw = decryptdata(self.repo.get(oid).read_raw(), self.meta.key)
            return self.clearrepo.odb.write(raw[20], raw[21:])
        subprocess.run(
            ['git', 'fetch',
             CryptRepo.verbosityflags[self.settings['verbosity']],
             '--progress' if self.settings['progress'] else '--no-progress'],
            cwd=self.repo.path, check=True, stdout=sys.stderr)
        self.meta.read()
        self.trust()
        # [ dec(crypt) , prefdec(crypt), crypt ]
        refs = [[r[0], self.refprefix + r[0], self.repo.revparse_single(r[1])]
                for r in [[decryptrefname(r, self.meta.key), r] for r in
                          filter(lambda r: len(r) > 14, self.repo.references)]]
        cryptmap = self.meta.readmap(reverse=self.clearrepo)
        self._progress_for(
            'Decrypting objects',
            CryptRepo._revlist(CryptRepo.RevMode.BLOB, [r[2].id for r in refs],
                               cryptmap, cwd=self.repo.path),
            decryptobject)
        for r in refs:
            self.clearrepo.create_reference(
                r[1], decryptdata(r[2].tree['0'].read_raw(),
                                  self.meta.key)[0:20].hex(), force=True)
        expected = [r[1] for r in refs]
        result = [['HEAD', f'@{self.meta.defaultbranch}']]
        for r in self.clearrepo.references:
            if r.startswith(self.refprefix):
                if r in expected:
                    result.append([r[len(self.refprefix):],
                                   self.clearrepo.lookup_reference(r).target])
                else:
                    self.clearrepo.references.delete(r)
        return result

    def push(self, refs):
        'push refs'
        def encryptobject(oid):
            'encrypt an object'
            clear = self.clearrepo.get(oid)
            cryptobjs[clear.id] = self.repo.create_blob(encryptdata(
                clear.id.raw + clear.type.to_bytes(1, byteorder='big') +
                clear.read_raw(), self.meta.key))

        def createcommit(oid):
            'create a commit'
            def collectorinsert(collector, oid):
                'insert an object into the collector'
                collector.insert(str(oid), oid, pygit2.enums.FileMode.BLOB)

            def encrypttree(tree):
                'encrypt a tree'
                for obj in tree:
                    if obj.type == pygit2.enums.ObjectType.TREE:
                        encrypttree(obj)
                    elif obj.type == pygit2.enums.ObjectType.BLOB:
                        collectorinsert(collector, cryptobjs[obj.id])
                collectorinsert(collector, cryptobjs[tree.id])

            obj = self.clearrepo.get(oid)
            collector = self.repo.TreeBuilder()
            if obj.type == pygit2.enums.ObjectType.COMMIT:
                encrypttree(obj.tree)
            elif obj.type != pygit2.enums.ObjectType.TAG:
                raise TypeError(
                    f'Unexpected type {obj.type_str} in git history')
            collector.insert('0', cryptobjs[obj.id],
                             pygit2.enums.FileMode.BLOB)
            colid = collector.write()
            cryptmap[str(obj.id)] = self.meta.secretcommit(
                colid, [cryptmap[str(c)] for c in obj.parent_ids]
                if obj.type == pygit2.enums.ObjectType.COMMIT
                else [cryptmap[str(obj.target)]])

        xrefs = [[r[0], r[1], r[3],
                  f"{'+' if r[2] else ''}{r[3] if r[0] else ''}:{r[3]}",
                  self.clearrepo.revparse_single(r[0]) if r[0] else None]
                 for r in [[r[0], r[1], r[2], encryptrefname(r[1],
                           self.meta.key)] for r in refs]]
        cryptmap = self.meta.readmap()
        colobjs = CryptRepo._revlist(
            CryptRepo.RevMode.PARENT, [r[4].id for r in xrefs if r[4]],
            cryptmap)
        cryptobjs = {}
        self._progress_for('Encrypting objects',
                           CryptRepo._revlist(CryptRepo.RevMode.ALL, colobjs),
                           encryptobject)
        self._progress_for('Creating encrypted commits', colobjs, createcommit)
        self.meta.write(cryptmap)
        for r in xrefs:
            if r[4]:
                self.repo.create_reference(r[2], cryptmap[str(r[4].id)],
                                           force=True)
            else:
                try:
                    self.repo.references.delete(r[2])
                except KeyError:
                    pass
        resdict = {}
        for line in subprocess.run(
                ['git', 'push'] +
                ([CryptRepo.verbosityflags[self.settings['verbosity']]]
                 if self.settings['verbosity'] > 1 else []) +
                ['--progress' if self.settings['progress']
                    else '--no-progress', '--porcelain'] +
                (['--atomic'] if self.settings['atomic'] else []) +
                ['incrypt', f'+{MetaData.REFNAME}:{MetaData.REFNAME}'] +
                [r[3] for r in xrefs],
                cwd=self.repo.path, check=False, text=True,
                stdout=subprocess.PIPE,
                stderr=sys.stderr if self.settings['verbosity'] > 1
                else subprocess.DEVNULL
                ).stdout.split('\n'):
            e = line.split('\t', 2)
            if len(e) == 3:
                resdict[e[1].split(':', 1)[1]] = \
                    e[2].split('(', 1)[1][0:-1] if e[0] == '!' else None
        return {r[1]: resdict[r[2]] for r in xrefs}


def remotehelperloop():
    'communication loop'
    def fetchcommand(line):
        'parse fetch batch'
        while line:
            command = line.split()
            assert command[0] == 'fetch', \
                f'Unexpected command in fetch batch: {line}'
            line = sys.stdin.readline().strip()
        sys.stdout.write('\n')

    def pushcommand(line):
        'parse push batch'
        refs = []
        while line:
            command = line.split()
            assert command[0] == 'push', \
                f'Unexpected command in push batch: {line}'
            src, dst = command[1].split(':')
            force = len(src) and src[0] == '+'
            if force:
                src = src[1:]
            refs.append([src, dst, force])
            line = sys.stdin.readline().strip()
        results = crypt.push(refs)
        sys.stdout.write('{}\n'.format(
            ''.join([(f'error {r} {e}\n' if e else f'ok {r}\n')
                     for r, e in results.items()])))

    crypt = CryptRepo(os.environ.pop('GIT_DIR', None), sys.argv[2])
    while True:
        sys.stdout.flush()
        line = sys.stdin.readline().strip()
        if not line:
            break
        command = line.split()
        if command[0] == 'capabilities':
            sys.stdout.write('fetch\npush\noption\n\n')
        elif command[0] == 'list':
            sys.stdout.write('{}\n'.format(
                ''.join([f'{r[1]} {r[0]}\n' for r in crypt.getrefs()])))
        elif command[0] == 'fetch':
            fetchcommand(line)
        elif command[0] == 'push':
            pushcommand(line)
        elif command[0] == 'option':
            if command[1] in ['atomic', 'progress', 'verbosity', 'followtags']:
                crypt.settings[command[1]] = command[2] == 'true' \
                    if isinstance(crypt.settings[command[1]], bool) \
                    else int(command[2])
                sys.stdout.write('ok\n')
            else:
                sys.stdout.write('unsupported\n')
        else:
            raise RuntimeError(f'Unknown command: {line}')


class MetaData:
    'container for meta data'
    VER = b'git-incrypt\n1.0.0\n'
    KEYVER = b'AES-256-CBC+IV'
    REFNAME = 'refs/heads/_'

    def __init__(self, repo):
        self.repo = repo
        self.files = None
        self.key = None
        self.keyhash = None
        self.template = None
        self.defaultbranch = None
        self.gpgkeys = []

    def _gpg(self, args, inp):
        'run gpg'
        return subprocess.check_output(
                [f'gpg@incrypt::{self.repo.remotes["incrypt"].url}'] + args,
                executable='gpg', input=inp)

    def init(self, gpgkeys, template, defaultbranch):
        'initialize the metadata'
        self.files = {}
        self.files['ver'] = self.repo.create_blob(MetaData.VER)
        self.key = os.urandom(48)
        keyhashbase = MetaData.KEYVER + b'\x00' + self.key
        self.keyhash = hashlib.sha1(keyhashbase).hexdigest()
        cryptedkey = self._gpg(
            ['-q', '-e'] + ['-r' + k for k in gpgkeys], keyhashbase)
        self.files['key'] = self.repo.create_blob(cryptedkey)
        self.files['sig'] = self.repo.TreeBuilder().write()
        self.template = template
        self.files['msg'] = self.repo.create_blob(encryptdata(
            hashlib.sha1(template).digest() + template, self.key))
        self.defaultbranch = defaultbranch
        encodedbranch = defaultbranch.encode('utf-8')
        self.files['def'] = self.repo.create_blob(encryptdata(
            hashlib.sha1(encodedbranch).digest() + encodedbranch, self.key))
        self.write()
        return self

    def addkey(self, gpgkeys):
        'add gpg key'
        self.gpgkeys += gpgkeys
        cryptedkey = self._gpg(
            ['-q', '-e'] + ['-r' + k for k in self.gpgkeys],
            MetaData.KEYVER + b'\x00' + self.key)
        self.files['key'] = self.repo.create_blob(cryptedkey)

    def read(self):
        'read the metadata'
        self.files = {}
        tree = self.repo.revparse_single(MetaData.REFNAME).tree
        obj = tree['ver']
        self.files['ver'] = obj.id
        data = obj.read_raw()
        assert data == MetaData.VER, \
            f'Version format is {data}, expected {MetaData.VER}'
        obj = tree['key']
        self.files['key'] = obj.id
        data = self._gpg(['-q', '-d'], obj.read_raw())
        newkeyhash = hashlib.sha1(data).hexdigest()
        if self.keyhash:
            assert newkeyhash == self.keyhash, \
                f'Key hash is {newkeyhash}, was {self.keyhash}'
        self.keyhash = newkeyhash
        keyver, self.key = data.split(b'\x00', 1)
        assert keyver == MetaData.KEYVER, \
            f'Key format is {keyver}, expected {MetaData.KEYVER}'
        keydata = self._gpg(['-q', '--list-packets'],
                            obj.read_raw()).decode('utf-8').split('\n')
        for k in keydata:
            match = re.search(r'^:pubkey enc packet:.*keyid ([0-9A-F]+)', k)
            if match:
                self.gpgkeys.append(match.group(1))
        self.files['sig'] = tree['sig'].id
        obj = tree['msg']
        self.files['msg'] = obj.id
        data = decryptdata(obj.read_raw(), self.key)
        assert hashlib.sha1(data[20:]).digest() == \
            data[0:20], 'corrupted template'
        self.template = data[20:]
        obj = tree['def']
        self.files['def'] = obj.id
        data = decryptdata(obj.read_raw(), self.key)
        assert hashlib.sha1(data[20:]).digest() == \
            data[0:20], 'corrupted default branch information'
        self.defaultbranch = data[20:].decode('utf-8')
        return self

    def sign(self):
        'sign key'
        sig = self._gpg(['-q', '-b', '-s'], self.key)
        sigfile = self.repo.create_blob(encryptdata(
            hashlib.sha1(sig).digest() + sig, self.key))
        sigtree = self.repo.TreeBuilder(self.repo.get(self.files['sig']))
        sigtree.insert(str(sigfile), sigfile, pygit2.enums.FileMode.BLOB)
        self.files['sig'] = sigtree.write()

    def checksigs(self):
        'check signatures'
        trusted = False
        for sig in self.repo.get(self.files['sig']):
            decrypted = decryptdata(sig.read_raw(), self.key)
            assert hashlib.sha1(decrypted[20:]).digest() == \
                decrypted[0:20], 'corrupted signature'
            with tempfile.NamedTemporaryFile(delete=False) as tmp:
                tmp.write(decrypted[20:])
                tmp.flush()
                sigfile = tmp.name
            try:
                sigoutput = self._gpg(
                    ['--verify', '--status-fd', '1', sigfile, '-'], self.key
                    ).decode('utf-8').strip().split('\n')
                sigresults = [d.split()[1] for d in sigoutput]
                if 'GOODSIG' in sigresults and \
                        ('TRUST_ULTIMATE' in sigresults or
                         'TRUST_FULLY' in sigresults):
                    trusted = True
            finally:
                os.remove(sigfile)
        return trusted

    def secretcommit(self, tree, parents):
        'create secret commit wrapper'
        raw = b'tree ' + str(tree).encode('utf-8') + b'\n'
        for parent in parents:
            raw += b'parent ' + str(parent).encode('utf-8') + b'\n'
        raw += self.template
        return str(self.repo.odb.write(pygit2.enums.ObjectType.COMMIT, raw))

    def write(self, cryptmap=None):
        'write the metadata'
        collector = self.repo.TreeBuilder()
        for name, file in self.files.items():
            collector.insert(name, file, pygit2.enums.FileMode.TREE
                             if name == 'sig' else pygit2.enums.FileMode.BLOB)
        if not cryptmap:
            cryptmap = {}
        rawdata = b''
        for a, b in cryptmap.items():
            rawdata += bytes.fromhex(a) + bytes.fromhex(b)
        mapfile = self.repo.create_blob(encryptdata(
            hashlib.sha1(rawdata).digest() + rawdata, self.key))
        collector.insert('map', mapfile, pygit2.enums.FileMode.BLOB)
        readmefile = self.repo.create_blob(CRYPTREADME.encode('utf-8'))
        collector.insert('README.md', readmefile, pygit2.enums.FileMode.BLOB)
        colid = collector.write()
        commit = self.secretcommit(colid, [])
        self.repo.create_reference(MetaData.REFNAME, commit, force=True)
        return self

    def readmap(self, reverse=None):
        'read the mapping table'
        tree = self.repo.revparse_single(MetaData.REFNAME).tree
        o = 20 if reverse else 0
        processed = {}
        rawdata = decryptdata(tree['map'].read_raw(), self.key)
        for i in range(20, len(rawdata), 40):
            target = rawdata[i+20-o:i+40-o].hex()
            if target in (reverse if reverse else self.repo):
                processed[rawdata[i+o:i+20+o].hex()] = target
        return processed


def init_command(args):
    'init incrypt repository'
    assert args.url.startswith('incrypt::'), \
        'url must start with incrypt::'
    with tempfile.TemporaryDirectory() as tempdir:
        crypt = CryptRepo(tempdir, args.url[9:], init=args)
        crypt.push([])


def addkey_command(args):
    'add key to incrypt repository'
    try:
        url = pygit2.Repository('.').remotes[args.remote].url
    except (KeyError, ValueError):
        url = args.remote
    assert url.startswith('incrypt::'), \
        f'url `{url}` must start with incrypt::'
    crypt = CryptRepo('.', url[9:])
    crypt.meta.addkey(args.keys)
    crypt.push([])


def trust_command(args):
    'trust incrypt repository'
    try:
        url = pygit2.Repository('.').remotes[args.remote].url
    except (KeyError, ValueError):
        url = args.remote
    assert url.startswith('incrypt::'), \
        f'url `{url}` must start with incrypt::'
    crypt = CryptRepo('.', url[9:], forcetrust=True)
    if args.sign:
        crypt.meta.sign()
        crypt.push([])


def tool():
    'git incrypt tool implementation'
    parser = argparse.ArgumentParser(
        description='git inccrypt: '
        'A git remote helper to encrypt git repositories incrementally.')
    subparsers = parser.add_subparsers(
        title='subcommands', dest='command', help='Available commands')
    subparsers.required = True
    init_parser = subparsers.add_parser(
        'init', help='Initialize the project')
    init_parser.add_argument(
        '-n', '--name', type=str, default=None,
        help='Name of the author of the encrypted commits')
    init_parser.add_argument(
        '-e', '--email', type=str, default=None,
        help='Email of the author of the encrypted commits')
    init_parser.add_argument(
        '-d', '--date', type=str, default=None,
        help='Seconds since the epoch when the encypted commits are authored')
    init_parser.add_argument(
        '-m', action='append', type=str, default=[],
        help='Message in encrypted commits')
    init_parser.add_argument(
        'url', type=str, help='URL of the remote repository')
    init_parser.add_argument(
        'keys', nargs='+', help='GPG keys used to encrypt the data')
    init_parser.set_defaults(func=init_command)
    addkey_parser = subparsers.add_parser(
        'addkey', help='Add an encryption key')
    addkey_parser.add_argument(
        'remote', type=str, help='Name of the remote')
    addkey_parser.add_argument(
        'keys', nargs='+', help='GPG keys to add')
    addkey_parser.set_defaults(func=addkey_command)
    trust_parser = subparsers.add_parser(
        'trust', help='Trust the remote repository')
    trust_parser.add_argument(
        'remote', nargs='?', default='origin',
        help='Name of the remote')
    trust_parser.add_argument(
        '-s', '--sign', action='store_true',
        help='Sign the key')
    trust_parser.set_defaults(func=trust_command)
    args = parser.parse_args()
    args.func(args)


cmd = sys.argv[0].rsplit('/', 1)[-1]

if cmd == 'git-remote-incrypt':
    remotehelperloop()
elif cmd == 'git-incrypt':
    tool()

# vim: set expandtab shiftwidth=4 tabstop=4 :
