Beispiel #1
0
    def get_patch(self, issue, patchset):
        """Returns a PatchSet object containing the details to apply this patch."""
        props = self.get_patchset_properties(issue, patchset) or {}
        out = []
        for filename, state in props.get('files', {}).iteritems():
            logging.debug('%s' % filename)
            # If not status, just assume it's a 'M'. Rietveld often gets it wrong and
            # just has status: null. Oh well.
            status = state.get('status') or 'M'
            if status[0] not in ('A', 'D', 'M', 'R'):
                raise patch.UnsupportedPatchFormat(
                    filename,
                    'Change with status \'%s\' is not supported.' % status)

            svn_props = self.parse_svn_properties(
                state.get('property_changes', ''), filename)

            if state.get('is_binary'):
                if status[0] == 'D':
                    if status[0] != status.strip():
                        raise patch.UnsupportedPatchFormat(
                            filename,
                            'Deleted file shouldn\'t have property change.')
                    out.append(
                        patch.FilePatchDelete(filename, state['is_binary']))
                else:
                    content = self.get_file_content(issue, patchset,
                                                    state['id'])
                    if not content or content == 'None':
                        # As a precaution due to a bug in upload.py for git checkout, refuse
                        # empty files. If it's empty, it's not a binary file.
                        raise patch.UnsupportedPatchFormat(
                            filename,
                            'Binary file is empty. Maybe the file wasn\'t uploaded in the '
                            'first place?')
                    out.append(
                        patch.FilePatchBinary(filename,
                                              content,
                                              svn_props,
                                              is_new=(status[0] == 'A')))
                continue

            try:
                diff = self.get_file_diff(issue, patchset, state['id'])
            except urllib2.HTTPError, e:
                if e.code == 404:
                    raise patch.UnsupportedPatchFormat(
                        filename, 'File doesn\'t have a diff.')
                raise

            # FilePatchDiff() will detect file deletion automatically.
            p = patch.FilePatchDiff(filename, diff, svn_props)
            out.append(p)
            if status[0] == 'A':
                # It won't be set for empty file.
                p.is_new = True
            if (len(status) > 1 and status[1] == '+'
                    and not (p.source_filename or p.svn_properties)):
                raise patch.UnsupportedPatchFormat(
                    filename, 'Failed to process the svn properties')
Beispiel #2
0
    def get_patch(self, issue, patchset):
        """Returns a PatchSet object containing the details to apply this patch."""
        props = self.get_patchset_properties(issue, patchset) or {}
        out = []
        for filename, state in props.get('files', {}).iteritems():
            logging.debug('%s' % filename)
            status = state.get('status')
            if not status:
                raise patch.UnsupportedPatchFormat(
                    filename,
                    'File\'s status is None, patchset upload is incomplete.')
            if status[0] not in ('A', 'D', 'M'):
                raise patch.UnsupportedPatchFormat(
                    filename,
                    'Change with status \'%s\' is not supported.' % status)

            svn_props = self.parse_svn_properties(
                state.get('property_changes', ''), filename)

            if state.get('is_binary'):
                if status[0] == 'D':
                    if status[0] != status.strip():
                        raise patch.UnsupportedPatchFormat(
                            filename,
                            'Deleted file shouldn\'t have property change.')
                    out.append(
                        patch.FilePatchDelete(filename, state['is_binary']))
                else:
                    out.append(
                        patch.FilePatchBinary(filename,
                                              self.get_file_content(
                                                  issue, patchset,
                                                  state['id']),
                                              svn_props,
                                              is_new=(status[0] == 'A')))
                continue

            try:
                diff = self.get_file_diff(issue, patchset, state['id'])
            except urllib2.HTTPError, e:
                if e.code == 404:
                    raise patch.UnsupportedPatchFormat(
                        filename, 'File doesn\'t have a diff.')
                raise

            # FilePatchDiff() will detect file deletion automatically.
            p = patch.FilePatchDiff(filename, diff, svn_props)
            out.append(p)
            if status[0] == 'A':
                # It won't be set for empty file.
                p.is_new = True
            if (len(status) > 1 and status[1] == '+'
                    and not (p.source_filename or p.svn_properties)):
                raise patch.UnsupportedPatchFormat(
                    filename, 'Failed to process the svn properties')
