Commit 31612497 authored by Damien Lespiau's avatar Damien Lespiau
Browse files

api: Add a way to tag patches with links to the patchwork instance



It can be handy to tag commit messages to get back to the discussion
from the git log. A new 'link' GET parameter make this possible on all
the mbox entry points, for both patches and series.
Signed-off-by: default avatarDamien Lespiau <damien.lespiau@intel.com>
parent b7f14573
......@@ -525,6 +525,10 @@ A series has then ``n`` revisions, ``n`` going from ``1`` to ``version``.
order in which to apply them. This mbox file can be directly piped into
``git am``.
:query link: Add an HTTP link to the Patchwork patch page in each commit
message. This link is preceded by a tag which name is given
as argument of this parameter, eg. ``?link=Patchwork``.
::
$ curl -s http://patchwork.example.com/api/1.0/series/42/revisions/2/mbox/ | git am -3
......@@ -639,6 +643,10 @@ Patches
Retrieve an mbox file. This mbox file can be directly piped into ``git am``.
:query link: Add an HTTP link to the Patchwork patch page in the commit
message. This link is preceded by a tag which name is given
as argument of this parameter, eg. ``?link=Patchwork``.
::
$ curl -s http://patchwork.example.com/api/1.0/patches/42/mbox/ | git am -3
......
......@@ -23,6 +23,7 @@ import datetime
import dateutil.parser
import dateutil.tz
import email
import re
from django.test import TestCase
......@@ -93,6 +94,31 @@ class MboxPatchSplitResponseTest(TestCase):
'Acked-by: 1\nAcked-by: 2\n')
class MboxPatchworkLink(TestCase):
fixtures = ['default_states', 'default_events']
""" Test that the mbox view appends the patch link """
def setUp(self):
defaults.project.save()
self.person = defaults.patch_author_person
self.person.save()
self.patch = Patch(project=defaults.project,
msgid='p1', name='testpatch',
submitter=self.person, content='')
self.patch.save()
def testPatchResponse(self):
response = self.client.get('/patch/%d/mbox/' % self.patch.id,
{'link': 'Patchwork'})
m = re.search(r'^Patchwork:.*/%d/$' % self.patch.pk, response.content,
re.M)
self.assertTrue(m)
class MboxPassThroughHeaderTest(TestCase):
fixtures = ['default_states', 'default_events']
......
......@@ -233,10 +233,17 @@ class APITest(APITestBase):
'for_each_-intel_-crtc-v2.mbox',
'42e2b2c9eeccf912c998be41683f50d7')
def _check_mbox_link(self, url, n):
response = self.client.get('/api/1.0' + url, {'link': 'Patchwork'})
m = re.findall('^Patchwork:.*http.*$', response.content, re.M)
self.assertEqual(len(m), n)
def testSeriesMboxPatchworkLink(self):
self._check_mbox_link(
"/series/%s/revisions/1/mbox/" % self.series2.pk, 3)
def testPatchMbox(self):
self.check_mbox("/patches/%s/mbox/" % self.patch.pk,
'3-4-drm-i915-Introduce-a-for_each_crtc-macro.patch',
'b951af09618c6360516f16ed97a30753')
self._check_mbox_link("/patches/%s/mbox/" % self.patch.pk, 1)
def testSeriesNewRevisionEvent(self):
# no 'since' parameter
......
......@@ -167,9 +167,15 @@ class PatchMbox(MIMENonMultipart):
encode_7or8bit(self)
def patch_to_mbox(patch):
def patch_to_mbox(patch, mbox_options={}):
postscript_re = re.compile('\n-{2,3} ?\n')
options = {
'patch-link': None,
'request': None, # needed to build the link URL
}
options.update(mbox_options)
comment = None
try:
comment = Comment.objects.get(patch=patch, msgid=patch.msgid)
......@@ -192,6 +198,12 @@ def patch_to_mbox(patch):
.exclude(msgid=patch.msgid):
body += comment.patch_responses()
request = options.get('request')
link_name = options['patch-link']
if link_name and request:
body += link_name + ': ' + \
request.build_absolute_uri(patch.get_absolute_url()) + '\n'
if postscript:
body += '---\n' + postscript + '\n'
......
......@@ -306,9 +306,14 @@ class SeriesViewSet(mixins.ListModelMixin,
old, new)
def series_mbox(revision):
def series_mbox(request, revision):
options = {
'patch-link': request.GET.get('link', None),
'request': request,
}
patches = revision.ordered_patches()
data = '\n'.join([patch_to_mbox(x).as_string(True) for x in patches])
data = '\n'.join([patch_to_mbox(x, options).as_string(True)
for x in patches])
response = HttpResponse(content_type="text/plain")
response.write(data)
response['Content-Disposition'] = 'attachment; filename=' + \
......@@ -335,7 +340,7 @@ class RevisionViewSet(mixins.ListModelMixin, ListMixin,
@detail_route(methods=['get'])
def mbox(self, request, series_pk=None, pk=None):
rev = get_object_or_404(SeriesRevision, series=series_pk, version=pk)
return series_mbox(rev)
return series_mbox(request, rev)
class ResultMixin(object):
......
......@@ -102,12 +102,16 @@ def content(request, patch_id):
def mbox(request, patch_id):
patch = get_object_or_404(Patch, id=patch_id)
options = {
'patch-link': request.GET.get('link', None),
'request': request,
}
response = HttpResponse(content_type="text/plain")
# NOTE(stephenfin) http://stackoverflow.com/a/28584090/613428
if six.PY3:
response.write(patch_to_mbox(patch).as_bytes(True).decode())
response.write(patch_to_mbox(patch, options).as_bytes(True).decode())
else:
response.write(patch_to_mbox(patch).as_string(True))
response.write(patch_to_mbox(patch, options).as_string(True))
response['Content-Disposition'] = 'attachment; filename=' + \
patch.filename().replace(';', '').replace('\n', '')
return response
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment