PQM

Merge lp:~oddbloke/pqm/remove-vcs-abstraction into lp:pqm

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
Reviewer Review Type Date Requested Status
pqm developers Pending
Review via email: mp+23367@code.launchpad.net
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:

Subscribers

People subscribed via source and target branches