Beispiel #3
0
    def parse_svn_properties(rietveld_svn_props, filename):
        """Returns a list of tuple [('property', 'newvalue')].

    rietveld_svn_props is the exact format from 'svn diff'.
    """
        rietveld_svn_props = rietveld_svn_props.splitlines()
        svn_props = []
        if not rietveld_svn_props:
            return svn_props
        # 1. Ignore svn:mergeinfo.
        # 2. Accept svn:eol-style and svn:executable.
        # 3. Refuse any other.
        # \n
        # Added: svn:ignore\n
        #    + LF\n

        spacer = rietveld_svn_props.pop(0)
        if spacer or not rietveld_svn_props:
            # svn diff always put a spacer between the unified diff and property
            # diff
            raise patch.UnsupportedPatchFormat(
                filename, 'Failed to parse svn properties.')

        while rietveld_svn_props:
            # Something like 'Added: svn:eol-style'. Note the action is localized.
            # *sigh*.
            action = rietveld_svn_props.pop(0)
            match = re.match(r'^(\w+): (.+)$', action)
            if not match or not rietveld_svn_props:
                raise patch.UnsupportedPatchFormat(
                    filename, 'Failed to parse svn properties: %s, %s' %
                    (action, svn_props))

            if match.group(2) == 'svn:mergeinfo':
                # Silently ignore the content.
                rietveld_svn_props.pop(0)
                continue

            if match.group(1) not in ('Added', 'Modified'):
                # Will fail for our French friends.
                raise patch.UnsupportedPatchFormat(
                    filename, 'Unsupported svn property operation.')

            if match.group(2) in ('svn:eol-style', 'svn:executable',
                                  'svn:mime-type'):
                # '   + foo' where foo is the new value. That's fragile.
                content = rietveld_svn_props.pop(0)
                match2 = re.match(r'^   \+ (.*)$', content)
                if not match2:
                    raise patch.UnsupportedPatchFormat(
                        filename, 'Unsupported svn property format.')
                svn_props.append((match.group(2), match2.group(1)))
        return svn_props
Beispiel #4
0
  def apply_patch(self, patches, post_processors=None):
    """Applies a patch on 'working_branch' and switch to it.

    Also commits the changes on the local branch.

    Ignores svn properties and raise an exception on unexpected ones.
    """
    post_processors = post_processors or self.post_processors or []
    # It this throws, the checkout is corrupted. Maybe worth deleting it and
    # trying again?
    if self.remote_branch:
      self._check_call_git(
          ['checkout', '-b', self.working_branch,
            '%s/%s' % (self.remote, self.remote_branch), '--quiet'])
    for index, p in enumerate(patches):
      try:
        stdout = ''
        if p.is_delete:
          if (not os.path.exists(p.filename) and
              any(p1.source_filename == p.filename for p1 in patches[0:index])):
            # The file could already be deleted if a prior patch with file
            # rename was already processed. To be sure, look at all the previous
            # patches to see if they were a file rename.
            pass
          else:
            stdout += self._check_output_git(['rm', p.filename])
        else:
          dirname = os.path.dirname(p.filename)
          full_dir = os.path.join(self.project_path, dirname)
          if dirname and not os.path.isdir(full_dir):
            os.makedirs(full_dir)
          if p.is_binary:
            with open(os.path.join(self.project_path, p.filename), 'wb') as f:
              f.write(p.get())
            stdout += self._check_output_git(['add', p.filename])
          else:
            # No need to do anything special with p.is_new or if not
            # p.diff_hunks. git apply manages all that already.
            stdout += self._check_output_git(
                ['apply', '--index', '-p%s' % p.patchlevel], stdin=p.get(True))
          for prop in p.svn_properties:
            # Ignore some known auto-props flags through .subversion/config,
            # bails out on the other ones.
            # TODO(maruel): Read ~/.subversion/config and detect the rules that
            # applies here to figure out if the property will be correctly
            # handled.
            if not prop[0] in (
                'svn:eol-style', 'svn:executable', 'svn:mime-type'):
              raise patch.UnsupportedPatchFormat(
                  p.filename,
                  'Cannot apply svn property %s to file %s.' % (
                        prop[0], p.filename))
        for post in post_processors:
          post(self, p)
      except OSError, e:
        raise PatchApplicationFailed(p, '%s%s' % (stdout, e))
      except subprocess.CalledProcessError, e:
        raise PatchApplicationFailed(
            p, '%s%s' % (stdout, getattr(e, 'stdout', None)))
