comparison sat/plugins/plugin_merge_req_mercurial.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 181735d1b062
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SàT plugin for import external blogs 4 # SàT plugin for import external blogs
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
33 C.PI_IMPORT_NAME: "MERGE_REQUEST_MERCURIAL", 33 C.PI_IMPORT_NAME: "MERGE_REQUEST_MERCURIAL",
34 C.PI_TYPE: C.PLUG_TYPE_MISC, 34 C.PI_TYPE: C.PLUG_TYPE_MISC,
35 C.PI_DEPENDENCIES: ["MERGE_REQUESTS"], 35 C.PI_DEPENDENCIES: ["MERGE_REQUESTS"],
36 C.PI_MAIN: "MercurialHandler", 36 C.PI_MAIN: "MercurialHandler",
37 C.PI_HANDLER: "no", 37 C.PI_HANDLER: "no",
38 C.PI_DESCRIPTION: _(u"""Merge request handler for Mercurial""") 38 C.PI_DESCRIPTION: _("""Merge request handler for Mercurial""")
39 } 39 }
40 40
41 SHORT_DESC = D_(u"handle Mercurial repository") 41 SHORT_DESC = D_("handle Mercurial repository")
42 CLEAN_RE = re.compile(ur'[^\w -._]', flags=re.UNICODE) 42 CLEAN_RE = re.compile(r'[^\w -._]', flags=re.UNICODE)
43 43
44 44
45 class MercurialProtocol(async_process.CommandProtocol): 45 class MercurialProtocol(async_process.CommandProtocol):
46 """handle hg commands""" 46 """handle hg commands"""
47 name = u"Mercurial" 47 name = "Mercurial"
48 command = None 48 command = None
49 49
50 @classmethod 50 @classmethod
51 def run(cls, path, command, *args, **kwargs): 51 def run(cls, path, command, *args, **kwargs):
52 """Create a new MercurialRegisterProtocol and execute the given mercurial command. 52 """Create a new MercurialRegisterProtocol and execute the given mercurial command.
53 53
54 @param path(unicode): path to the repository 54 @param path(unicode): path to the repository
55 @param command(unicode): hg command to run 55 @param command(unicode): hg command to run
56 """ 56 """
57 assert u"path" not in kwargs 57 assert "path" not in kwargs
58 kwargs["path"] = path 58 kwargs["path"] = path
59 # FIXME: we have to use this workaround because Twisted's protocol.ProcessProtocol 59 # FIXME: we have to use this workaround because Twisted's protocol.ProcessProtocol
60 # is not using new style classes. This can be removed once moved to 60 # is not using new style classes. This can be removed once moved to
61 # Python 3 (super can be used normally then). 61 # Python 3 (super can be used normally then).
62 d = async_process.CommandProtocol.run.__func__(cls, command, *args, **kwargs) 62 d = async_process.CommandProtocol.run.__func__(cls, command, *args, **kwargs)
63 d.addErrback(utils.logError) 63 d.addErrback(utils.logError)
64 return d 64 return d
65 65
66 66
67 class MercurialHandler(object): 67 class MercurialHandler(object):
68 data_types = (u'mercurial_changeset',) 68 data_types = ('mercurial_changeset',)
69 69
70 def __init__(self, host): 70 def __init__(self, host):
71 log.info(_(u"Mercurial merge request handler initialization")) 71 log.info(_("Mercurial merge request handler initialization"))
72 try: 72 try:
73 MercurialProtocol.command = which('hg')[0] 73 MercurialProtocol.command = which('hg')[0]
74 except IndexError: 74 except IndexError:
75 raise exceptions.NotFound(_(u"Mercurial executable (hg) not found, " 75 raise exceptions.NotFound(_("Mercurial executable (hg) not found, "
76 u"can't use Mercurial handler")) 76 "can't use Mercurial handler"))
77 self.host = host 77 self.host = host
78 self._m = host.plugins['MERGE_REQUESTS'] 78 self._m = host.plugins['MERGE_REQUESTS']
79 self._m.register('mercurial', self, self.data_types, SHORT_DESC) 79 self._m.register('mercurial', self, self.data_types, SHORT_DESC)
80 80
81 81
90 '--encoding=utf-8') 90 '--encoding=utf-8')
91 91
92 def import_(self, repository, data, data_type, item_id, service, node, extra): 92 def import_(self, repository, data, data_type, item_id, service, node, extra):
93 parsed_data = self.parse(data) 93 parsed_data = self.parse(data)
94 try: 94 try:
95 parsed_name = parsed_data[0][u'commit_msg'].split(u'\n')[0] 95 parsed_name = parsed_data[0]['commit_msg'].split('\n')[0]
96 parsed_name = CLEAN_RE.sub(u'', parsed_name)[:40] 96 parsed_name = CLEAN_RE.sub('', parsed_name)[:40]
97 except Exception: 97 except Exception:
98 parsed_name = u'' 98 parsed_name = ''
99 name = u'mr_{item_id}_{parsed_name}'.format(item_id=CLEAN_RE.sub(u'', item_id), 99 name = 'mr_{item_id}_{parsed_name}'.format(item_id=CLEAN_RE.sub('', item_id),
100 parsed_name=parsed_name) 100 parsed_name=parsed_name)
101 return MercurialProtocol.run(repository, 'qimport', '-g', '--name', name, 101 return MercurialProtocol.run(repository, 'qimport', '-g', '--name', name,
102 '--encoding=utf-8', '-', stdin=data) 102 '--encoding=utf-8', '-', stdin=data)
103 103
104 def parse(self, data, data_type=None): 104 def parse(self, data, data_type=None):
109 patch = {} 109 patch = {}
110 commit_msg = [] 110 commit_msg = []
111 diff = [] 111 diff = []
112 state = 'init' 112 state = 'init'
113 if lines[0] != '# HG changeset patch': 113 if lines[0] != '# HG changeset patch':
114 raise exceptions.DataError(_(u'invalid changeset signature')) 114 raise exceptions.DataError(_('invalid changeset signature'))
115 # line index of this patch in the whole data 115 # line index of this patch in the whole data
116 patch_idx = total_lines - len(lines) 116 patch_idx = total_lines - len(lines)
117 del lines[0] 117 del lines[0]
118 118
119 for idx, line in enumerate(lines): 119 for idx, line in enumerate(lines):
120 if state == 'init': 120 if state == 'init':
121 if line.startswith(u'# '): 121 if line.startswith('# '):
122 if line.startswith(u'# User '): 122 if line.startswith('# User '):
123 elems = line[7:].split() 123 elems = line[7:].split()
124 if not elems: 124 if not elems:
125 continue 125 continue
126 last = elems[-1] 126 last = elems[-1]
127 if (last.startswith(u'<') and last.endswith(u'>') 127 if (last.startswith('<') and last.endswith('>')
128 and u'@' in last): 128 and '@' in last):
129 patch[self._m.META_EMAIL] = elems.pop()[1:-1] 129 patch[self._m.META_EMAIL] = elems.pop()[1:-1]
130 patch[self._m.META_AUTHOR] = u' '.join(elems) 130 patch[self._m.META_AUTHOR] = ' '.join(elems)
131 elif line.startswith(u'# Date '): 131 elif line.startswith('# Date '):
132 time_data = line[7:].split() 132 time_data = line[7:].split()
133 if len(time_data) != 2: 133 if len(time_data) != 2:
134 log.warning(_(u'unexpected time data: {data}') 134 log.warning(_('unexpected time data: {data}')
135 .format(data=line[7:])) 135 .format(data=line[7:]))
136 continue 136 continue
137 patch[self._m.META_TIMESTAMP] = (int(time_data[0]) 137 patch[self._m.META_TIMESTAMP] = (int(time_data[0])
138 + int(time_data[1])) 138 + int(time_data[1]))
139 elif line.startswith(u'# Node ID '): 139 elif line.startswith('# Node ID '):
140 patch[self._m.META_HASH] = line[10:] 140 patch[self._m.META_HASH] = line[10:]
141 elif line.startswith(u'# Parent '): 141 elif line.startswith('# Parent '):
142 patch[self._m.META_PARENT_HASH] = line[10:] 142 patch[self._m.META_PARENT_HASH] = line[10:]
143 else: 143 else:
144 state = 'commit_msg' 144 state = 'commit_msg'
145 if state == 'commit_msg': 145 if state == 'commit_msg':
146 if line.startswith(u'diff --git a/'): 146 if line.startswith('diff --git a/'):
147 state = 'diff' 147 state = 'diff'
148 patch[self._m.META_DIFF_IDX] = patch_idx + idx + 1 148 patch[self._m.META_DIFF_IDX] = patch_idx + idx + 1
149 else: 149 else:
150 commit_msg.append(line) 150 commit_msg.append(line)
151 if state == 'diff': 151 if state == 'diff':
152 if line.startswith(u'# ') or idx == len(lines)-1: 152 if line.startswith('# ') or idx == len(lines)-1:
153 # a new patch is starting or we have reached end of patches 153 # a new patch is starting or we have reached end of patches
154 if idx == len(lines)-1: 154 if idx == len(lines)-1:
155 # end of patches, we need to keep the line 155 # end of patches, we need to keep the line
156 diff.append(line) 156 diff.append(line)
157 patch[self._m.META_COMMIT_MSG] = u'\n'.join(commit_msg) 157 patch[self._m.META_COMMIT_MSG] = '\n'.join(commit_msg)
158 patch[self._m.META_DIFF] = u'\n'.join(diff) 158 patch[self._m.META_DIFF] = '\n'.join(diff)
159 patches.append(patch) 159 patches.append(patch)
160 if idx == len(lines)-1: 160 if idx == len(lines)-1:
161 del lines[:] 161 del lines[:]
162 else: 162 else:
163 del lines[:idx] 163 del lines[:idx]