Mercurial > libervia-backend
comparison sat/plugins/plugin_merge_req_mercurial.py @ 2562:26edcf3a30eb
core, setup: huge cleaning:
- moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention
- move twisted directory to root
- removed all hacks from setup.py, and added missing dependencies, it is now clean
- use https URL for website in setup.py
- removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed
- renamed sat.sh to sat and fixed its installation
- added python_requires to specify Python version needed
- replaced glib2reactor which use deprecated code by gtk3reactor
sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 02 Apr 2018 19:44:50 +0200 |
parents | src/plugins/plugin_merge_req_mercurial.py@bd30dc3ffe5a |
children | 72f6f37ab648 |
comparison
equal
deleted
inserted
replaced
2561:bd30dc3ffe5a | 2562:26edcf3a30eb |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SàT plugin for import external blogs | |
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 from sat.core.i18n import _, D_ | |
21 from sat.core.constants import Const as C | |
22 from sat.core import exceptions | |
23 from twisted.internet import reactor, defer, protocol | |
24 from twisted.python.failure import Failure | |
25 from twisted.python.procutils import which | |
26 import re | |
27 from sat.core.log import getLogger | |
28 log = getLogger(__name__) | |
29 | |
30 | |
31 PLUGIN_INFO = { | |
32 C.PI_NAME: "Mercurial Merge Request handler", | |
33 C.PI_IMPORT_NAME: "MERGE_REQUEST_MERCURIAL", | |
34 C.PI_TYPE: C.PLUG_TYPE_MISC, | |
35 C.PI_DEPENDENCIES: ["MERGE_REQUESTS"], | |
36 C.PI_MAIN: "MercurialHandler", | |
37 C.PI_HANDLER: "no", | |
38 C.PI_DESCRIPTION: _(u"""Merge request handler for Mercurial""") | |
39 } | |
40 | |
41 SHORT_DESC = D_(u"handle Mercurial repository") | |
42 | |
43 | |
44 class MercurialProtocol(protocol.ProcessProtocol): | |
45 """handle hg commands""" | |
46 hg = None | |
47 | |
48 def __init__(self, deferred, stdin=None): | |
49 """ | |
50 @param deferred(defer.Deferred): will be called when command is completed | |
51 @param stdin(str, None): if not None, will be push to standard input | |
52 """ | |
53 self._stdin = stdin | |
54 self._deferred = deferred | |
55 self.data = [] | |
56 | |
57 def connectionMade(self): | |
58 if self._stdin is not None: | |
59 self.transport.write(self._stdin) | |
60 self.transport.closeStdin() | |
61 | |
62 def outReceived(self, data): | |
63 self.data.append(data) | |
64 | |
65 def errReceived(self, data): | |
66 self.data.append(data) | |
67 | |
68 def processEnded(self, reason): | |
69 data = u''.join([d.decode('utf-8') for d in self.data]) | |
70 if (reason.value.exitCode == 0): | |
71 log.debug(_('Mercurial command succeed')) | |
72 self._deferred.callback(data) | |
73 else: | |
74 msg = _(u"Can't complete Mercurial command (error code: {code}): {message}").format( | |
75 code = reason.value.exitCode, | |
76 message = data) | |
77 log.warning(msg) | |
78 self._deferred.errback(Failure(RuntimeError(msg))) | |
79 | |
80 @classmethod | |
81 def run(cls, path, command, *args, **kwargs): | |
82 """Create a new MercurialRegisterProtocol and execute the given mercurialctl command. | |
83 | |
84 @param path(unicode): path to the repository | |
85 @param command(unicode): command to run | |
86 @param *args(unicode): command arguments | |
87 @param **kwargs: used because Python2 doesn't handle normal kw args after *args | |
88 can only be: | |
89 - stdin(unicode, None): data to push to standard input | |
90 @return ((D)): | |
91 """ | |
92 stdin = kwargs.pop('stdin', None) | |
93 if kwargs: | |
94 raise exceptions.InternalError(u'only stdin is allowed as keyword argument') | |
95 if stdin is not None: | |
96 stdin = stdin.encode('utf-8') | |
97 d = defer.Deferred() | |
98 mercurial_prot = MercurialProtocol(d, stdin=stdin) | |
99 cmd_args = [cls.hg, command.encode('utf-8')] | |
100 cmd_args.extend([a.encode('utf-8') for a in args]) | |
101 reactor.spawnProcess(mercurial_prot, | |
102 cls.hg, | |
103 cmd_args, | |
104 path=path.encode('utf-8')) | |
105 return d | |
106 | |
107 | |
108 class MercurialHandler(object): | |
109 data_types = (u'mercurial_changeset',) | |
110 | |
111 def __init__(self, host): | |
112 log.info(_(u"Mercurial merge request handler initialization")) | |
113 try: | |
114 MercurialProtocol.hg = which('hg')[0] | |
115 except IndexError: | |
116 raise exceptions.NotFound(_(u"Mercurial executable (hg) not found, can't use Mercurial handler")) | |
117 self.host = host | |
118 self._m = host.plugins['MERGE_REQUESTS'] | |
119 self._m.register('mercurial', self, self.data_types, SHORT_DESC) | |
120 | |
121 def check(self, repository): | |
122 d = MercurialProtocol.run(repository, 'identify') | |
123 d.addCallback(lambda dummy: True) | |
124 d.addErrback(lambda dummy: False) | |
125 return d | |
126 | |
127 def export(self, repository): | |
128 return MercurialProtocol.run(repository, 'export', '-g', '-r', 'outgoing()', '--encoding=utf-8') | |
129 | |
130 def import_(self, repository, data, data_type, item_id, service, node, extra): | |
131 parsed_data = self.parse(data) | |
132 try: | |
133 parsed_name = parsed_data[0][u'commit_msg'].split(u'\n')[0] | |
134 parsed_name = re.sub(ur'[^\w -.]', u'', parsed_name, flags=re.UNICODE)[:40] | |
135 except Exception: | |
136 parsed_name = u'' | |
137 name = u'mr_{item_id}_{parsed_name}'.format(item_id=item_id, parsed_name=parsed_name) | |
138 return MercurialProtocol.run(repository, 'qimport', '-g', '--name', name, '--encoding=utf-8', '-', stdin=data) | |
139 | |
140 def parse(self, data, data_type=None): | |
141 lines = data.splitlines() | |
142 total_lines = len(lines) | |
143 patches = [] | |
144 while lines: | |
145 patch = {} | |
146 commit_msg = [] | |
147 diff = [] | |
148 state = 'init' | |
149 if lines[0] != '# HG changeset patch': | |
150 raise exceptions.DataError(_(u'invalid changeset signature')) | |
151 # line index of this patch in the whole data | |
152 patch_idx = total_lines - len(lines) | |
153 del lines[0] | |
154 | |
155 for idx, line in enumerate(lines): | |
156 if state == 'init': | |
157 if line.startswith(u'# '): | |
158 if line.startswith(u'# User '): | |
159 elems = line[7:].split() | |
160 if not elems: | |
161 continue | |
162 last = elems[-1] | |
163 if last.startswith(u'<') and last.endswith(u'>') and u'@' in last: | |
164 patch[self._m.META_EMAIL] = elems.pop()[1:-1] | |
165 patch[self._m.META_AUTHOR] = u' '.join(elems) | |
166 elif line.startswith(u'# Date '): | |
167 time_data = line[7:].split() | |
168 if len(time_data) != 2: | |
169 log.warning(_(u'unexpected time data: {data}').format(data=line[7:])) | |
170 continue | |
171 patch[self._m.META_TIMESTAMP] = int(time_data[0]) + int(time_data[1]) | |
172 elif line.startswith(u'# Node ID '): | |
173 patch[self._m.META_HASH] = line[10:] | |
174 elif line.startswith(u'# Parent '): | |
175 patch[self._m.META_PARENT_HASH] = line[10:] | |
176 else: | |
177 state = 'commit_msg' | |
178 if state == 'commit_msg': | |
179 if line.startswith(u'diff --git a/'): | |
180 state = 'diff' | |
181 patch[self._m.META_DIFF_IDX] = patch_idx + idx + 1 | |
182 else: | |
183 commit_msg.append(line) | |
184 if state == 'diff': | |
185 if line.startswith(u'# ') or idx == len(lines)-1: | |
186 # a new patch is starting or we have reached end of patches | |
187 patch[self._m.META_COMMIT_MSG] = u'\n'.join(commit_msg) | |
188 patch[self._m.META_DIFF] = u'\n'.join(diff) | |
189 patches.append(patch) | |
190 if idx == len(lines)-1: | |
191 del lines[:] | |
192 else: | |
193 del lines[:idx] | |
194 break | |
195 else: | |
196 diff.append(line) | |
197 return patches |