diff --git a/bugwarrior/README.rst b/bugwarrior/README.rst
index 651f3e898..6987ae795 100644
--- a/bugwarrior/README.rst
+++ b/bugwarrior/README.rst
@@ -16,6 +16,7 @@ It currently supports the following remote resources:
- `redmine `_
- `jira `_
- `activecollab `_ (2.x and 3.x)
+ - `phabricator `_
Configuring
-----------
@@ -268,6 +269,23 @@ Create a ``~/.bugwarriorrc`` file with the following contents.
# - jiraid
#description_template = {{ jiraid }}: {{ jirasummary }}
+ # Here's an example of a phabricator target
+ [my_phabricator]
+ service = phabricator
+
+ # No need to specify credentials. They are gathered from ~/.arcrc
+
+ add_tags = whatever
+
+ # You can override how an issue's description is created by entering
+ # a one-line Jinja template like the below; in addition to the default
+ # taskwarrior issue properties (project, priority, due, etc), the
+ # following properties are available for Teamlab issues:
+ # - phabricatorurl
+ # - phabricatorid
+ # - phabricatortitle
+ #description_template = #{{ phabricatorid }}: {{ phabricatortitle }}
+
# Here's an example of a teamlab target.
[my_teamlab]
service = teamlab
@@ -433,8 +451,6 @@ Hacking on It
See the `HACKING.rst
`_ file.
-
-
Contributors
------------
diff --git a/bugwarrior/services/__init__.py b/bugwarrior/services/__init__.py
index b014b5f93..b26746bf1 100644
--- a/bugwarrior/services/__init__.py
+++ b/bugwarrior/services/__init__.py
@@ -473,3 +473,9 @@ def aggregate_issues(conf):
SERVICES['megaplan'] = MegaplanService
except ImportError:
pass
+
+try:
+ from .phab import PhabricatorService
+ SERVICES['phabricator'] = PhabricatorService
+except ImportError as e:
+ pass
diff --git a/bugwarrior/services/phab.py b/bugwarrior/services/phab.py
new file mode 100644
index 000000000..e57a5278e
--- /dev/null
+++ b/bugwarrior/services/phab.py
@@ -0,0 +1,107 @@
+import six
+from twiggy import log
+
+from bugwarrior.services import IssueService, Issue
+
+# This comes from PyPI
+import phabricator
+
+
+class PhabricatorIssue(Issue):
+ TITLE = 'phabricatortitle'
+ URL = 'phabricatorurl'
+ TYPE = 'phabricatortype'
+ OBJECT_NAME = 'phabricatorid'
+
+ UDAS = {
+ TITLE: {
+ 'type': 'string',
+ 'label': 'Phabricator Title',
+ },
+ URL: {
+ 'type': 'string',
+ 'label': 'Phabricator URL',
+ },
+ TYPE: {
+ 'type': 'string',
+ 'label': 'Phabricator Type',
+ },
+ OBJECT_NAME: {
+ 'type': 'string',
+ 'label': 'Phabricator Object',
+ },
+ }
+ UNIQUE_KEY = (URL, TYPE,)
+
+ def to_taskwarrior(self):
+ return {
+ 'project': self.extra['project'],
+ 'priority': self.origin['default_priority'],
+ 'annotations': self.extra.get('annotations', []),
+
+ self.URL: self.record['uri'],
+ self.TYPE: self.extra['type'],
+ self.TITLE: self.record['title'],
+ self.OBJECT_NAME: self.record['uri'].split('/')[-1],
+ }
+
+ def get_default_description(self):
+ return self.build_default_description(
+ title=self.record['title'],
+ url=self.get_processed_url(self.record['uri']),
+ number=self.record['uri'].split('/')[-1],
+ cls=self.extra['type'],
+ )
+
+
+class PhabricatorService(IssueService):
+ ISSUE_CLASS = PhabricatorIssue
+ CONFIG_PREFIX = 'phabricator'
+
+ def __init__(self, *args, **kw):
+ super(PhabricatorService, self).__init__(*args, **kw)
+ # These reads in login credentials from ~/.arcrc
+ self.api = phabricator.Phabricator()
+
+ def issues(self):
+
+ # TODO -- get a list of these from the api
+ projects = {}
+
+ issues = self.api.maniphest.query(status='status-open')
+ issues = list(issues.iteritems())
+
+ log.name(self.target).info("Found %i issues" % len(issues))
+
+ for phid, issue in issues:
+ project = self.target # a sensible default
+ try:
+ project = projects.get(issue['projectPHIDs'][0], project)
+ except IndexError:
+ pass
+
+ extra = {
+ 'project': project,
+ 'type': 'issue',
+ #'annotations': self.annotations(phid, issue)
+ }
+ yield self.get_issue_for_record(issue, extra)
+
+ diffs = self.api.differential.query(status='status-open')
+ diffs = list(diffs)
+
+ log.name(self.target).info("Found %i differentials" % len(diffs))
+
+ for diff in list(diffs):
+ project = self.target # a sensible default
+ try:
+ project = projects.get(issue['projectPHIDs'][0], project)
+ except IndexError:
+ pass
+
+ extra = {
+ 'project': project,
+ 'type': 'pull_request',
+ #'annotations': self.annotations(phid, issue)
+ }
+ yield self.get_issue_for_record(diff, extra)