Пример #1
0
 def _last_minute_checks(self, pending):
   """Does last minute checks on Rietvld before committing a pending patch."""
   pending_data = self.context.rietveld.get_issue_properties(
       pending.issue, True)
   if pending_data['commit'] != True:
     raise base.DiscardPending(pending, None)
   if pending_data['closed'] != False:
     raise base.DiscardPending(pending, None)
   if pending.description != pending_data['description'].replace('\r', ''):
     raise base.DiscardPending(pending, self.DESCRIPTION_UPDATED)
   commit_user = set([self.context.rietveld.email])
   expected = set(pending.reviewers) - commit_user
   actual  = set(pending_data['reviewers']) - commit_user
   # Try to be nice, if there was a drive-by review and the new reviewer left
   # a lgtm, don't abort.
   def is_approver(r):
     return any(
         m.get('approval') for m in pending_data['messages']
         if m['sender'] == r)
   drivers_by = [r for r in (actual - expected) if not is_approver(r)]
   if drivers_by:
     # That annoying driver-by.
     raise base.DiscardPending(
         pending,
         'List of reviewers changed. %s did a drive-by without LGTM\'ing!' %
         ','.join(drivers_by))
   if pending.patchset != pending_data['patchsets'][-1]:
     raise base.DiscardPending(pending,
         'Commit queue failed due to new patchset.')
Пример #2
0
  def _commit_patch(self, pending):
    """Commits the pending patch to the repository.

    Do the checkout and applies the patch.
    """
    try:
      # Make sure to apply on HEAD.
      pending.revision = None
      pending.apply_patch(self.context, True)
      commit_message = '%s\n\nReview URL: %s/%s' % (
          pending.description,
          self.context.rietveld.url,
          pending.issue)
      pending.revision = self.context.checkout.commit(
          commit_message, pending.owner)

      self.recent_commit_timestamps.append(time.time())
      self.recent_commit_timestamps = (
          self.recent_commit_timestamps[-(self.MAX_COMMIT_BURST + 1):])

      if not pending.revision:
        raise base.DiscardPending(pending, 'Failed to commit patch.')
      self._close_issue(pending)
    except (checkout.PatchApplicationFailed, patch.UnsupportedPatchFormat), e:
      raise base.DiscardPending(pending, str(e))
Пример #3
0
 def apply_patch(self, context_obj, prepare):
     """Applies the pending patch to the checkout and throws if it fails."""
     try:
         if prepare:
             self.prepare_for_patch(context_obj)
         patches = context_obj.rietveld.get_patch(self.issue, self.patchset)
         if not patches:
             raise base.DiscardPending(
                 self, 'No diff was found for this patchset.')
         if self.relpath:
             patches.set_relpath(self.relpath)
         self.files = [p.filename for p in patches]
         if not self.files:
             raise base.DiscardPending(
                 self, 'No file was found in this patchset.')
         context_obj.checkout.apply_patch(patches)
     except (checkout.PatchApplicationFailed,
             patch.UnsupportedPatchFormat) as e:
         raise base.DiscardPending(self, str(e))
     except subprocess2.CalledProcessError as e:
         out = 'Failed to apply the patch.'
         if e.stdout:
             out += '\n%s' % e.stdout
         raise base.DiscardPending(self, out)
     except urllib2.HTTPError as e:
         raise base.DiscardPending(self, (
             'Failed to request the patch to try. Please note that binary files '
             'are still unsupported at the moment, this is being worked on.\n\n'
             'Thanks for your patience.\n\n%s') % e)