Beispiel #5
0
    def apply_patch(self,
                    patches,
                    post_processors=None,
                    verbose=False,
                    name=None,
                    email=None):
        """Applies a patch on 'working_branch' and switches to it.

    Also commits the changes on the local branch.

    Ignores svn properties and raise an exception on unexpected ones.
    """
        post_processors = post_processors or self.post_processors or []
        # It this throws, the checkout is corrupted. Maybe worth deleting it and
        # trying again?
        if self.remote_branch:
            self._check_call_git([
                'checkout', '-b', self.working_branch, '-t',
                self.remote_branch, '--quiet'
            ])

        for index, p in enumerate(patches):
            stdout = []
            try:
                filepath = os.path.join(self.project_path, p.filename)
                if p.is_delete:
                    if (not os.path.exists(filepath)
                            and any(p1.source_filename == p.filename
                                    for p1 in patches[0:index])):
                        # The file was already deleted if a prior patch with file rename
                        # was already processed because 'git apply' did it for us.
                        pass
                    else:
                        stdout.append(
                            self._check_output_git(['rm', p.filename]))
                        stdout.append('Deleted.')
                else:
                    dirname = os.path.dirname(p.filename)
                    full_dir = os.path.join(self.project_path, dirname)
                    if dirname and not os.path.isdir(full_dir):
                        os.makedirs(full_dir)
                        stdout.append('Created missing directory %s.' %
                                      dirname)
                    if p.is_binary:
                        content = p.get()
                        with open(filepath, 'wb') as f:
                            f.write(content)
                        stdout.append('Added binary file %d bytes' %
                                      len(content))
                        cmd = ['add', p.filename]
                        if verbose:
                            cmd.append('--verbose')
                        stdout.append(self._check_output_git(cmd))
                    else:
                        # No need to do anything special with p.is_new or if not
                        # p.diff_hunks. git apply manages all that already.
                        cmd = ['apply', '--index', '-p%s' % p.patchlevel]
                        if verbose:
                            cmd.append('--verbose')
                        stdout.append(
                            self._check_output_git(cmd, stdin=p.get(True)))
                    for name, value in p.svn_properties:
                        # Ignore some known auto-props flags through .subversion/config,
                        # bails out on the other ones.
                        # TODO(maruel): Read ~/.subversion/config and detect the rules that
                        # applies here to figure out if the property will be correctly
                        # handled.
                        stdout.append('Property %s=%s' % (name, value))
                        if not name in ('svn:eol-style', 'svn:executable',
                                        'svn:mime-type'):
                            raise patch.UnsupportedPatchFormat(
                                p.filename,
                                'Cannot apply svn property %s to file %s.' %
                                (name, p.filename))
                for post in post_processors:
                    post(self, p)
                if verbose:
                    print p.filename
                    print align_stdout(stdout)
            except OSError, e:
                raise PatchApplicationFailed(
                    p, '%s%s' % (align_stdout(stdout), e))
            except subprocess.CalledProcessError, e:
                raise PatchApplicationFailed(
                    p, 'While running %s;\n%s%s' %
                    (' '.join(e.cmd), align_stdout(stdout),
                     align_stdout([getattr(e, 'stdout', '')])))