#!/usr/bin/env python
"""xx-encrypted-nodeps

Merge GPG encrypted text files using Mercurial and on dependencies.
(For a graphical diff version of this, see xx-encrypted.)

Use this in your .hgrc ::

  [merge-tools]
  xx-encrypted-nodeps = 
  xx-encrypted-nodeps.priority = 100
  xx-encrypted-nodeps.premerge = False
  xx-encrypted-nodeps.args = $local $base $other -o $output
  
  [merge-patterns]
  **.asc = xx-encrypted-nodeps
  **.gpg = xx-encrypted-nodeps

"""
__author__ = "Martin Blais <blais@furius.ca>"
__license__ = "BSD License"

import sys, os, re
from os.path import *
from tempfile import NamedTemporaryFile
from subprocess import Popen, PIPE


def get_recipient(fn):
    """ Extract the recipient/key name from the given encrypted text, without
    saving any temporary file. """
    p = Popen(('gpg', '--list-only', fn), stdin=PIPE, stdout=PIPE, stderr=PIPE)
    out, err = p.communicate()
    mo = re.compile('.*public key is ([0-9A-F]+)', re.M).search(err)
    if mo:
        return mo.group(1)

def decrypt_file(fn):
    "Read the given filename and decrypt it, and return its string contents."
    p = Popen(('gpg', '--quiet', '--decrypt', '--use-agent', '--no-permission-warning', fn),
              shell=0, stdout=PIPE, stderr=PIPE)
    out, err = p.communicate()
    if p.returncode == 0 and out:
        return out
    else:
        raise ValueError("Errors decrypting '%s': %s" % (fn, err))
    
def encrypt_file(fn, armor=False):
    "Encrypt the given filename and return its string contents."
    cmd = ['gpg', '--encrypt', '--use-agent', '--no-permission-warning', '--output=-']
    if armor:
        cmd.append('--armor')
    cmd.append(fn)
    p = Popen(cmd, shell=0, stdout=PIPE, stderr=PIPE)
    out, err = p.communicate()
    if p.returncode == 0 and out:
        return out
    else:
        raise ValueError("Errors decrypting '%s': %s" % (fn, err))


# Note: you could probably use vimdiff too, or xxdiff (see xx-encrypted, which
# is better than this script).

diffcmd = r'emacs-merge %(mine)s %(older)s %(yours)s %(output)s'

def main():
    import optparse
    parser = optparse.OptionParser(__doc__.strip())
    parser.add_option('-o', '--output', action='store', help="Specify the output filename.")
    opts, args = parser.parse_args()
    if len(args) != 3:
        parser.error("Usage: [MINE] [OLDER] [YOURS] -o [OUTPUT]")

    output = opts.output or mine

    idfile = args[0]

    # Find out who the recipient is.
    recipient = get_recipient(idfile)

    # Read and decrypt the files to temporary files (WARNING: not very safe, but
    # easy enough to do).
    temps, filemap = [], {}
    for kind, fn in zip('mine older yours output'.split(), args + [output]):
        f = NamedTemporaryFile(prefix='hg-merge-gpg')
        if kind != 'output':
            content = decrypt_file(fn)
            f.write(content)
        temps.append(f)
        filemap[kind] = f.name
        
    # Invoke the diff program.
    filemap['conf'] = os.environ['CONF']
    cmd = diffcmd % filemap
    p = Popen(cmd, shell=1)
    out, err = p.communicate()

    # Encrypt the merged file.
    armor = re.match(r'.*\.asc(\.orig)?$', idfile)
    merged = encrypt_file(filemap['output'], armor)
    open(output, 'w').write(merged)

    # Close and delete all the temporary input files explicitly (not necessary,
    # but waddayaknow).
    for f in temps:
        f.close()

if __name__ == '__main__':
    main()