Пример #4
0
  def _pending_run_verifiers(cls, pending, verifiers):
    """Runs verifiers on a pending change.

    Returns True if all Verifiers were run.
    """
    for verifier in verifiers:
      if verifier.name in pending.verifications:
        logging.warning(
            'Re-running verififer %s for issue %s' % (
                verifier.name, pending.issue))
      verifier.verify(pending)
      assert verifier.name in pending.verifications
      if pending.get_state() == base.IGNORED:
        assert pending.verifications[verifier.name].get_state() == base.IGNORED
        # Remove all the other verifiers since we need to keep it in the
        # 'datastore' to not retry this issue constantly.
        for key in pending.verifications.keys():
          if key != verifier.name:
            del pending.verifications[key]
        return False
      if pending.get_state() == base.FAILED:
        # Throw if it didn't pass, so the error message is not lost.
        raise base.DiscardPending(
            pending, pending.error_message() or cls.FAILED_NO_MESSAGE)
    return True
Пример #5
0
 def apply_patch(self, context_obj, prepare):
   """Applies the pending patch to the checkout and throws if it fails."""
   try:
     if prepare:
       self.prepare_for_patch(context_obj)
     patches = context_obj.rietveld.get_patch(self.issue, self.patchset)
     if not patches:
       raise base.DiscardPending(
           self, 'No diff was found for this patchset.')
     if self.relpath:
       patches.set_relpath(self.relpath)
     self.files = [p.filename for p in patches]
     if not self.files:
       raise base.DiscardPending(
           self, 'No file was found in this patchset.')
     context_obj.checkout.apply_patch(patches)
   except (checkout.PatchApplicationFailed, patch.UnsupportedPatchFormat), e:
     raise base.DiscardPending(self, str(e))
Пример #6
0
    def _send_jobs(self, pending, jobs, need_prepare, builders_and_tests,
                   job_name):
        for job in jobs:
            job.tries = job.tries or 0
            job.tries += 1
            if job.tries > 4:
                raise base.DiscardPending(pending, (
                    'The commit queue went berserk retrying too often for a\n'
                    'seemingly flaky test. Builder is %s, revision is %s, job name\n'
                    'was %s.') % (job.builder, job.revision, job_name))

        builders = sorted(job.builder for job in jobs)
        assert len(set(builders)) == len(builders)

        revision = set(job.revision for job in jobs)
        assert len(revision) == 1
        revision = revision.pop()

        clobber = set(job.clobber for job in jobs)
        assert len(clobber) == 1
        clobber = clobber.pop()

        for job in jobs:
            job.result = None
            job.build = None
            job.name = job_name
            job.tests = builders_and_tests[job.builder]

        if need_prepare:
            # Running from inside update_status(), the patch wasn't applied. Do it
            # now.
            pending.revision = revision
            pending.apply_patch(self.context, True)
        self._send_job(pending, revision, clobber, builders_and_tests,
                       job_name)
        for builder in builders:
            # Signal a new try job was sent.
            info = {
                'builder': builder,
                'clobber': job.clobber,
                'job_name': job_name,
                'revision': revision,
            }
            self.send_status(pending, info)
        for job in jobs:
            job.sent = time.time()
Пример #7
0
 def _send_job(self, pending, revision, clobber, builders_and_tests,
               job_name):
     """Sends a try job."""
     assert revision
     cmd = [
         '--no_search', '--revision',
         '%s@%s' % (self.solution, revision), '--name', job_name, '--user',
         self.commit_user.split('@', 1)[0], '--email', self.commit_user,
         '--rietveld_url',
         self._patch_url(pending), '--issue',
         str(pending.issue), '--patchset',
         str(pending.patchset)
     ]
     cmd.extend(self.extra_flags)
     for builder in sorted(builders_and_tests):
         cmd.append('--bot')
         tests = builders_and_tests[builder]
         if tests:
             cmd.append('%s:%s' % (builder, ','.join(tests)))
         else:
             cmd.append(builder)
     if clobber:
         cmd.append('--clobber')
     # TODO(maruel): use GitChange when relevant.
     change = presubmit_support.SvnChange(
         job_name, pending.description, self.context.checkout.project_path,
         [('M', f) for f in pending.files], pending.issue, pending.patchset,
         pending.owner)
     prev_dir = os.getcwd()
     try:
         os.chdir(self.context.checkout.project_path)
         trychange.TryChange(cmd, change, swallow_exception=True)
     except SystemExit, e:
         logging.error('_send_job(%s, %s, %s, %s, %s) failed!' %
                       (pending.pending_name(), revision, clobber,
                        builders_and_tests, job_name))
         raise base.DiscardPending(
             pending, 'Failed to send try job %s: %s' % (job_name, e))
