Merge lp:~oddbloke/pqm/remove-vcs-abstraction into lp:pqm
- remove-vcs-abstraction
- Merge into trunk
Proposed by
Jelmer Vernooij
Status: | Work in progress |
---|---|
Proposed branch: | lp:~oddbloke/pqm/remove-vcs-abstraction |
Merge into: | lp:pqm |
Diff against target: |
943 lines (+738/-6) (has conflicts) 2 files modified
pqm/__init__.py (+679/-0) pqm/tests/test_pqm.py (+59/-6) Text conflict in pqm/__init__.py Text conflict in pqm/tests/test_pqm.py |
To merge this branch: | bzr merge lp:~oddbloke/pqm/remove-vcs-abstraction |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
pqm developers | Pending | ||
Review via email: mp+23367@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Unmerged revisions
- 201. By Dan Watkins
-
Merged in further test changes.
- 200. By Dan Watkins
-
Renamed Bazaar2Handler to BzrHandler.
- 199. By Dan Watkins
-
Removed VCSHandler.
- 198. By Dan Watkins
-
Moved Command.
wrap_command to MergeCommand. wrap_command. - 197. By Dan Watkins
-
Removed needless merge_method parameter to MergeCommand.
do_merge. - 196. By Dan Watkins
-
Moved Command.do_merge to MergeCommand.
do_merge. - 195. By Dan Watkins
-
Removed debug statement from test.
- 194. By Dan Watkins
-
Removed now-irrelevant config filenames.
- 193. By Dan Watkins
-
Removed Command.get_vcs.
- 192. By Dan Watkins
-
Removed Command.
get_branch_ handler and accompanying tests.
Updating diff...
An updated diff will be available in a few minutes. Reload to see the changes.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'pqm/__init__.py' |
2 | --- pqm/__init__.py 2010-04-20 12:11:40 +0000 |
3 | +++ pqm/__init__.py 2010-05-04 17:12:27 +0000 |
4 | @@ -56,7 +56,218 @@ |
5 | sys.exit(1) |
6 | |
7 | |
8 | +<<<<<<< TREE |
9 | def verify_sig(sender, msg, sig, check_replay, logger, keyring): |
10 | +======= |
11 | +if 'PQM_CONFIG' in os.environ: |
12 | + configfile_names = [os.environ['PQM_CONFIG']] |
13 | +else: |
14 | + configfile_names = ['/etc/pqm.conf'] |
15 | + configfile_names.append(os.path.expanduser('~/.pqm.conf')) |
16 | + |
17 | + |
18 | +class Script(object): |
19 | + """A command script.""" |
20 | + |
21 | + whitespace_re = re.compile('^\s*$') |
22 | + pgp_re = re.compile('^-----BEGIN PGP.*MESSAGE') |
23 | + pgp_end_re = re.compile('^-----BEGIN PGP SIG') |
24 | + # parser for merge recognition |
25 | + star_re = re.compile('^star-merge (\S+/\S+)\s+(\S+/\S+)\s*$') |
26 | + # parse matcher for the debug command |
27 | + debug_re = re.compile('^debug') |
28 | + |
29 | + def __init__(self, filename, logger, verify_sigs, submission_time, |
30 | + branch_spec_handler, configp): |
31 | + """Create a script for a given file.""" |
32 | + self.debug = True |
33 | + self.filename = filename |
34 | + self.logger = logger |
35 | + self.verify_sigs = verify_sigs |
36 | + self.readComplete = False |
37 | + self.submission_time = submission_time |
38 | + self._branch_spec_handler = branch_spec_handler |
39 | + self._configp = configp |
40 | + |
41 | + def _read(self): |
42 | + """Read in the script details.""" |
43 | + # This is cruft from the binary script. It actually does need |
44 | + # to read the file twice with the current layering - yes thats funky, but |
45 | + # not something I plan on breaking in an ad hoc manner right now. The |
46 | + # read_email and email.message_from_file and verify_sig functions need |
47 | + # rearranging and consolidating to allow this. |
48 | + details = read_email(self.logger, open(self.filename)) |
49 | + (self.sender, self.subject, self.msg, self.sig) = details |
50 | + self.signing_email = None |
51 | + if self.verify_sigs: |
52 | + sigid,siguid = verify_sig(self.sender, |
53 | + self.msg, |
54 | + self.sig, |
55 | + 0, |
56 | + self.logger) |
57 | + self.signing_email = siguid |
58 | + self.msg = email.message_from_file(open(self.filename)) |
59 | + self.sender = self.msg['From'] |
60 | + self.logger.info ('sender: %s' , self.sender) |
61 | + self.logger.info ('subject: %s' , self.subject) |
62 | + self.readComplete = True |
63 | + |
64 | + def getProjects(self): |
65 | + """Return the set of projects this script will affect.""" |
66 | + result = set() |
67 | + for command in self.getCommands(): |
68 | + result.update(command.getProjects()) |
69 | + return result |
70 | + |
71 | + def getSender(self): |
72 | + """Get the sender of the script.""" |
73 | + if not self.readComplete: |
74 | + self._read() |
75 | + return self.sender |
76 | + |
77 | + def getSubject(self): |
78 | + """Get the subject of the script.""" |
79 | + if not self.readComplete: |
80 | + self._read() |
81 | + return self.subject |
82 | + |
83 | + def getContent(self): |
84 | + """Get the raw content for folk that want it.""" |
85 | + if not self.readComplete: |
86 | + self._read() |
87 | + return self.msg.get_payload() |
88 | + |
89 | + def getLines(self): |
90 | + """Get the lines in the script.""" |
91 | + if not self.readComplete: |
92 | + self._read() |
93 | + to_skip = 0 |
94 | + result = [] |
95 | + for line in self.msg.get_payload().split('\n'): |
96 | + if to_skip: |
97 | + to_skip -= 1 |
98 | + continue |
99 | + if self.whitespace_re.match(line): |
100 | + continue |
101 | + if self.pgp_re.match(line): |
102 | + to_skip = 2 |
103 | + continue |
104 | + if self.pgp_end_re.match(line): |
105 | + break |
106 | + result.append(line) |
107 | + return result |
108 | + |
109 | + def getCommands(self): |
110 | + """Get the actual command lines from the script.""" |
111 | + logger.info("parsing commands") |
112 | + result = [] |
113 | + legacy_lines = [] |
114 | + for line in self.getLines(): |
115 | + if not self.isCommand(line): |
116 | + continue |
117 | + # identify and construct commands |
118 | + star_match = Script.star_re.match(line) |
119 | + debug_match = Script.debug_re.match(line) |
120 | + if star_match: |
121 | + result.append(MergeCommand(self, |
122 | + self._branch_spec_handler, |
123 | + self._configp, |
124 | + star_match.group(1), |
125 | + star_match.group(2))) |
126 | + elif debug_match: |
127 | + result.append(DebugCommand(self, |
128 | + self._branch_spec_handler, |
129 | + self._configp)) |
130 | + return result |
131 | + |
132 | + def getSubmissionTime(self): |
133 | + """Return the time the script was submitted.""" |
134 | + return self.submission_time |
135 | + |
136 | + def isCommand(self, line): |
137 | + """Return true if line looks like a valid command line.""" |
138 | + # XXX: This is not ready yet, its still in CommandRunners guts. |
139 | + return True |
140 | + |
141 | + def run(self): |
142 | + """Run this script. |
143 | + |
144 | + :return: (successes, unrecognized, output) |
145 | + """ |
146 | + successes = [] |
147 | + unrecognized = [] |
148 | + output = [] |
149 | + for command in self.getCommands(): |
150 | + command_result = command.run() |
151 | + successes.extend(command_result[0]) |
152 | + unrecognized.extend(command_result[1]) |
153 | + output.extend(command_result[2]) |
154 | + return successes, unrecognized, output |
155 | + |
156 | + |
157 | +def find_patches(queuedir, logger, verify_sigs, branch_spec_handler, configp): |
158 | + patches=[] |
159 | + patches_re=re.compile('^patch\.\d+$') |
160 | + for f in os.listdir(queuedir): |
161 | + if patches_re.match(f): |
162 | + fname=os.path.join(queuedir, f) |
163 | + submission_time = os.stat(fname)[stat.ST_MTIME] |
164 | + patches.append((Script(fname, logger, verify_sigs, |
165 | + submission_time, branch_spec_handler, |
166 | + configp), |
167 | + f)) |
168 | + def sortpatches(a, b): |
169 | + return cmp(a[1], b[1]) |
170 | + patches.sort(sortpatches) |
171 | + return [patch[0] for patch in patches] |
172 | + |
173 | +def read_email(logger, file=None): |
174 | + """Read an email and check it has the required structure for commands.""" |
175 | + if not file: |
176 | + file = sys.stdin |
177 | + msg = email.message_from_file(file) |
178 | + sender = msg['From'] |
179 | + logger.info("recieved email from %s", sender) |
180 | + subject = msg['Subject'] |
181 | + if not sender: |
182 | + raise PQMException(None, "No From specified") |
183 | + if (not subject) or subject=='': |
184 | + raise PQMException(sender, "No Subject specified") |
185 | + text = None |
186 | + sig = None |
187 | + if msg.is_multipart(): |
188 | + print ("Multipart support is known buggy. Patches, or better still" |
189 | + "unittest tests with or without patches solicited.") |
190 | + parts = msg.get_payload() |
191 | + if not len(parts) == 2: |
192 | + raise PQMException(sender, |
193 | + "Multipart message must have exactly two parts") |
194 | + if not parts[0].get_content_type() == 'text/plain': |
195 | + raise PQMException(sender, |
196 | + "First part of multipart message must be text/plain") |
197 | + if not parts[1].get_content_type() == 'application/pgp-signature': |
198 | + raise PQMException(sender, |
199 | + "Second part of multipart message must be" |
200 | + " application/pgp-signature") |
201 | + return (sender, |
202 | + subject, |
203 | + parts[0].get_payload(), |
204 | + parts[1].get_payload()) |
205 | + else: |
206 | + return (sender, subject, msg.get_payload(), None) |
207 | + |
208 | + |
209 | +class PQMException(Exception): |
210 | + """PWM specific exceptions derive from this class.""" |
211 | + def __init__(self, sender, msg): |
212 | + self.sender = sender |
213 | + self.msg = msg |
214 | + |
215 | + def __str__(self): |
216 | + return `self.msg` |
217 | + |
218 | +def verify_sig(sender, msg, sig, check_replay, logger): |
219 | +>>>>>>> MERGE-SOURCE |
220 | """Verify the GPG signature on a message.""" |
221 | verifier = GPGSigVerifier([keyring], gpgv=gpgv_path) |
222 | try: |
223 | @@ -152,6 +363,469 @@ |
224 | return output |
225 | |
226 | |
227 | +<<<<<<< TREE |
228 | +======= |
229 | +class Command(object): |
230 | + """A single command in a script. |
231 | + |
232 | + These are generated by the parser for specific messages - such as Script |
233 | + for the original format pqm messages. |
234 | + """ |
235 | + |
236 | + def __init__(self, script, branch_spec_handler, configp): |
237 | + """Construct a command. |
238 | + |
239 | + :param script: The Script object with user details etc for this command. |
240 | + :param branch_spec_handler: The branch specification handler for this |
241 | + PQM. |
242 | + :param configp: The ConfigParser for this PQM. |
243 | + """ |
244 | + self.script = script |
245 | + self.branch_spec_handler = branch_spec_handler |
246 | + self.configp = configp |
247 | + self._url_override_mapper = None |
248 | + self.successful = [] |
249 | + self._vcs = BzrHandler() |
250 | + |
251 | + def asHTML(self): |
252 | + """Return an HTML representation of the command.""" |
253 | + raise NotImplementedError(self.asHTML) |
254 | + |
255 | + def __eq__(self, other): |
256 | + # command top level objects have no intrinsic state. |
257 | + return isinstance(other, Command) |
258 | + |
259 | + def getBranchConfig(self, branch_spec): |
260 | + return self.branch_spec_handler.get_target_config(branch_spec) |
261 | + |
262 | + def getProjects(self): |
263 | + """Return the projects this Command will affect.""" |
264 | + raise NotImplementedError(self.getProjects) |
265 | + |
266 | + def log_with_status(self, logger, message, *args): |
267 | + """Log message to the logfile, and to the status file.""" |
268 | + logger.info(message, *args) |
269 | + self.branch_spec_handler.status.line(message % args) |
270 | + |
271 | + def _branch_name(self, branchspec): |
272 | + elements = branchspec.split('/') |
273 | + return elements[-1] |
274 | + |
275 | + def branch_from_config(self, config, sender, branch_list, fullpath): |
276 | + """build the config config in dir fullpath. Then |
277 | + find one of branch_list in the config, and |
278 | + return the path to it.""" |
279 | + # TODO: probe the config path for the url to get - |
280 | + # config package isn't guaranteed arch-style. |
281 | + config_segments = config.split('/') |
282 | + config_path = config_segments.pop() |
283 | + done = False |
284 | + while not done: |
285 | + try: |
286 | + config_branch = '/'.join(config_segments) |
287 | + self._vcs.make_local_dir(sender, config_branch, fullpath) |
288 | + done = True |
289 | + except PQMTlaFailure, e: |
290 | + if config_path == config or len(config_segments) == 1: |
291 | + raise |
292 | + config_path = '/'.join((config_segments.pop(), config_path)) |
293 | + if os.path.exists(fullpath): |
294 | + shutil.rmtree(fullpath) |
295 | + try: |
296 | + stream = urllib.urlopen(os.path.join(fullpath, config_path)) |
297 | + except IOError,e: |
298 | + if e.errno == errno.ENOENT: |
299 | + raise PQMException(sender, "No such config '%s'" % config_path) |
300 | + else: |
301 | + raise PQMException(sender, "IOError opening configuration '%s'" % config_path) |
302 | + cm_config = config_manager.Config(fromStream=stream, |
303 | + override_mapper=self._get_url_override_mapper()) |
304 | + cm_config.build(fullpath) |
305 | + if config_branch in branch_list: |
306 | + return fullpath |
307 | + for path, entry in cm_config.get_entries().items(): |
308 | + # probably want a url comparison that understands url parameters |
309 | + # TODO: but that can wait. |
310 | + if (entry.url in branch_list or |
311 | + (entry.url.startswith('arch://') and |
312 | + entry.url[7:] in branch_list)): |
313 | + return os.path.join(fullpath, path) |
314 | + raise PQMException(sender, |
315 | + "Branch %s not found in config" % branch) |
316 | + |
317 | + def check_commit_regex(self, branch, config): |
318 | + """Confirm that commit message matches any regexp supplied in the |
319 | + configuration file. |
320 | + """ |
321 | + if not config["commit_re"]: |
322 | + # No regexp, therefore accept anything |
323 | + return |
324 | + regex = config["commit_re"] |
325 | + if re.match(regex, self.commitmsg): |
326 | + # Regexp matched, accept the commitmsg |
327 | + return |
328 | + raise PQMException(self.script.getSender(), |
329 | + "Commit message [%s] does not match commit_re [%s]" |
330 | + % (self.commitmsg, regex) |
331 | + ) |
332 | + |
333 | + def check_target(self, branch, line): |
334 | + """Check that the sender is allowed to commit to torepo/to_revision""" |
335 | + # FIXME check gpg etc etc. |
336 | + try: |
337 | + self.getBranchConfig(branch) |
338 | + except KeyError: |
339 | + raise PQMCmdFailure(self.script.getSender(), |
340 | + self.successful, |
341 | + line, |
342 | + ["Sender not authorised to commit to branch %s" |
343 | + % branch]) |
344 | + |
345 | + def cleanup_wd(self): |
346 | + """Cleans up all possible working dirs.""" |
347 | + self.log_with_status(logger, "cleaning working directory") |
348 | + for top in os.listdir(workdir): |
349 | + self.rm_rf(os.path.join(workdir, top)) |
350 | + for branch, value in self.branch_spec_handler._specs.items(): |
351 | + possible_dir = value['build_dir'] |
352 | + if not possible_dir: |
353 | + continue |
354 | + if not os.path.exists(possible_dir): |
355 | + continue |
356 | + for top in os.listdir(possible_dir): |
357 | + self.rm_rf(os.path.join(possible_dir, top)) |
358 | + |
359 | + def _make_wd_path(self, directory, branch): |
360 | + """Make the path to be used for branch in directory""" |
361 | + elements = branch.split('/') |
362 | + index = 0 |
363 | + if elements[0] == '': |
364 | + index = 1 |
365 | + result = os.path.join(directory, elements[index]) |
366 | + if result[-1] == ':': |
367 | + result = result[:-1] |
368 | + return result |
369 | + |
370 | + def prep_wd(self, sender, dirpath, branch, config): |
371 | + branch_name = self._branch_name(branch) |
372 | + if not os.access(dirpath, os.W_OK): |
373 | + os.mkdir(dirpath) |
374 | + # set revision to the last element of the branch... |
375 | + if config: |
376 | + elements=re.split("/", config) |
377 | + fullpath=os.path.join(dirpath, "%s---%s" % (elements[0], branch_name)) |
378 | + else: |
379 | + fullpath=os.path.join(dirpath, branch_name) |
380 | + |
381 | + if os.access(fullpath, os.W_OK): |
382 | + logger.error("Working dir already exists: " + fullpath) |
383 | + raise PQMException(sender, "Working dir already exists: " + fullpath) |
384 | + return fullpath |
385 | + |
386 | + def _get_url_override_mapper(self): |
387 | + """Get a URL override mapper from the config.""" |
388 | + if self._url_override_mapper is not None: |
389 | + return self._url_override_mapper |
390 | + mapper = config_manager.URLMapper() |
391 | + if not self.configp.has_section('location overrides'): |
392 | + return mapper |
393 | + for target, source in self.configp.section_items('location overrides'): |
394 | + equal_pos = source.find('=') |
395 | + if (equal_pos > -1 and target.find('//') == -1 and |
396 | + source.startswith('//')): |
397 | + # : triggers separation, THANKS config-parser. |
398 | + target = target + ":" + source[:equal_pos] |
399 | + source = source[equal_pos + 1:] |
400 | + print target, source |
401 | + mapper.add_map(source, target) |
402 | + self._url_override_mapper = mapper |
403 | + return mapper |
404 | + |
405 | + def validate_revision(self, branch): |
406 | + if not self._vcs.branch_exists(self.script.getSender(), branch): |
407 | + raise PQMCmdFailure(self.script.getSender(), |
408 | + self.successful, |
409 | + line, |
410 | + self.output + self._vcs.last_error.output) |
411 | + |
412 | + def rm_rf(self, top): |
413 | + os.chmod(top, 0755) |
414 | + for root, dirs, files in os.walk(top, topdown=True): |
415 | + for name in files: |
416 | + if not os.path.islink(os.path.join(root, name)): |
417 | + os.chmod(os.path.join(root, name), 0644) |
418 | + for name in dirs: |
419 | + if not os.path.islink(os.path.join(root, name)): |
420 | + os.chmod(os.path.join(root, name), 0755) |
421 | + |
422 | + for root, dirs, files in os.walk(top, topdown=False): |
423 | + for name in files: |
424 | + os.remove(os.path.join(root, name)) |
425 | + for name in dirs: |
426 | + if os.path.islink(os.path.join(root, name)): |
427 | + os.remove(os.path.join(root, name)) |
428 | + else: |
429 | + os.rmdir(os.path.join(root, name)) |
430 | + |
431 | + def run(self): |
432 | + """Run this script. |
433 | + |
434 | + :return: (successes, unrecognized, output) |
435 | + """ |
436 | + # current prelude code useful to all commands |
437 | + # may need to shrink or grow in the future. |
438 | + self.successful = [] |
439 | + self.unrecognized = [] |
440 | + self.output = [] |
441 | + self.commitmsg = self.script.getSubject() |
442 | + self.user_email = self.script.signing_email |
443 | + self.sender = self.script.getSender() |
444 | + |
445 | + def run_in_dir(self, local_dir, method, *args): |
446 | + """call method after chdiring to local_dir, then chdir back.""" |
447 | + orig_dir = os.getcwdu() |
448 | + try: |
449 | + os.chdir(local_dir) |
450 | + return method(*args) |
451 | + finally: |
452 | + os.chdir(orig_dir) |
453 | + |
454 | + def run_precommit(self, branch, config, line, dir): |
455 | + command = PrecommitCommand( |
456 | + self.script, |
457 | + self.branch_spec_handler, |
458 | + self.configp, |
459 | + config, |
460 | + line, |
461 | + dir) |
462 | + successes, unrecognized, command_output = command.run() |
463 | + self.successful.extend(successes) |
464 | + self.unrecognized.extend(unrecognized) |
465 | + self.output.extend(command_output) |
466 | + |
467 | + |
468 | +class DebugCommand(Command): |
469 | + """Turns on debug mode.""" |
470 | + |
471 | + def asHTML(self): |
472 | + return "debug" |
473 | + |
474 | + def getProjects(self): |
475 | + """Debug applies to no projects.""" |
476 | + return set() |
477 | + |
478 | + def run(self): |
479 | + """Turn debug mode on.""" |
480 | + super(DebugCommand, self).run() |
481 | + self.script.debug = True |
482 | + return ['debug'], [], ['Debug mode enabled.'] |
483 | + |
484 | + |
485 | +class MergeCommand(Command): |
486 | + """A merge request.""" |
487 | + |
488 | + def __init__(self, script, branch_spec_handler, configp, from_branch, to_branch): |
489 | + """Construct a merge command. |
490 | + |
491 | + :param from_branch: The branch specification to merge from. |
492 | + :param to_branch: The branch specification to merge into. |
493 | + """ |
494 | + super(MergeCommand, self).__init__(script, branch_spec_handler, configp) |
495 | + self.from_branch = from_branch |
496 | + self.to_branch = to_branch |
497 | + |
498 | + def asHTML(self): |
499 | + return cgi.escape("Merge %s %s" % (self.from_branch, self.to_branch)) |
500 | + |
501 | + def __eq__(self, other): |
502 | + return (super(MergeCommand, self).__eq__(other) and |
503 | + isinstance(other, MergeCommand) and |
504 | + self.from_branch == other.from_branch and |
505 | + self.to_branch == other.to_branch) |
506 | + |
507 | + def getProjects(self): |
508 | + b, config = self.getBranchConfig(self.to_branch) |
509 | + project = config['project'] |
510 | + if project is not None: |
511 | + return set([project]) |
512 | + else: |
513 | + return set() |
514 | + |
515 | + def wrap_command(self, command, line, sender, *args): |
516 | + try: |
517 | + self.output += command(sender, *args) |
518 | + except PQMTlaFailure, e: |
519 | + raise PQMCmdFailure(sender, self.successful, line, self.output + e.output) |
520 | + |
521 | + def do_merge(self, from_repo_revision, to_repo_revision, merge_name, line): |
522 | + sender = self.script.getSender() |
523 | + # Star-merge |
524 | + self.check_target(to_repo_revision, line) |
525 | + to_repo_revision, config = self.getBranchConfig(to_repo_revision) |
526 | + self.validate_revision(from_repo_revision) |
527 | + self.validate_revision(to_repo_revision) |
528 | + self.check_commit_regex(to_repo_revision, config) |
529 | + logger.info("current cwd is %s", os.getcwd()) |
530 | + logger.info("getting working dir for %s", to_repo_revision) |
531 | + dir = self.get_wd(sender, to_repo_revision, config) |
532 | + origdir = os.getcwd() |
533 | + merge_line = 'Executing %s %s at %s' % (merge_name, |
534 | + from_repo_revision, |
535 | + time.strftime('%c')) |
536 | + self.log_with_status(logger, merge_line) |
537 | + self.output += [ |
538 | + '\n', |
539 | + merge_line, |
540 | + '\n', |
541 | + ] |
542 | + self.wrap_command(self._vcs.do_star_merge, line, sender, |
543 | + from_repo_revision, dir) |
544 | + self.run_precommit(to_repo_revision, config, line, dir) |
545 | + os.chdir(origdir) |
546 | + self.log_with_status(logger, "success: %s", line) |
547 | + self.successful.append(line) |
548 | + self.output += ['\n', '%s succeeded at %s' % (merge_name, time.strftime('%c')), '\n'] |
549 | + self._vcs.commit(sender, dir, self.commitmsg, to_repo_revision, config) |
550 | + |
551 | + def get_wd(self, sender, branch, config): |
552 | + dirpath = self._make_wd_path(workdir, branch) |
553 | + commiters = config['commiters'] |
554 | + if commiters and not self.user_email in groups[commiters]: |
555 | + logger.error("%s is not permitted to commit to %s", |
556 | + self.user_email, |
557 | + branch) |
558 | + raise PQMException(sender, |
559 | + "%s is not permitted to commit to %s" % ( |
560 | + self.user_email, branch)) |
561 | + possible_dir = config['build_dir'] |
562 | + if possible_dir: |
563 | + dirpath = self._make_wd_path(possible_dir, branch) |
564 | + build_config = config['build_config'] |
565 | + fullpath = self.prep_wd(sender, dirpath, branch, build_config) |
566 | + if build_config: |
567 | + branch_list = [branch] |
568 | + if config.get('published_at', None): |
569 | + branch_list.append(config['published_at']) |
570 | + return self.branch_from_config(build_config, sender, branch_list, fullpath) |
571 | + else: |
572 | + self._vcs.make_local_dir(sender, branch, fullpath) |
573 | + return fullpath |
574 | + |
575 | + def run(self): |
576 | + super(MergeCommand, self).run() |
577 | + self.cleanup_wd() |
578 | + self.do_merge(from_repo_revision=self.from_branch, |
579 | + to_repo_revision=self.to_branch, |
580 | + merge_name='star-merge', |
581 | + line='merge %s %s' % (self.from_branch, self.to_branch)) |
582 | + return self.successful, self.unrecognized, self.output |
583 | + |
584 | + |
585 | +class PrecommitCommand(Command): |
586 | + """A pre-commit command.""" |
587 | + |
588 | + def __init__(self, script, branch_spec_handler, configp, config, line, dir): |
589 | + """Construct a merge command. |
590 | + |
591 | + :param config: The branch config to use for the precommit logic. |
592 | + :param line: The parsed line(s) that triggered the precommit hook. |
593 | + :param dir: The directory to run the precommit check in. |
594 | + """ |
595 | + super(PrecommitCommand, self).__init__(script, branch_spec_handler, configp) |
596 | + self.config = config |
597 | + self.line = line |
598 | + self.dir = dir |
599 | + |
600 | + def asHTML(self): |
601 | + return cgi.escape("Pre-commit tests") |
602 | + |
603 | + def __eq__(self, other): |
604 | + return (super(PrecommitCommand, self).__eq__(other) and |
605 | + isinstance(other, PrecommitCommand) and |
606 | + self.from_branch == other.from_branch and |
607 | + self.to_branch == other.to_branch) |
608 | + |
609 | + def getProjects(self): |
610 | + b, config = self.getBranchConfig(self.to_branch) |
611 | + project = config['project'] |
612 | + if project is not None: |
613 | + return set([project]) |
614 | + else: |
615 | + return set() |
616 | + |
617 | + def iter_pipe_lines(self, pipe): |
618 | + '''process the output of pipe into lines and yield them.''' |
619 | + read_set = [] |
620 | + if pipe.fromchild: |
621 | + read_set.append(pipe.fromchild) |
622 | + if pipe.childerr: |
623 | + read_set.append(pipe.childerr) |
624 | + |
625 | + out_data = err_data = '' |
626 | + while read_set: |
627 | + rlist, wlist, xlist = select.select(read_set, [], []) |
628 | + |
629 | + if pipe.fromchild in rlist: |
630 | + out_chunk = os.read(pipe.fromchild.fileno(), 1024) |
631 | + if out_chunk == '': |
632 | + pipe.fromchild.close() |
633 | + read_set.remove(pipe.fromchild) |
634 | + out_data += out_chunk |
635 | + while '\n' in out_data: |
636 | + pos = out_data.find('\n') |
637 | + yield out_data[:pos+1] |
638 | + out_data = out_data[pos+1:] |
639 | + |
640 | + if pipe.childerr in rlist: |
641 | + err_chunk = os.read(pipe.childerr.fileno(), 1024) |
642 | + if err_chunk == '': |
643 | + pipe.childerr.close() |
644 | + read_set.remove(pipe.childerr) |
645 | + err_data += err_chunk |
646 | + while '\n' in err_data: |
647 | + pos = err_data.find('\n') |
648 | + yield err_data[:pos+1] |
649 | + err_data = err_data[pos+1:] |
650 | + |
651 | + select.select([],[],[],.1) # give a little time for buffers to fill |
652 | + |
653 | + def run(self): |
654 | + super(PrecommitCommand, self).run() |
655 | + hook = self.config['precommit_hook'] |
656 | + if not hook: |
657 | + hook = precommit_hook |
658 | + if hook: |
659 | + logger.info("running precommit hook: %s" % (hook,)) |
660 | + self.output.extend(['\n', 'Executing pre-commit hook %s at %s' % (hook, time.strftime('%c')), '\n']) |
661 | + child = self.run_in_dir(self.dir, popen2.Popen4, hook) |
662 | + child.tochild.close() |
663 | + for line in self.iter_pipe_lines(child): |
664 | + self.branch_spec_handler.status.line(line) |
665 | + self.output.append(line) |
666 | + # self.output.extend(child.fromchild.readlines()) |
667 | + ecode = child.wait() |
668 | + if not ((ecode is None) or (ecode == 0)): |
669 | + self.output.append('\npre-commit hook failed with error code %d at %s\n' % (ecode - 255, time.strftime('%c'))) |
670 | + raise PQMCmdFailure(self.sender, self.successful, self.line, self.output) |
671 | + self.output += ['\n', 'pre-commit hook succeeded at %s' % (time.strftime('%c')), '\n'] |
672 | + return self.successful, self.unrecognized, self.output |
673 | + |
674 | + |
675 | +class PQMCmdFailure(Exception): |
676 | + def __init__(self, sender, goodcmds, badcmd, output): |
677 | + self.sender = sender |
678 | + self.goodcmds = goodcmds |
679 | + self.badcmd = badcmd |
680 | + self.output = output |
681 | + self.args = (sender, goodcmds, badcmd, output) |
682 | + |
683 | +class PQMTlaFailure(PQMException): |
684 | + def __init__(self, sender, output): |
685 | + self.sender = sender |
686 | + self.output = output |
687 | + self.msg = str(output) |
688 | + |
689 | +>>>>>>> MERGE-SOURCE |
690 | def popen_noshell_with_input(cmd, inputfd, *args): |
691 | (stdin, stdout) = os.pipe() |
692 | pid = os.fork() |
693 | @@ -183,6 +857,7 @@ |
694 | def popen_noshell(cmd, *args): |
695 | return apply(popen_noshell_with_input, [cmd, None] + list(args)) |
696 | |
697 | +<<<<<<< TREE |
698 | class VCSHandler: |
699 | |
700 | def branch_exists(self, sender, branch): |
701 | @@ -361,6 +1036,10 @@ |
702 | |
703 | |
704 | class Bazaar2Handler(VCSHandler): |
705 | +======= |
706 | + |
707 | +class BzrHandler(object): |
708 | +>>>>>>> MERGE-SOURCE |
709 | |
710 | def __init__(self): |
711 | try: |
712 | |
713 | === modified file 'pqm/tests/test_pqm.py' |
714 | --- pqm/tests/test_pqm.py 2010-04-19 02:40:26 +0000 |
715 | +++ pqm/tests/test_pqm.py 2010-05-04 17:12:27 +0000 |
716 | @@ -208,12 +208,20 @@ |
717 | self.assertEqual(script.getLines(), |
718 | [("star-merge http://www.example.com/foo/bar " |
719 | "http://www.example.com/bar/baz")]) |
720 | +<<<<<<< TREE |
721 | self.assertEqual([MergeCommand(None, |
722 | None, |
723 | None, |
724 | 'http://www.example.com/foo/bar', |
725 | 'http://www.example.com/bar/baz', |
726 | self.queue.manager)], |
727 | +======= |
728 | + self.assertEqual([pqm.MergeCommand(None, |
729 | + None, |
730 | + None, |
731 | + 'http://www.example.com/foo/bar', |
732 | + 'http://www.example.com/bar/baz')], |
733 | +>>>>>>> MERGE-SOURCE |
734 | script.getCommands()) |
735 | |
736 | def testGPGFields(self): |
737 | @@ -309,7 +317,7 @@ |
738 | return "a_sender" |
739 | |
740 | |
741 | -class TestCommandRunner(unittest.TestCase): |
742 | +class TestCommand(unittest.TestCase): |
743 | |
744 | def setUp(self): |
745 | unittest.TestCase.setUp(self) |
746 | @@ -327,6 +335,7 @@ |
747 | self.assertEqual(star_match.group(1), 'file:///url1') |
748 | self.assertEqual(star_match.group(2), 'file:///url2') |
749 | |
750 | +<<<<<<< TREE |
751 | def test_get_arch_impl(self): |
752 | self.logs = [] |
753 | configp = ConfigParser() |
754 | @@ -342,12 +351,19 @@ |
755 | # something went wrong other than baz not being installed. |
756 | raise |
757 | |
758 | +======= |
759 | +>>>>>>> MERGE-SOURCE |
760 | def test_check_revision(self): |
761 | configp = ConfigParser() |
762 | handler = pqm.BranchSpecOptionHandler(configp) |
763 | handler._specs = {'file:///tmp/foo':{}, |
764 | 'file:///tmp/bar/':{}} |
765 | +<<<<<<< TREE |
766 | runner = self.make_runner(MockScript(), handler, configp) |
767 | +======= |
768 | + runner = pqm.Command(None, handler, configp) |
769 | + runner.script = MockScript() |
770 | +>>>>>>> MERGE-SOURCE |
771 | runner.check_target('file:///tmp/foo', 'blah') |
772 | self.assertRaises(PQMCmdFailure, |
773 | runner.check_target, |
774 | @@ -362,7 +378,11 @@ |
775 | def test__make_wd_path(self): |
776 | configp = ConfigParser() |
777 | handler = pqm.BranchSpecOptionHandler(configp) |
778 | +<<<<<<< TREE |
779 | runner = self.make_runner(None, handler, configp) |
780 | +======= |
781 | + runner = pqm.Command(None, handler, configp) |
782 | +>>>>>>> MERGE-SOURCE |
783 | self.assertEqual(runner._make_wd_path('/foo', '/foo/bar'), '/foo/foo') |
784 | self.assertEqual(runner._make_wd_path('/foo', 'file:///foo/bar'), |
785 | '/foo/file') |
786 | @@ -372,7 +392,11 @@ |
787 | configp = ConfigParser() |
788 | pqm.pqm_subdir = '/tmp' # ewww |
789 | handler = pqm.BranchSpecOptionHandler(configp) |
790 | +<<<<<<< TREE |
791 | runner = self.make_runner(None, handler, configp) |
792 | +======= |
793 | + runner = pqm.Command(None, handler, configp) |
794 | +>>>>>>> MERGE-SOURCE |
795 | self.assertEqual(runner._branch_name('foo'), 'foo') |
796 | self.assertEqual(runner._branch_name('file:///home/bar/foo'), 'foo') |
797 | self.assertEqual(runner._branch_name('file:///home/bar/foo'), 'foo') |
798 | @@ -381,7 +405,11 @@ |
799 | configp = ConfigParser() |
800 | configp.readfp(StringIO(sample_config_with_workdir)) |
801 | handler = pqm.BranchSpecOptionHandler(configp) |
802 | +<<<<<<< TREE |
803 | runner = self.make_runner(MockScript(), handler, configp) |
804 | +======= |
805 | + runner = pqm.Command(None, handler, configp) |
806 | +>>>>>>> MERGE-SOURCE |
807 | pqm.workdir = "test-workdir" |
808 | pqm.branch_specs={'path':{'build_dir':'nonexistant-dir'}} |
809 | os.mkdir("test-workdir") |
810 | @@ -393,25 +421,39 @@ |
811 | def test__get_url_override_mapper_no_overrides(self): |
812 | configp = ConfigParser() |
813 | handler = pqm.BranchSpecOptionHandler(configp) |
814 | +<<<<<<< TREE |
815 | runner = self.make_runner(None, handler, configp) |
816 | runner.configp = ConfigParser() |
817 | +======= |
818 | + runner = pqm.Command(None, handler, configp) |
819 | + runner.configp = pqm.ConfigParser() |
820 | +>>>>>>> MERGE-SOURCE |
821 | expected_mapper = config_manager.URLMapper() |
822 | self.assertEqual(expected_mapper, runner._get_url_override_mapper()) |
823 | |
824 | def test__get_url_override_mapper(self): |
825 | configp = ConfigParser() |
826 | handler = pqm.BranchSpecOptionHandler(configp) |
827 | +<<<<<<< TREE |
828 | runner = self.make_runner(None, handler, configp) |
829 | runner.configp = ConfigParser() |
830 | +======= |
831 | + runner = pqm.Command(None, handler, configp) |
832 | + runner.configp = pqm.ConfigParser() |
833 | +>>>>>>> MERGE-SOURCE |
834 | runner.configp.readfp(StringIO(sample_config_with_overrides)) |
835 | expected_mapper = config_manager.URLMapper() |
836 | expected_mapper.add_map("sftp://host/otherpath", "/local/path") |
837 | expected_mapper.add_map("righthandside", "scheme://something") |
838 | self.assertEqual(expected_mapper, runner._get_url_override_mapper()) |
839 | +<<<<<<< TREE |
840 | |
841 | |
842 | class TestCommand(unittest.TestCase): |
843 | |
844 | +======= |
845 | + |
846 | +>>>>>>> MERGE-SOURCE |
847 | def test_debug_command_sets_script_debug(self): |
848 | configp = ConfigParser() |
849 | pqm.pqm_subdir = '/tmp' # ewww |
850 | @@ -427,7 +469,7 @@ |
851 | self.assertTrue(script.debug) |
852 | |
853 | |
854 | -class FunctionalTestCommandRunner(unittest.TestCase): |
855 | +class FunctionalTestCommand(unittest.TestCase): |
856 | |
857 | def setUp(self): |
858 | from bzrlib.bzrdir import BzrDir |
859 | @@ -440,6 +482,7 @@ |
860 | def tearDown(self): |
861 | shutil.rmtree("bzrbranch") |
862 | |
863 | +<<<<<<< TREE |
864 | def test_get_branch_handler_arch(self): |
865 | # I would test this, but I don't want to add a full dependency on |
866 | # pybaz at this point |
867 | @@ -448,9 +491,12 @@ |
868 | ## TODO set a fake logger |
869 | pass |
870 | |
871 | +======= |
872 | +>>>>>>> MERGE-SOURCE |
873 | def branch_url(self): |
874 | branchurl = "file://%s" % os.path.abspath("bzrbranch") |
875 | return branchurl.replace('\\', '/') |
876 | +<<<<<<< TREE |
877 | |
878 | def test_get_branch_handler_bzr_file(self): |
879 | configp = ConfigParser() |
880 | @@ -480,6 +526,8 @@ |
881 | runner.set_current_vcs(self.branch_url(), "bzrbranch") |
882 | self.assertNotEqual(runner.get_vcs(), None) |
883 | self.failUnless(isinstance(runner.get_vcs(), pqm.Bazaar2Handler)) |
884 | +======= |
885 | +>>>>>>> MERGE-SOURCE |
886 | |
887 | |
888 | class BzrHandlerTestCase(TestCaseWithTransport): |
889 | @@ -512,7 +560,7 @@ |
890 | branch = Branch.open("bzrbranch") |
891 | self.assertEqual(branch.repository.get_revision(branch.last_revision()).message, |
892 | 'start branch.') |
893 | - handler = pqm.Bazaar2Handler() |
894 | + handler = pqm.BzrHandler() |
895 | config = {'publish_to':'bzrbranch-public'} |
896 | handler.commit("me", "bzrbranch", "wheee", "bzrbranch-parent", config) |
897 | for path in ['bzrbranch', 'bzrbranch-parent', 'bzrbranch-public']: |
898 | @@ -525,7 +573,7 @@ |
899 | branch = Branch.open("bzrbranch") |
900 | self.assertEqual(branch.repository.get_revision(branch.last_revision()).message, |
901 | 'start branch.') |
902 | - handler = pqm.Bazaar2Handler() |
903 | + handler = pqm.BzrHandler() |
904 | config = {'publish_to':'bzrbranch-public-missing'} |
905 | self.assertRaises(pqm.PQMTlaFailure, handler.commit, "me", |
906 | "bzrbranch", "wheee", "bzrbranch-parent", config) |
907 | @@ -539,7 +587,7 @@ |
908 | |
909 | def test_make_local_dir(self): |
910 | try: |
911 | - handler = pqm.Bazaar2Handler() |
912 | + handler = pqm.BzrHandler() |
913 | handler.make_local_dir("me", "bzrbranch", "proof") |
914 | from bzrlib.branch import Branch |
915 | branch = Branch.open("proof") |
916 | @@ -558,7 +606,7 @@ |
917 | contrib_branch.last_revision()).message, |
918 | 'add README') |
919 | self.failIf(os.path.exists("bzrbranch/README")) |
920 | - handler = pqm.Bazaar2Handler() |
921 | + handler = pqm.BzrHandler() |
922 | result = handler.do_star_merge("me", "branch-contributor", "bzrbranch") |
923 | self.assertEqual(result, ["merge successful"]) |
924 | branch = Branch.open("bzrbranch") |
925 | @@ -593,6 +641,7 @@ |
926 | file.write("Boo conflict YEAH!\n") |
927 | file.close() |
928 | contrib_tree.commit('add conflicting README') |
929 | +<<<<<<< TREE |
930 | handler = pqm.Bazaar2Handler() |
931 | err = self.assertRaises(pqm.PQMTlaFailure, handler.do_star_merge, "me", |
932 | "branch-contributor", "bzrbranch") |
933 | @@ -605,6 +654,10 @@ |
934 | unrelated = self.make_branch_and_tree('unrelated') |
935 | unrelated.commit("unrelated first commit") |
936 | handler = pqm.Bazaar2Handler() |
937 | +======= |
938 | + handler = pqm.BzrHandler() |
939 | + e = None |
940 | +>>>>>>> MERGE-SOURCE |
941 | try: |
942 | handler.do_star_merge("me", "unrelated", "bzrbranch") |
943 | except pqm.PQMTlaFailure, e: |