(Steve Dougherty)
2013-07-02: Add partial pull request support; other things. Add partial pull request support; other things. Exit when unable to resolve WoT URI. (Possibly would be better to use ui.abort()) Add listing of WoT dependencies and pull request syntax to readme.
diff --git a/Readme.txt b/Readme.txt
--- a/Readme.txt
+++ b/Readme.txt
@@ -14,3 +14,15 @@ and many other features.
And it works transparently: To publish, just clone to freenet:
hg clone REPO freenet://USK@/REPO
+
+It supports WoT too:
+
+ hg clone REPO freenet://wot_nick@public_key_hash/repo
+
+ - Push / pull by name
+ - Pull requests
+
+Dependencies for full WoT support:
+ - PyYAML http://pyyaml.org/wiki/PyYAMLDocumentation
+ - lib-pyFreenet https://github.com/freenet/lib-pyFreenet-official
+ - DefusedXML https://pypi.python.org/pypi/defusedxml/
\ No newline at end of file
diff --git a/infocalypse/__init__.py b/infocalypse/__init__.py
--- a/infocalypse/__init__.py
+++ b/infocalypse/__init__.py
@@ -390,9 +390,11 @@ cmdtable = {
"[options]"),
"fn-pull-request": (infocalypse_pull_request,
- WOT_OPTS +
- FCP_OPTS,
- "--wot id@key/repo"),
+ [('', 'wot', '', 'WoT nick@key/repo to send request '
+ 'to')]
+ + WOT_OPTS
+ + FCP_OPTS,
+ "[--truster nick@key] --wot nick@key/repo"),
"fn-push": (infocalypse_push,
[('', 'uri', '', 'insert URI to push to'),
@@ -693,9 +695,16 @@ def freenetclone(orig, *args, **opts):
pulluri, pushuri = None, None
if isfreenetpath(source):
pulluri = freenetpathtouri(ui, source)
+
+ if not pulluri:
+ raise util.Abort()
+
if isfreenetpath(dest):
pushuri = freenetpathtouri(ui, dest, pull=False)
+ if not pushuri:
+ return
+
# decide which infocalypse command to use.
if pulluri and pushuri:
action = "copy"
diff --git a/infocalypse/commands.py b/infocalypse/commands.py
--- a/infocalypse/commands.py
+++ b/infocalypse/commands.py
@@ -107,9 +107,8 @@ def infocalypse_create(ui_, repo, **opts
params['INSERT_URI'] = insert_uri
inserted_to = execute_create(ui_, repo, params, stored_cfg)
- # TODO: Move into some function. How to separate local success context?
- if inserted_to is not None and attributes is not None and \
- stored_cfg.has_wot_identity(stored_cfg.get_request_uri(repo.root)):
+ if inserted_to and opts['wot']:
+ # TODO: Imports don't go out of scope, right?
import wot
wot.update_repo_listing(ui_, attributes['Identity'])
@@ -213,10 +212,15 @@ def infocalypse_pull(ui_, repo, **opts):
def infocalypse_pull_request(ui, repo, **opts):
+ import wot
if not opts['wot']:
- ui.warning("Who do you want to send the pull request to? Set --wot.")
+ ui.warn("Who do you want to send the pull request to? Set --wot.\n")
return
+ wot_id, repo_name = opts['wot'].split('/', 1)
+ wot.send_pull_request(ui, repo, get_truster(ui, repo, opts), wot_id,
+ repo_name)
+
def infocalypse_push(ui_, repo, **opts):
""" Push to an Infocalypse repository in Freenet. """
diff --git a/infocalypse/wot.py b/infocalypse/wot.py
--- a/infocalypse/wot.py
+++ b/infocalypse/wot.py
@@ -7,38 +7,123 @@ import smtplib
from base64 import b32encode
from fcp.node import base64decode
from keys import USK
+import yaml
+from email.mime.text import MIMEText
+import imaplib
+FREEMAIL_SMTP_PORT = 4025
+FREEMAIL_IMAP_PORT = 4143
+PULL_REQUEST_PREFIX = "[vcs] "
-def send_pull_request(ui, from_identity, to_identity):
- local_identity = resolve_local_identity(ui, from_identity)
- target_identity = resolve_identity(ui, from_identity, to_identity)
- if local_identity is None or target_identity is None:
- # Error.
+def send_pull_request(ui, repo, from_identifier, to_identifier, to_repo_name):
+ local_identity = resolve_local_identity(ui, from_identifier)
+ if local_identity is None:
+ return
+
+ target_identity = resolve_identity(ui, local_identity['Identity'],
+ to_identifier)
+ if target_identity is None:
return
from_address = to_freemail_address(local_identity)
- to_address = to_freemail_address(to_identity)
+ to_address = to_freemail_address(target_identity)
if from_address is None or to_address is None:
if from_address is None:
- ui.warn("{0} is not using Freemail.\n".format(from_identity[
- 'Nickname']))
+ ui.warn("{0}@{1} is not using Freemail.\n".format(local_identity[
+ 'Nickname'], local_identity['Identity']))
if to_address is None:
- ui.warn("{0} is not using Freemail.\n".format(to_identity[
- 'Nickname']))
+ ui.warn("{0}@{1} is not using Freemail.\n".format(target_identity[
+ 'Nickname'], target_identity['Identity']))
return
- # TODO: Use FCP host; default port.
- smtp = smtplib.SMTP()
- # TODO: Where to configure Freemail password?
- smtp.login(from_address, )
- smtp.sendmail()
+ # Check that a password is set.
+ cfg = Config.from_ui(ui)
+ password = cfg.get_freemail_password(local_identity['Identity'])
+ if password is None:
+ ui.warn("{0} does not have a Freemail password set.\n"
+ "Run hg fn-setupfreemail --truster {0}@{1}\n"
+ .format(local_identity['Nickname'], local_identity['Identity']))
+ return
+
+ to_repo = find_repo(ui, local_identity['Identity'], to_identifier,
+ to_repo_name)
+
+ # TODO: Frequently doing {0}@{1} ... would a WoTIdentity class make sense?
+ if to_repo is None:
+ ui.warn("{0}@{1} has not published a repository named '{2}'.\n"
+ .format(target_identity['Nickname'],
+ target_identity['Identifier'],
+ to_repo_name))
+ return
+
+ repo_context = repo['tip']
+ # TODO: Will there always be a request URI set in the config? What about
+ # a path?
+ from_uri = cfg.get_request_uri(repo.root)
+ from_branch = repo_context.branch()
+
+ # Use double-quoted scalars so that Unicode can be included. (Nicknames.)
+ footer = yaml.dump({'request': 'pull',
+ 'vcs': 'Infocalypse',
+ 'source': from_uri + '#' + from_branch,
+ 'target': to_repo}, default_style='"',
+ explicit_start=True, explicit_end=True,
+ allow_unicode=True)
+
+ # TODO: Break config sanity check and sending apart so that further
+ # things can check config, prompt for whatever, then send.
+
+ source_text = ui.edit("""
+
+HG: Enter pull request message here. Lines beginning with 'HG:' are removed.
+HG: The first line has "{0}" added before it and is the email subject.
+HG: The second line should be blank.
+HG: Following lines are the body of the message.
+HG: Below is the machine-readable footer describing the request. Modifying it
+HG: or putting things below it has the potential to cause problems.
+
+{1}
+""".format(PULL_REQUEST_PREFIX, footer), from_identifier)
+ # TODO: Abort in the case of a blank message?
+ # Markdown support would be on receiving end. Maybe CLI preview eventually.
+ # (Would that even work?)
+ # TODO: Save message and load later in case sending fails.
+
+ # TODO: What if the editor uses different line endings? How to slice
+ # by-line?
+ source_lines = source_text.split('\n')
+
+ # Body is third line and after.
+ msg = MIMEText('\n'.join(source_lines[2:]))
+ msg['Subject'] = PULL_REQUEST_PREFIX + source_lines[0]
+ msg['To'] = to_address
+ msg['From'] = from_address
+
+ smtp = smtplib.SMTP(cfg.defaults['HOST'], FREEMAIL_SMTP_PORT)
+ smtp.login(from_address, password)
+ # TODO: Catch exceptions and give nice error messages.
+ smtp.sendmail(from_address, to_address, msg.as_string())
+
+
+def receive_pull_requests(ui):
+ # TODO: Terminology - send/receive different from resolve/read elsewhere.
+ # TODO: How to find YAML? Look from end backwards for "---\n" then forward
+ # from there for "...\n"?
+ # TODO: How to match local repo with the "target" URI? Based on repo list.
+
+ cfg = Config.from_ui(ui)
+ imap = imaplib.IMAP4(cfg.defaults['HOST'], FREEMAIL_IMAP_PORT)
+
+ type, message_numbers = imap.search(None, "SUBJECT", PULL_REQUEST_PREFIX)
+ print(type, message_numbers)
def update_repo_listing(ui, for_identity):
# TODO: WoT property containing edition. Used when requesting.
config = Config.from_ui(ui)
+ # Version number to support possible format changes.
root = ET.Element('vcs', {'version': '0'})
# Add request URIs associated with the given identity.