Пример #8
0
 def prepare_for_patch(self, context_obj):
   self.revision = context_obj.checkout.prepare(self.revision)
   # Verify revision consistency.
   if not self.revision:
     raise base.DiscardPending(
         self, 'Internal error: failed to checkout. Please try again.')
Пример #9
0
class PendingCommit(base.Verified):
  """Represents a pending commit that is being processed."""
  persistent = base.Verified.persistent + [
      # Important since they tell if we need to revalidate and send try jobs
      # again or not if any of these value changes.
      'issue', 'patchset', 'description', 'files',
      # Only a cache, these values can be regenerated.
      'owner', 'reviewers', 'base_url', 'messages', 'relpath',
      # Only used after a patch was committed. Keeping here for try job retries.
      'revision',
  ]

  def __init__(
      self, issue, owner, reviewers, patchset, base_url, description,
      messages):
    super(PendingCommit, self).__init__()
    self.issue = issue
    self.owner = owner
    self.reviewers = reviewers
    self.patchset = patchset
    self.base_url = base_url
    self.description = description
    # Convert to unicode whenever necessary.
    if isinstance(self.description, str):
      self.description = self.description.decode('utf-8')
    assert isinstance(self.description, unicode)
    self.messages = messages
    for message in self.messages:
      # Save storage, no verifier really need 'text', just 'approval'.
      if 'text' in message:
        del message['text']
    self.revision = None
    self.relpath = ''
    self.files = []

  def pending_name(self):
    """The name that should be used for try jobs.

    It makes it possible to regenerate the try_jobs array if ever needed."""
    return '%d-%d' % (self.issue, self.patchset)

  def prepare_for_patch(self, context_obj):
    self.revision = context_obj.checkout.prepare(self.revision)
    # Verify revision consistency.
    if not self.revision:
      raise base.DiscardPending(
          self, 'Internal error: failed to checkout. Please try again.')

  def apply_patch(self, context_obj, prepare):
    """Applies the pending patch to the checkout and throws if it fails."""
    try:
      if prepare:
        self.prepare_for_patch(context_obj)
      patches = context_obj.rietveld.get_patch(self.issue, self.patchset)
      if not patches:
        raise base.DiscardPending(
            self, 'No diff was found for this patchset.')
      if self.relpath:
        patches.set_relpath(self.relpath)
      self.files = [p.filename for p in patches]
      if not self.files:
        raise base.DiscardPending(
            self, 'No file was found in this patchset.')
      context_obj.checkout.apply_patch(patches)
    except (checkout.PatchApplicationFailed, patch.UnsupportedPatchFormat), e:
      raise base.DiscardPending(self, str(e))
    except subprocess2.CalledProcessError, e:
      out = 'Failed to apply the patch.'
      if e.stdout:
        out += '\n%s' % e.stdout
      raise base.DiscardPending(self, out)
Пример #10
0
      self.files = [p.filename for p in patches]
      if not self.files:
        raise base.DiscardPending(
            self, 'No file was found in this patchset.')
      context_obj.checkout.apply_patch(patches)
    except (checkout.PatchApplicationFailed, patch.UnsupportedPatchFormat), e:
      raise base.DiscardPending(self, str(e))
    except subprocess2.CalledProcessError, e:
      out = 'Failed to apply the patch.'
      if e.stdout:
        out += '\n%s' % e.stdout
      raise base.DiscardPending(self, out)
    except urllib2.HTTPError, e:
      raise base.DiscardPending(
          self,
          ('Failed to request the patch to try. Please note that binary files'
          'are still unsupported at the moment, this is being worked on.\n\n'
          'Thanks for your patience.\n\n%s') % e)


class PendingQueue(model.PersistentMixIn):
  """Represents the queue of pending commits being processed."""
  persistent = ['pending_commits']

  def __init__(self):
    super(PendingQueue, self).__init__()
    self.pending_commits = []


class PendingManager(object):
  """Fetch new issues from rietveld, pass the issues through all of verifiers
Пример #11
0
    def _commit_patch(self, pending):
        """Commits the pending patch to the repository.

    Do the checkout and applies the patch.
    """
        try:
            try:
                # Make sure to apply on HEAD.
                pending.revision = None
                pending.apply_patch(self.context, True)
                # Commit it.
                commit_desc = git_cl.ChangeDescription(pending.description)
                if (self.context.server_hooks_missing
                        and self.context.rietveld.email != pending.owner):
                    commit_desc.update_reviewers(pending.reviewers)
                    commit_desc.append_footer('Author: ' + pending.owner)
                commit_desc.append_footer(
                    'Review URL: %s/%s' %
                    (self.context.rietveld.url, pending.issue))
                pending.revision = self.context.checkout.commit(
                    commit_desc.description, pending.owner)
                if not pending.revision:
                    raise base.DiscardPending(pending,
                                              'Failed to commit patch.')

                # Note that the commit succeeded for commit throttling.
                self.recent_commit_timestamps.append(time.time())
                self.recent_commit_timestamps = (
                    self.recent_commit_timestamps[-(self.MAX_COMMIT_BURST +
                                                    1):])

                viewvc_url = self.context.checkout.get_settings('VIEW_VC')
                issue_desc = git_cl.ChangeDescription(pending.description)
                msg = 'Committed: %s' % pending.revision
                if viewvc_url:
                    viewvc_url = '%s%s' % (viewvc_url.rstrip('/'),
                                           pending.revision)
                    msg = 'Committed: %s' % viewvc_url
                    issue_desc.append_footer(msg)

                # Update the CQ dashboard.
                self.context.status.send(
                    pending, {
                        'verification': 'commit',
                        'payload': {
                            'revision': pending.revision,
                            'output': msg,
                            'url': viewvc_url
                        }
                    })

                # Closes the issue on Rietveld.
                # TODO(csharp): Retry if exceptions are encountered.
                try:
                    self.context.rietveld.close_issue(pending.issue)
                    self.context.rietveld.update_description(
                        pending.issue, issue_desc.description)
                    self.context.rietveld.add_comment(
                        pending.issue,
                        'Change committed as %s' % pending.revision)
                except (urllib2.HTTPError, urllib2.URLError) as e:
                    # Ignore AppEngine flakiness.
                    logging.warning('Unable to fully close the issue')
                # And finally remove the issue. If the close_issue() call above failed,
                # it is possible the dashboard will be confused but it is harmless.
                try:
                    self.queue.get(pending.issue)
                except KeyError:
                    logging.error('Internal inconsistency for %d',
                                  pending.issue)
                self.queue.remove(pending.issue)
            except (checkout.PatchApplicationFailed,
                    patch.UnsupportedPatchFormat) as e:
                raise base.DiscardPending(pending, str(e))
            except subprocess2.CalledProcessError as e:
                stdout = getattr(e, 'stdout', None)
                out = 'Failed to apply the patch.'
                if stdout:
                    out += '\n%s' % stdout
                raise base.DiscardPending(pending, out)
        except base.DiscardPending as e:
            self._discard_pending(e.pending, e.status)
        except Exception as e:
            traceback.print_exc()
            # Swallow every exception in that code and move on. Make sure to send a
            # stack trace though.
            errors.send_stack(e)