Example #1
0
    def _diff_directories(self, old_dir, new_dir):
        """Return uniffied diff between two directories content.

        Function save two version's content of directory to temp
        files and treate them as casual diff between two files.
        """
        old_content = self._directory_content(old_dir)
        new_content = self._directory_content(new_dir)

        old_tmp = make_tempfile(content=old_content)
        new_tmp = make_tempfile(content=new_content)

        diff_cmd = ['diff', '-uN', old_tmp, new_tmp]
        dl = execute(diff_cmd,
                     extra_ignore_errors=(1, 2),
                     results_unicode=False,
                     split_lines=True)

        # Replace temporary filenames with real directory names and add ids
        if dl:
            dl[0] = dl[0].replace(old_tmp.encode('utf-8'),
                                  old_dir.encode('utf-8'))
            dl[1] = dl[1].replace(new_tmp.encode('utf-8'),
                                  new_dir.encode('utf-8'))
            old_oid = execute(
                ['cleartool', 'describe', '-fmt', '%On', old_dir],
                results_unicode=False)
            new_oid = execute(
                ['cleartool', 'describe', '-fmt', '%On', new_dir],
                results_unicode=False)
            dl.insert(2, b'==== %s %s ====\n' % (old_oid, new_oid))

        return dl
Example #2
0
    def diff_directories(self, old_dir, new_dir):
        """Return uniffied diff between two directories content.

        Function save two version's content of directory to temp
        files and treate them as casual diff between two files.
        """
        old_content = self._directory_content(old_dir)
        new_content = self._directory_content(new_dir)

        old_tmp = make_tempfile(content=old_content)
        new_tmp = make_tempfile(content=new_content)

        diff_cmd = ["diff", "-uN", old_tmp, new_tmp]
        dl = execute(diff_cmd,
                     extra_ignore_errors=(1, 2),
                     translate_newlines=False,
                     split_lines=True)

        # Replacing temporary filenames to
        # real directory names and add ids
        if dl:
            dl[0] = dl[0].replace(old_tmp, old_dir)
            dl[1] = dl[1].replace(new_tmp, new_dir)
            old_oid = execute(["cleartool", "describe", "-fmt", "%On",
                               old_dir])
            new_oid = execute(["cleartool", "describe", "-fmt", "%On",
                               new_dir])
            dl.insert(2, "==== %s %s ====\n" % (old_oid, new_oid))

        return dl
Example #3
0
    def _extract_delete_files(self, depot_file, revision):
        """Extract the 'old' and 'new' files for a delete operation.

        Returns a tuple of (old filename, new filename). This can raise a
        ValueError if extraction fails.
        """
        # Get the old version out of perforce
        old_filename = make_tempfile()
        self._write_file('%s#%s' % (depot_file, revision), old_filename)

        # Make an empty tempfile for the new file
        new_filename = make_tempfile()

        return old_filename, new_filename
Example #4
0
    def _extract_delete_files(self, depot_file, revision, cl_is_shelved):
        """Extract the 'old' and 'new' files for a delete operation.

        Returns a tuple of (old filename, new filename). This can raise a
        ValueError if extraction fails.
        """
        # Get the old version out of perforce
        old_filename = make_tempfile()
        self._write_file('%s#%s' % (depot_file, revision), old_filename)

        # Make an empty tempfile for the new file
        new_filename = make_tempfile()

        return old_filename, new_filename
Example #5
0
    def test_diff_with_pending_changelist(self):
        """Testing PerforceClient.diff with a pending changelist"""
        client = self._build_client()
        client.p4.repo_files = [
            {
                'depotFile': '//mydepot/test/README',
                'rev': '2',
                'action': 'edit',
                'change': '12345',
                'text': 'This is a test.\n',
            },
            {
                'depotFile': '//mydepot/test/README',
                'rev': '3',
                'action': 'edit',
                'change': '',
                'text': 'This is a mess.\n',
            },
            {
                'depotFile': '//mydepot/test/COPYING',
                'rev': '1',
                'action': 'add',
                'change': '12345',
                'text': 'Copyright 2013 Joe User.\n',
            },
            {
                'depotFile': '//mydepot/test/Makefile',
                'rev': '3',
                'action': 'delete',
                'change': '12345',
                'text': 'all: all\n',
            },
        ]

        readme_file = make_tempfile()
        copying_file = make_tempfile()
        makefile_file = make_tempfile()
        client.p4.print_file('//mydepot/test/README#3', readme_file)
        client.p4.print_file('//mydepot/test/COPYING#1', copying_file)

        client.p4.where_files = {
            '//mydepot/test/README': readme_file,
            '//mydepot/test/COPYING': copying_file,
            '//mydepot/test/Makefile': makefile_file,
        }

        revisions = client.parse_revision_spec(['12345'])
        diff = client.diff(revisions)
        self._compare_diff(diff, '07aa18ff67f9aa615fcda7ecddcb354e')
Example #6
0
    def test_diff_with_pending_changelist(self):
        """Testing PerforceClient.diff with a pending changelist"""
        client = self._build_client()
        client.p4.repo_files = [
            {
                'depotFile': '//mydepot/test/README',
                'rev': '2',
                'action': 'edit',
                'change': '12345',
                'text': 'This is a test.\n',
            },
            {
                'depotFile': '//mydepot/test/README',
                'rev': '3',
                'action': 'edit',
                'change': '',
                'text': 'This is a mess.\n',
            },
            {
                'depotFile': '//mydepot/test/COPYING',
                'rev': '1',
                'action': 'add',
                'change': '12345',
                'text': 'Copyright 2013 Joe User.\n',
            },
            {
                'depotFile': '//mydepot/test/Makefile',
                'rev': '3',
                'action': 'delete',
                'change': '12345',
                'text': 'all: all\n',
            },
        ]

        readme_file = make_tempfile()
        copying_file = make_tempfile()
        makefile_file = make_tempfile()
        client.p4.print_file('//mydepot/test/README#3', readme_file)
        client.p4.print_file('//mydepot/test/COPYING#1', copying_file)

        client.p4.where_files = {
            '//mydepot/test/README': readme_file,
            '//mydepot/test/COPYING': copying_file,
            '//mydepot/test/Makefile': makefile_file,
        }

        revisions = client.parse_revision_spec(['12345'])
        diff = client.diff(revisions)
        self._compare_diff(diff, '07aa18ff67f9aa615fcda7ecddcb354e')
Example #7
0
def edit_text(content='', filename=None):
    """Run a user-configured editor to prompt for text.

    This will run a configured text editor (trying the :envvar:`VISUAL` or
    :envvar:`EDITOR` environment variables, falling back on :program:`vi`)
    to request text for use in a commit message or some other purpose.

    Args:
        content (unicode, optional):
            Existing content to edit.

        filename (unicode, optional):
            The optional name of the temp file to edit. This can be used to
            help the editor provide a proper editing environment for the
            file.

    Returns:
        unicode:
        The resulting content.

    Raises:
        rbcommons.utils.errors.EditorError:
            The configured editor could not be run, or it failed with an
            error.
    """
    tempfile = make_tempfile(content.encode('utf8'), filename=filename)
    result = edit_file(tempfile)
    os.unlink(tempfile)

    return result
Example #8
0
    def test_make_tempfile(self):
        """Testing 'make_tempfile' method."""
        fname = filesystem.make_tempfile()

        self.assertTrue(os.path.isfile(fname))
        self.assertEqual(os.stat(fname).st_uid, os.geteuid())
        self.assertTrue(os.access(fname, os.R_OK | os.W_OK))
Example #9
0
    def test_make_tempfile(self):
        """Testing 'make_tempfile' method."""
        fname = filesystem.make_tempfile()

        self.assertTrue(os.path.isfile(fname))
        self.assertEqual(os.stat(fname).st_uid, os.geteuid())
        self.assertTrue(os.access(fname, os.R_OK | os.W_OK))
Example #10
0
    def _extract_add_files(self, depot_file, revision, cl_is_shelved):
        """Extract the 'old' and 'new' files for an add operation.

        Returns a tuple of (old filename, new filename). This can raise a
        ValueError if the extraction fails.
        """
        # Make an empty tempfile for the old file
        old_filename = make_tempfile()

        if cl_is_shelved:
            new_filename = make_tempfile()
            self._write_file('%s@=%s' % (depot_file, revision), new_filename)
        else:
            # Just reference the file within the client view
            new_filename = self._depot_to_local(depot_file)

        return old_filename, new_filename
Example #11
0
    def test_make_tempfile_with_prefix(self):
        """Testing make_tempfile with prefix"""
        filename = make_tempfile(prefix='supertest-')

        self.assertIn(filename, filesystem.tempfiles)
        self.assertTrue(os.path.isfile(filename))
        self.assertTrue(os.path.basename(filename).startswith('supertest-'))
        self.assertEqual(os.stat(filename).st_uid, os.geteuid())
        self.assertTrue(os.access(filename, os.R_OK | os.W_OK))
Example #12
0
    def test_make_tempfile(self):
        """Testing make_tempfile"""
        filename = make_tempfile()
        self.assertIn(filename, filesystem.tempfiles)

        self.assertTrue(os.path.isfile(filename))
        self.assertTrue(os.path.basename(filename).startswith('rbtools.'))
        self.assertEqual(os.stat(filename).st_uid, os.geteuid())
        self.assertTrue(os.access(filename, os.R_OK | os.W_OK))
Example #13
0
    def _diff_directories(self, old_dir, new_dir):
        """Return a unified diff between two directories' content.

        This function saves two version's content of directory to temp
        files and treats them as casual diff between two files.

        Args:
            old_dir (unicode):
                The path to a directory within a vob.

            new_dir (unicode):
                The path to a directory within a vob.

        Returns:
            list:
            The diff between the two directory trees, split into lines.
        """
        old_content = self._directory_content(old_dir)
        new_content = self._directory_content(new_dir)

        old_tmp = make_tempfile(content=old_content)
        new_tmp = make_tempfile(content=new_content)

        diff_cmd = ['diff', '-uN', old_tmp, new_tmp]
        dl = execute(diff_cmd,
                     extra_ignore_errors=(1, 2),
                     results_unicode=False,
                     split_lines=True)

        # Replace temporary filenames with real directory names and add ids
        if dl:
            dl[0] = dl[0].replace(old_tmp.encode('utf-8'),
                                  old_dir.encode('utf-8'))
            dl[1] = dl[1].replace(new_tmp.encode('utf-8'),
                                  new_dir.encode('utf-8'))
            old_oid = execute(['cleartool', 'describe', '-fmt', '%On',
                               old_dir],
                              results_unicode=False)
            new_oid = execute(['cleartool', 'describe', '-fmt', '%On',
                               new_dir],
                              results_unicode=False)
            dl.insert(2, b'==== %s %s ====\n' % (old_oid, new_oid))

        return dl
Example #14
0
    def _extract_edit_files(self, depot_file, tip, base_revision,
                            cl_is_shelved):
        """Extract the 'old' and 'new' files for an edit operation.

        Returns a tuple of (old filename, new filename). This can raise a
        ValueError if the extraction fails.
        """
        # Get the old version out of perforce
        old_filename = make_tempfile()
        self._write_file('%s#%s' % (depot_file, base_revision), old_filename)

        if cl_is_shelved:
            new_filename = make_tempfile()
            self._write_file('%s@=%s' % (depot_file, tip), new_filename)
        else:
            # Just reference the file within the client view
            new_filename = self._depot_to_local(depot_file)

        return old_filename, new_filename
Example #15
0
    def main(self, request_id):
        """Run the command."""
        repository_info, tool = self.initialize_scm_tool(
            client_name=self.options.repository_type)
        server_url = self.get_server_url(repository_info, tool)
        api_client, api_root = self.get_api(server_url)
        self.setup_tool(tool, api_root=api_root)

        # Check if repository info on reviewboard server match local ones.
        repository_info = repository_info.find_server_repository_info(api_root)

        # Get the patch, the used patch ID and base dir for the diff
        diff_body, diff_revision, base_dir = self.get_patch(
            request_id,
            api_root,
            self.options.diff_revision)

        if self.options.patch_stdout:
            print diff_body
        else:
            try:
                if tool.has_pending_changes():
                    message = 'Working directory is not clean.'

                    if not self.options.commit:
                        print 'Warning: %s' % message
                    else:
                        raise CommandError(message)
            except NotImplementedError:
                pass

            tmp_patch_file = make_tempfile(diff_body)
            success = self.apply_patch(repository_info, tool, request_id,
                                       diff_revision, tmp_patch_file, base_dir)

            if success and (self.options.commit or
                            self.options.commit_no_edit):
                try:
                    review_request = api_root.get_review_request(
                        review_request_id=request_id,
                        force_text_type='plain')
                except APIError, e:
                    raise CommandError('Error getting review request %s: %s'
                                       % (request_id, e))

                message = self._extract_commit_message(review_request)
                author = review_request.get_submitter()

                try:
                    tool.create_commit(message, author,
                                       not self.options.commit_no_edit)
                    print('Changes committed to current branch.')
                except NotImplementedError:
                    raise CommandError('--commit is not supported with %s'
                                       % tool.name)
Example #16
0
    def test_edit_file_with_invalid_editor(self):
        """Testing edit_file with invalid filename"""
        message = (
            'The editor "./bad-rbtools-editor" was not found or could not '
            'be run. Make sure the EDITOR environment variable is set '
            'to your preferred editor.')

        os.environ[str('RBTOOLS_EDITOR')] = './bad-rbtools-editor'

        with self.assertRaisesMessage(EditorError, message):
            edit_file(make_tempfile(b'Test content'))
Example #17
0
    def test_edit_file_with_file_deleted(self):
        """Testing edit_file with file deleted during edit"""
        def _subprocess_call(*args, **kwargs):
            os.unlink(filename)

        filename = make_tempfile(b'Test content')
        message = 'The edited file "%s" was deleted during edit.' % filename

        self.spy_on(subprocess.call, call_fake=_subprocess_call)

        with self.assertRaisesMessage(EditorError, message):
            edit_file(filename)
Example #18
0
    def _extract_add_files(self, depot_file, local_file, revision,
                           cl_is_shelved, cl_is_pending):
        """Extract the 'old' and 'new' files for an add operation.

        Returns a tuple of (old filename, new filename). This can raise a
        ValueError if the extraction fails.
        """
        # Make an empty tempfile for the old file
        old_filename = make_tempfile()

        if cl_is_shelved:
            new_filename = make_tempfile()
            self._write_file('%s@=%s' % (depot_file, revision), new_filename)
        elif cl_is_pending:
            # Just reference the file within the client view
            new_filename = local_file
        else:
            new_filename = make_tempfile()
            self._write_file('%s#%s' % (depot_file, revision), new_filename)

        return old_filename, new_filename
Example #19
0
    def main(self, request_id):
        """Run the command."""
        repository_info, tool = self.initialize_scm_tool(
            client_name=self.options.repository_type)
        server_url = self.get_server_url(repository_info, tool)
        api_client, api_root = self.get_api(server_url)
        self.setup_tool(tool, api_root=api_root)

        # Check if repository info on reviewboard server match local ones.
        repository_info = repository_info.find_server_repository_info(api_root)

        # Get the patch, the used patch ID and base dir for the diff
        diff_body, diff_revision, base_dir = self.get_patch(
            request_id, api_root, self.options.diff_revision)

        if self.options.patch_stdout:
            print(diff_body)
        else:
            try:
                if tool.has_pending_changes():
                    message = 'Working directory is not clean.'

                    if not self.options.commit:
                        print('Warning: %s' % message)
                    else:
                        raise CommandError(message)
            except NotImplementedError:
                pass

            tmp_patch_file = make_tempfile(diff_body)
            success = self.apply_patch(repository_info, tool, request_id,
                                       diff_revision, tmp_patch_file, base_dir)

            if success and (self.options.commit
                            or self.options.commit_no_edit):
                try:
                    review_request = api_root.get_review_request(
                        review_request_id=request_id, force_text_type='plain')
                except APIError as e:
                    raise CommandError('Error getting review request %s: %s' %
                                       (request_id, e))

                message = extract_commit_message(review_request)
                author = review_request.get_submitter()

                try:
                    tool.create_commit(message, author,
                                       not self.options.commit_no_edit)
                    print('Changes committed to current branch.')
                except NotImplementedError:
                    raise CommandError('--commit is not supported with %s' %
                                       tool.name)
Example #20
0
    def _extract_edit_files(self, depot_file, local_file, rev_a, rev_b,
                            cl_is_shelved, cl_is_submitted):
        """Extract the 'old' and 'new' files for an edit operation.

        Returns a tuple of (old filename, new filename). This can raise a
        ValueError if the extraction fails.
        """
        # Get the old version out of perforce
        old_filename = make_tempfile()
        self._write_file('%s#%s' % (depot_file, rev_a), old_filename)

        if cl_is_shelved:
            new_filename = make_tempfile()
            self._write_file('%s@=%s' % (depot_file, rev_b), new_filename)
        elif cl_is_submitted:
            new_filename = make_tempfile()
            self._write_file('%s#%s' % (depot_file, rev_b), new_filename)
        else:
            # Just reference the file within the client view
            new_filename = local_file

        return old_filename, new_filename
Example #21
0
def edit_text(content):
    """Allows a user to edit a block of text and returns the saved result.

    The environment's default text editor is used if available, otherwise
    vim is used.
    """
    tempfile = make_tempfile(content.encode('utf8'))
    editor = os.environ.get('EDITOR', 'vim')
    subprocess.call([editor, tempfile])
    f = open(tempfile)
    result = f.read()
    f.close()

    return result.decode('utf8')
Example #22
0
def edit_text(content):
    """Allows a user to edit a block of text and returns the saved result.

    The environment's default text editor is used if available, otherwise
    vim is used.
    """
    tempfile = make_tempfile(content.encode('utf8'))
    editor = os.environ.get('EDITOR', 'vim')
    subprocess.call([editor, tempfile])
    f = open(tempfile)
    result = f.read()
    f.close()

    return result.decode('utf8')
Example #23
0
    def test_make_tempfile_with_filename(self):
        """Testing make_tempfile with filename"""
        filename = make_tempfile(filename='TEST123')

        self.assertIn(filename, filesystem.tempfiles)
        self.assertEqual(os.path.basename(filename), 'TEST123')
        self.assertTrue(os.path.isfile(filename))
        self.assertTrue(os.access(filename, os.R_OK | os.W_OK))
        self.assertEqual(os.stat(filename).st_uid, os.geteuid())

        parent_dir = os.path.dirname(filename)
        self.assertIn(parent_dir, filesystem.tempdirs)
        self.assertTrue(os.access(parent_dir, os.R_OK | os.W_OK | os.X_OK))
        self.assertEqual(os.stat(parent_dir).st_uid, os.geteuid())
Example #24
0
    def _patch(self, content, patch):
        """Patch content with a patch. Returnes patched content.

        The content and the patch should be a list of lines with no
        endl."""

        content_file = make_tempfile(content=os.linesep.join(content))
        patch_file = make_tempfile(content=os.linesep.join(patch))
        reject_file = make_tempfile()
        output_file = make_tempfile()

        patch_cmd = ["patch", "-r", reject_file, "-o", output_file,
                     "-i", patch_file, content_file]

        output = execute(patch_cmd, extra_ignore_errors=(1,),
                         translate_newlines=False)

        if "FAILED at" in output:
            logging.debug("patching content FAILED:")
            logging.debug(output)

        patched = open(output_file).read()
        eof_endl = patched.endswith('\n')

        patched = patched.splitlines()
        if eof_endl:
            patched.append('')

        try:
            os.unlink(content_file)
            os.unlink(patch_file)
            os.unlink(reject_file)
            os.unlink(output_file)
        except:
            pass

        return patched
Example #25
0
    def main(self, request_id):
        """Run the command."""
        self.repository_info, self.tool = self.initialize_scm_tool()
        server_url = self.get_server_url(self.repository_info, self.tool)
        self.root_resource = self.get_root(server_url)

        # Get the patch, the used patch ID and base dir for the diff
        diff_body, diff_revision, base_dir = self.get_patch(
            request_id, self.options.diff_revision)

        tmp_patch_file = make_tempfile(diff_body)

        self.apply_patch(request_id, diff_revision, tmp_patch_file, base_dir)

        os.remove(tmp_patch_file)
Example #26
0
    def main(self, request_id):
        """Run the command."""
        repository_info, tool = self.initialize_scm_tool()
        server_url = self.get_server_url(self.repository_info, self.tool)
        api_client, api_root = self.get_api(server_url)

        # Get the patch, the used patch ID and base dir for the diff
        diff_body, diff_revision, base_dir = self.get_patch(
            request_id,
            self.options.diff_revision)

        tmp_patch_file = make_tempfile(diff_body)

        self.apply_patch(tool, request_id, diff_revision, tmp_patch_file,
                         base_dir)
Example #27
0
    def _content_diff(self, old_content, new_content, old_file,
                      new_file, unified=True):
        """Returns unified diff as a list of lines with no end lines,
        uses temp files. The input content should be a list of lines
        without end lines."""

        old_tmp = make_tempfile(content=os.linesep.join(old_content))
        new_tmp = make_tempfile(content=os.linesep.join(new_content))

        diff_cmd = ['diff']
        if unified:
            diff_cmd.append('-uN')
        diff_cmd.extend((old_tmp, new_tmp))

        dl = execute(diff_cmd, extra_ignore_errors=(1, 2),
                     translate_newlines=False, split_lines=False)

        eof_endl = dl.endswith('\n')
        dl = dl.splitlines()
        if eof_endl:
            dl.append('')

        try:
            os.unlink(old_tmp)
            os.unlink(new_tmp)
        except:
            pass

        if unified and dl and len(dl) > 1:
            # Because the modification time is for temporary files here
            # replacing it with headers without modification time.
            if dl[0].startswith('---') and dl[1].startswith('+++'):
                dl[0] = '--- %s\t' % old_file
                dl[1] = '+++ %s\t' % new_file

        return dl
Example #28
0
    def main(self, request_id):
        """Run the command."""
        repository_info, tool = self.initialize_scm_tool()
        server_url = self.get_server_url(repository_info, tool)
        api_client, api_root = self.get_api(server_url)

        # Get the patch, the used patch ID and base dir for the diff
        diff_body, diff_revision, base_dir = self.get_patch(
            request_id,
            api_root,
            self.options.diff_revision)

        tmp_patch_file = make_tempfile(diff_body)

        self.apply_patch(repository_info, tool, request_id, diff_revision,
                         tmp_patch_file, base_dir)
Example #29
0
    def _exclude_files_not_in_tree(self, patch_file, base_path):
        """Process a diff and remove entries not in the current directory.

        The file at the location patch_file will be overwritten by the new
        patch.

        This function returns a tuple of two booleans. The first boolean
        indicates if any files have been excluded. The second boolean indicates
        if the resulting diff patch file is empty.
        """
        excluded_files = False
        empty_patch = True

        # If our base path does not have a trailing slash (which it won't
        # unless we are at a checkout root), we append a slash so that we can
        # determine if files are under the base_path. We do this so that files
        # like /trunkish (which begins with /trunk) do not mistakenly get
        # placed in /trunk if that is the base_path.
        if not base_path.endswith('/'):
            base_path += '/'

        filtered_patch_name = make_tempfile()

        with open(filtered_patch_name, 'w') as filtered_patch:
            with open(patch_file, 'r') as original_patch:
                include_file = True

                for line in original_patch.readlines():
                    m = self.INDEX_FILE_RE.match(line)

                    if m:
                        filename = m.group(1).decode('utf-8')

                        include_file = filename.startswith(base_path)

                        if not include_file:
                            excluded_files = True
                        else:
                            empty_patch = False

                    if include_file:
                        filtered_patch.write(line)

        os.rename(filtered_patch_name, patch_file)

        return (excluded_files, empty_patch)
Example #30
0
File: svn.py Project: beol/rbtools
    def _exclude_files_not_in_tree(self, patch_file, base_path):
        """Process a diff and remove entries not in the current directory.

        The file at the location patch_file will be overwritten by the new
        patch.

        This function returns a tuple of two booleans. The first boolean
        indicates if any files have been excluded. The second boolean indicates
        if the resulting diff patch file is empty.
        """
        excluded_files = False
        empty_patch = True

        # If our base path does not have a trailing slash (which it won't
        # unless we are at a checkout root), we append a slash so that we can
        # determine if files are under the base_path. We do this so that files
        # like /trunkish (which begins with /trunk) do not mistakenly get
        # placed in /trunk if that is the base_path.
        if not base_path.endswith('/'):
            base_path += '/'

        filtered_patch_name = make_tempfile()

        with open(filtered_patch_name, 'w') as filtered_patch:
            with open(patch_file, 'r') as original_patch:
                include_file = True

                for line in original_patch.readlines():
                    m = self.INDEX_FILE_RE.match(line)

                    if m:
                        filename = m.group(1).decode('utf-8')

                        include_file = filename.startswith(base_path)

                        if not include_file:
                            excluded_files = True
                        else:
                            empty_patch = False

                    if include_file:
                        filtered_patch.write(line)

        os.rename(filtered_patch_name, patch_file)

        return (excluded_files, empty_patch)
Example #31
0
    def _extract_move_files(self, old_depot_file, tip, base_revision,
                            cl_is_shelved):
        """Extract the 'old' and 'new' files for a move operation.

        Returns a tuple of (old filename, new filename, new depot path). This
        can raise a ValueError if extraction fails.
        """
        # XXX: fstat *ought* to work, but perforce doesn't supply the movedFile
        # field in fstat (or apparently anywhere else) when a change is
        # shelved. For now, _diff_pending will avoid calling this method at all
        # for shelved changes, and instead treat them as deletes and adds.
        assert not cl_is_shelved

        # if cl_is_shelved:
        #     fstat_path = '%s@=%s' % (depot_file, tip)
        # else:
        fstat_path = old_depot_file

        stat_info = self.p4.fstat(fstat_path,
                                  ['clientFile', 'movedFile'])
        if 'clientFile' not in stat_info or 'movedFile' not in stat_info:
            raise ValueError('Unable to get moved file information')

        old_filename = make_tempfile()
        self._write_file('%s#%s' % (old_depot_file, base_revision),
                         old_filename)

        # if cl_is_shelved:
        #     fstat_path = '%s@=%s' % (stat_info['movedFile'], tip)
        # else:
        fstat_path = stat_info['movedFile']

        stat_info = self.p4.fstat(fstat_path,
                                  ['clientFile', 'depotFile'])
        if 'clientFile' not in stat_info or 'depotFile' not in stat_info:
            raise ValueError('Unable to get moved file information')

        # Grab the new depot path (to include in the diff index)
        new_depot_file = stat_info['depotFile']

        # Reference the new file directly in the client view
        new_filename = stat_info['clientFile']

        return old_filename, new_filename, new_depot_file
Example #32
0
    def _extract_move_files(self, old_depot_file, tip, base_revision,
                            cl_is_shelved):
        """Extract the 'old' and 'new' files for a move operation.

        Returns a tuple of (old filename, new filename, new depot path). This
        can raise a ValueError if extraction fails.
        """
        # XXX: fstat *ought* to work, but perforce doesn't supply the movedFile
        # field in fstat (or apparently anywhere else) when a change is
        # shelved. For now, _diff_pending will avoid calling this method at all
        # for shelved changes, and instead treat them as deletes and adds.
        assert not cl_is_shelved

        # if cl_is_shelved:
        #     fstat_path = '%s@=%s' % (depot_file, tip)
        # else:
        fstat_path = old_depot_file

        stat_info = self.p4.fstat(fstat_path,
                                  ['clientFile', 'movedFile'])
        if 'clientFile' not in stat_info or 'movedFile' not in stat_info:
            raise ValueError('Unable to get moved file information')

        old_filename = make_tempfile()
        self._write_file('%s#%s' % (old_depot_file, base_revision),
                         old_filename)

        # if cl_is_shelved:
        #     fstat_path = '%s@=%s' % (stat_info['movedFile'], tip)
        # else:
        fstat_path = stat_info['movedFile']

        stat_info = self.p4.fstat(fstat_path,
                                  ['clientFile', 'depotFile'])
        if 'clientFile' not in stat_info or 'depotFile' not in stat_info:
            raise ValueError('Unable to get moved file information')

        # Grab the new depot path (to include in the diff index)
        new_depot_file = stat_info['depotFile']

        # Reference the new file directly in the client view
        new_filename = stat_info['clientFile']

        return old_filename, new_filename, new_depot_file
Example #33
0
def edit_text(content):
    """Allows a user to edit a block of text and returns the saved result.

    The environment's default text editor is used if available, otherwise
    vi is used.
    """
    tempfile = make_tempfile(content.encode('utf8'))
    editor = os.environ.get('VISUAL') or os.environ.get('EDITOR') or 'vi'
    try:
        subprocess.call([editor, tempfile])
    except OSError:
        print 'No editor found. Set EDITOR environment variable or install vi.'
        raise

    f = open(tempfile)
    result = f.read()
    f.close()

    return result.decode('utf8')
Example #34
0
    def main(self, pull_request_id):
        repository_info, tool = self.initialize_scm_tool()

        configs = [load_config()]

        if self.options.owner is None:
            self.options.owner = get_config_value(configs, "GITHUB_OWNER", None)

        if self.options.repo is None:
            self.options.repo = get_config_value(configs, "GITHUB_REPO", None)

        if self.options.owner is None or self.options.repo is None:
            raise CommandError("No GITHUB_REPO or GITHUB_OWNER has been configured.")

        diff = self.get_patch(pull_request_id)

        if self.options.patch_stdout:
            print diff
        else:
            try:
                if tool.has_pending_changes():
                    message = "Working directory is not clean."

                    if not self.options.commit:
                        print "Warning: %s" % message
                    else:
                        raise CommandError(message)
            except NotImplementedError:
                pass

            tmp_patch_file = make_tempfile(diff)
            self.apply_patch(repository_info, tool, pull_request_id, tmp_patch_file)

            if self.options.commit:
                message = self.generate_commit_message(pull_request_id)
                author = self.get_author_from_patch(tmp_patch_file)

                try:
                    tool.create_commit(message, author)
                    print ("Changes committed to current branch.")
                except NotImplementedError:
                    raise CommandError("--commit is not supported with %s" % tool.name)
Example #35
0
    def test_edit_file_with_editor_priority(self):
        """Testing edit_file editor priority"""
        self.spy_on(subprocess.call, call_original=False)

        # Save these so we can restore after the tests. We don't need to
        # save RBTOOLS_EDITOR, because this is taken care of in the base
        # TestCase class.
        old_visual = os.environ.get(str('VISUAL'))
        old_editor = os.environ.get(str('EDITOR'))

        filename = make_tempfile(b'Test content')

        try:
            os.environ[str('RBTOOLS_EDITOR')] = 'rbtools-editor'
            os.environ[str('VISUAL')] = 'visual'
            os.environ[str('EDITOR')] = 'editor'

            edit_file(filename)
            self.assertTrue(
                subprocess.call.last_called_with(['rbtools-editor', filename]))

            os.environ[str('RBTOOLS_EDITOR')] = ''
            edit_file(filename)
            self.assertTrue(
                subprocess.call.last_called_with(['visual', filename]))

            os.environ[str('VISUAL')] = ''
            edit_file(filename)
            self.assertTrue(
                subprocess.call.last_called_with(['editor', filename]))

            os.environ[str('EDITOR')] = ''
            edit_file(filename)
            self.assertTrue(subprocess.call.last_called_with(['vi', filename]))
        finally:
            if old_visual:
                os.environ[str('VISUAL')] = old_visual

            if old_editor:
                os.environ[str('EDITOR')] = old_editor
Example #36
0
    def precreate_tempfiles(self, count):
        """Pre-create a specific number of temporary files.

        This will call :py:func:`~rbtools.utils.filesystem.make_tempfile`
        the specified number of times, returning the list of generated temp
        file paths, and will then spy that function to return those temp
        files.

        Once each pre-created temp file is used up, any further calls to
        :py:func:`~rbtools.utils.filesystem.make_tempfile` will result in
        an error, failing the test.

        This is useful in unit tests that need to script a series of
        expected calls using :py:mod:`kgb` (such as through
        :py:class:`kgb.ops.SpyOpMatchInOrder`) that need to know the names
        of temporary filenames up-front.

        Unit test suites that use this must mix in :py:class:`kgb.SpyAgency`.

        Args:
            count (int):
                The number of temporary filenames to pre-create.

        Raises:
            AssertionError:
                The test suite class did not mix in :py:class:`kgb.SpyAgency`.
        """
        assert hasattr(self, 'spy_on'), (
            '%r must mix in kgb.SpyAgency in order to call this method.' %
            self.__class__)

        tmpfiles = [make_tempfile() for i in range(count)]

        self.spy_on(make_tempfile, op=kgb.SpyOpReturnInOrder(tmpfiles))

        return tmpfiles
Example #37
0
    def main(self, review_request_id):
        """Run the command."""
        repository_info, tool = self.initialize_scm_tool(
            client_name=self.options.repository_type)

        if self.options.patch_stdout and self.options.server:
            server_url = self.options.server
        else:
            server_url = self.get_server_url(repository_info, tool)
            if self.options.revert_patch and not tool.supports_patch_revert:
                raise CommandError('The %s backend does not support reverting '
                                   'patches.' % tool.name)

        api_client, api_root = self.get_api(server_url)

        if not self.options.patch_stdout:
            self.setup_tool(tool, api_root=api_root)

            # Check if repository info on reviewboard server match local ones.
            repository_info = repository_info.find_server_repository_info(
                api_root)

        # Get the patch, the used patch ID and base dir for the diff
        patch_data = self.get_patch(tool, api_root, review_request_id,
                                    self.options.diff_revision,
                                    self.options.commit_id)

        diff_body = patch_data['diff']
        diff_revision = patch_data['revision']
        base_dir = patch_data['base_dir']

        if self.options.patch_stdout:
            if isinstance(diff_body, bytes):
                print(diff_body.decode('utf-8'))
            else:
                print(diff_body)
        else:
            try:
                if tool.has_pending_changes():
                    message = 'Working directory is not clean.'

                    if not self.options.commit:
                        print('Warning: %s' % message)
                    else:
                        raise CommandError(message)
            except NotImplementedError:
                pass

            tmp_patch_file = make_tempfile(diff_body)

            success = self.apply_patch(repository_info,
                                       tool,
                                       review_request_id,
                                       diff_revision,
                                       tmp_patch_file,
                                       base_dir,
                                       revert=self.options.revert_patch)

            if not success:
                raise CommandError('Could not apply patch')

            if self.options.commit or self.options.commit_no_edit:
                if patch_data['commit_meta'] is not None:
                    # We are patching a commit so we already have the metadata
                    # required without making additional HTTP requests.
                    meta = patch_data['commit_meta']
                    message = meta['message']

                    # Fun fact: object does not have a __dict__ so you cannot
                    # call setattr() on them. We need this ability so we are
                    # creating a type that does.
                    author = type('Author', (object, ), {})()
                    author.fullname = meta['author_name']
                    author.email = meta['author_email']

                else:
                    try:
                        review_request = api_root.get_review_request(
                            review_request_id=review_request_id,
                            force_text_type='plain')
                    except APIError as e:
                        raise CommandError(
                            'Error getting review request %s: %s' %
                            (request_id, e))

                    message = extract_commit_message(review_request)
                    author = review_request.get_submitter()

                try:
                    tool.create_commit(message, author,
                                       not self.options.commit_no_edit)
                    print('Changes committed to current branch.')
                except NotImplementedError:
                    raise CommandError('--commit is not supported with %s' %
                                       tool.name)
Example #38
0
    def _process_diffs(self, diff_entries):
        """Process the given diff entries.

        Args:
            diff_entries (list):
                The list of diff entries.

        Returns:
            bytes:
            The processed diffs.
        """
        diff_lines = []

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        for f in diff_entries:
            f = f.strip()

            if not f:
                continue

            m = re.search(br'(?P<type>[ACMD]) (?P<file>.*) '
                          br'(?P<revspec>rev:revid:[-\d]+) '
                          br'(?P<parentrevspec>rev:revid:[-\d]+) '
                          br'src:(?P<srcpath>.*) '
                          br'dst:(?P<dstpath>.*)$',
                          f)
            if not m:
                raise SCMError('Could not parse "cm log" response: %s' % f)

            changetype = m.group('type')
            filename = m.group('file')

            if changetype == b'M':
                # Handle moved files as a delete followed by an add.
                # Clunky, but at least it works
                oldfilename = m.group('srcpath')
                oldspec = m.group('revspec')
                newfilename = m.group('dstpath')
                newspec = m.group('revspec')

                self._write_file(oldfilename, oldspec, tmp_diff_from_filename)
                dl = self._diff_files(tmp_diff_from_filename, empty_filename,
                                      oldfilename, 'rev:revid:-1', oldspec,
                                      changetype)
                diff_lines += dl

                self._write_file(newfilename, newspec, tmp_diff_to_filename)
                dl = self._diff_files(empty_filename, tmp_diff_to_filename,
                                      newfilename, newspec, 'rev:revid:-1',
                                      changetype)
                diff_lines += dl

            else:
                newrevspec = m.group('revspec')
                parentrevspec = m.group('parentrevspec')

                logging.debug('Type %s File %s Old %s New %s',
                              changetype, filename, parentrevspec, newrevspec)

                old_file = new_file = empty_filename

                if (changetype in [b'A'] or
                    (changetype in [b'C'] and
                     parentrevspec == b'rev:revid:-1')):
                    # There's only one content to show
                    self._write_file(filename, newrevspec,
                                     tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in [b'C']:
                    self._write_file(filename, parentrevspec,
                                     tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                    self._write_file(filename, newrevspec,
                                     tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in [b'D']:
                    self._write_file(filename, parentrevspec,
                                     tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                else:
                    raise SCMError('Unknown change type "%s" for %s'
                                   % (changetype, filename))

                dl = self._diff_files(old_file, new_file, filename,
                                      newrevspec, parentrevspec, changetype)
                diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)

        return b''.join(diff_lines)
Example #39
0
    def _process_diffs(self, diff_entries):
        """Process the given diff entries.

        Args:
            diff_entries (list):
                The list of diff entries.

        Returns:
            bytes:
            The processed diffs.
        """
        diff_lines = []

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        for f in diff_entries:
            f = f.strip()

            if not f:
                continue

            m = re.search(
                br'(?P<type>[ACMD]) (?P<file>.*) '
                br'(?P<revspec>rev:revid:[-\d]+) '
                br'(?P<parentrevspec>rev:revid:[-\d]+) '
                br'src:(?P<srcpath>.*) '
                br'dst:(?P<dstpath>.*)$', f)
            if not m:
                raise SCMError('Could not parse "cm log" response: %s' % f)

            changetype = m.group('type')
            filename = m.group('file')

            if changetype == b'M':
                # Handle moved files as a delete followed by an add.
                # Clunky, but at least it works
                oldfilename = m.group('srcpath')
                oldspec = m.group('revspec')
                newfilename = m.group('dstpath')
                newspec = m.group('revspec')

                self._write_file(oldfilename, oldspec, tmp_diff_from_filename)
                dl = self._diff_files(tmp_diff_from_filename, empty_filename,
                                      oldfilename, 'rev:revid:-1', oldspec,
                                      changetype)
                diff_lines += dl

                self._write_file(newfilename, newspec, tmp_diff_to_filename)
                dl = self._diff_files(empty_filename, tmp_diff_to_filename,
                                      newfilename, newspec, 'rev:revid:-1',
                                      changetype)
                diff_lines += dl

            else:
                newrevspec = m.group('revspec')
                parentrevspec = m.group('parentrevspec')

                logging.debug('Type %s File %s Old %s New %s', changetype,
                              filename, parentrevspec, newrevspec)

                old_file = new_file = empty_filename

                if (changetype in [b'A']
                        or (changetype in [b'C']
                            and parentrevspec == b'rev:revid:-1')):
                    # There's only one content to show
                    self._write_file(filename, newrevspec,
                                     tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in [b'C']:
                    self._write_file(filename, parentrevspec,
                                     tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                    self._write_file(filename, newrevspec,
                                     tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in [b'D']:
                    self._write_file(filename, parentrevspec,
                                     tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                else:
                    raise SCMError('Unknown change type "%s" for %s' %
                                   (changetype, filename))

                dl = self._diff_files(old_file, new_file, filename, newrevspec,
                                      parentrevspec, changetype)
                diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)

        return b''.join(diff_lines)
Example #40
0
    def _path_diff(self, args):
        """
        Process a path-style diff. This allows people to post individual files
        in various ways.

        Multiple paths may be specified in `args`.  The path styles supported
        are:

        //path/to/file
        Upload file as a "new" file.

        //path/to/dir/...
        Upload all files as "new" files.

        //path/to/file[@#]rev
        Upload file from that rev as a "new" file.

        //path/to/file[@#]rev,[@#]rev
        Upload a diff between revs.

        //path/to/dir/...[@#]rev,[@#]rev
        Upload a diff of all files between revs in that directory.
        """
        r_revision_range = re.compile(r'^(?P<path>//[^@#]+)' +
                                      r'(?P<revision1>[#@][^,]+)?' +
                                      r'(?P<revision2>,[#@][^,]+)?$')

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        diff_lines = []

        for path in args:
            m = r_revision_range.match(path)

            if not m:
                die('Path %r does not match a valid Perforce path.' % (path,))
            revision1 = m.group('revision1')
            revision2 = m.group('revision2')
            first_rev_path = m.group('path')

            if revision1:
                first_rev_path += revision1
            records = self.p4.files(first_rev_path)

            # Make a map for convenience.
            files = {}

            # Records are:
            # 'rev': '1'
            # 'func': '...'
            # 'time': '1214418871'
            # 'action': 'edit'
            # 'type': 'ktext'
            # 'depotFile': '...'
            # 'change': '123456'
            for record in records:
                if record['action'] not in ('delete', 'move/delete'):
                    if revision2:
                        files[record['depotFile']] = [record, None]
                    else:
                        files[record['depotFile']] = [None, record]

            if revision2:
                # [1:] to skip the comma.
                second_rev_path = m.group('path') + revision2[1:]
                records = self.p4.files(second_rev_path)
                for record in records:
                    if record['action'] not in ('delete', 'move/delete'):
                        try:
                            m = files[record['depotFile']]
                            m[1] = record
                        except KeyError:
                            files[record['depotFile']] = [None, record]

            old_file = new_file = empty_filename
            changetype_short = None

            for depot_path, (first_record, second_record) in files.items():
                old_file = new_file = empty_filename
                if first_record is None:
                    new_path = '%s#%s' % (depot_path, second_record['rev'])
                    self._write_file(new_path, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                    changetype_short = 'A'
                    base_revision = 0
                elif second_record is None:
                    old_path = '%s#%s' % (depot_path, first_record['rev'])
                    self._write_file(new_path, tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                    changetype_short = 'D'
                    base_revision = int(first_record['rev'])
                elif first_record['rev'] == second_record['rev']:
                    # We when we know the revisions are the same, we don't need
                    # to do any diffing. This speeds up large revision-range
                    # diffs quite a bit.
                    continue
                else:
                    old_path = '%s#%s' % (depot_path, first_record['rev'])
                    new_path = '%s#%s' % (depot_path, second_record['rev'])
                    self._write_file(old_path, tmp_diff_from_filename)
                    self._write_file(new_path, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                    old_file = tmp_diff_from_filename
                    changetype_short = 'M'
                    base_revision = int(first_record['rev'])

                # TODO: We're passing new_depot_file='' here just to make
                # things work like they did before the moved file change was
                # added (58ccae27). This section of code needs to be updated
                # to properly work with moved files.
                dl = self._do_diff(old_file, new_file, depot_path,
                                   base_revision, '', changetype_short,
                                   ignore_unmodified=True)
                diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)

        return {
            'diff': ''.join(diff_lines),
        }
Example #41
0
    def _diff_pending(self, changenum):
        logging.info('Generating diff for pending changeset %s' % changenum)

        opened_files = self.p4.opened(changenum)

        if not opened_files:
            die("Couldn't find any affected files for this change.")

        diff_lines = []

        action_mapping = {
            'edit': 'M',
            'integrate': 'M',
            'add': 'A',
            'branch': 'A',
            'delete': 'D',
        }

        if (self.capabilities and
            self.capabilities.has_capability('scmtools', 'perforce',
                                             'moved_files')):
            action_mapping['move/add'] = 'MV-a'
            action_mapping['move/delete'] = 'MV'
        else:
            # The Review Board server doesn't support moved files for
            # perforce--create a diff that shows moved files as adds and
            # deletes.
            action_mapping['move/add'] = 'A'
            action_mapping['move/delete'] = 'D'

        for f in opened_files:
            depot_path = f['depotFile']
            new_depot_path = ''
            try:
                base_revision = int(f['rev'])
            except ValueError:
                # For actions like deletes, there won't be any "current
                # revision". Just pass through whatever was there before
                base_revision = f['rev']
            action = f['action']

            empty_file = make_tempfile()
            old_file = make_tempfile()

            logging.debug('Processing %s of %s', action, depot_path)

            try:
                changetype_short = action_mapping[action]
            except KeyError:
                die('Unknown action type "%s" for %s' % (action, depot_path))

            if changetype_short == 'M':
                # Get the old version out of perforce and stick it in
                # 'old_file'
                try:
                    old_depot_path = '%s#%s' % (depot_path, base_revision)
                    self._write_file(old_depot_path, old_file)
                except ValueError, e:
                    logging.warning('Skipping file %s: %s', old_depot_path, e)
                    continue

                # Just reference the file within the client view for the new
                # file
                new_file = self._depot_to_local(depot_path)
            elif changetype_short == 'A':
                # Just reference the file within the client view for the new
                # file
                new_file = self._depot_to_local(depot_path)

                if os.path.islink(new_file):
                    logging.warning('Skipping symlink %s', new_file)
                    continue
Example #42
0
    def changenum_diff(self, changenum):
        logging.debug("changenum_diff: %s" % (changenum))
        files = execute(["cm", "log", "cs:" + changenum,
                         "--csFormat={items}",
                         "--itemFormat={shortstatus} {path} "
                         "rev:revid:{revid} rev:revid:{parentrevid} "
                         "src:{srccmpath} rev:revid:{srcdirrevid} "
                         "dst:{dstcmpath} rev:revid:{dstdirrevid}{newline}"],
                        split_lines = True)

        logging.debug("got files: %s" % (files))

        # Diff generation based on perforce client
        diff_lines = []

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        for f in files:
            f = f.strip()

            if not f:
                continue

            m = re.search(r'(?P<type>[ACIMR]) (?P<file>.*) '
                          r'(?P<revspec>rev:revid:[-\d]+) '
                          r'(?P<parentrevspec>rev:revid:[-\d]+) '
                          r'src:(?P<srcpath>.*) '
                          r'(?P<srcrevspec>rev:revid:[-\d]+) '
                          r'dst:(?P<dstpath>.*) '
                          r'(?P<dstrevspec>rev:revid:[-\d]+)$',
                          f)
            if not m:
                die("Could not parse 'cm log' response: %s" % f)

            changetype = m.group("type")
            filename = m.group("file")

            if changetype == "M":
                # Handle moved files as a delete followed by an add.
                # Clunky, but at least it works
                oldfilename = m.group("srcpath")
                oldspec = m.group("srcrevspec")
                newfilename = m.group("dstpath")
                newspec = m.group("dstrevspec")

                self.write_file(oldfilename, oldspec, tmp_diff_from_filename)
                dl = self.diff_files(tmp_diff_from_filename, empty_filename,
                                     oldfilename, "rev:revid:-1", oldspec,
                                     changetype)
                diff_lines += dl

                self.write_file(newfilename, newspec, tmp_diff_to_filename)
                dl = self.diff_files(empty_filename, tmp_diff_to_filename,
                                     newfilename, newspec, "rev:revid:-1",
                                     changetype)
                diff_lines += dl
            else:
                newrevspec = m.group("revspec")
                parentrevspec = m.group("parentrevspec")

                logging.debug("Type %s File %s Old %s New %s" % (changetype,
                                                         filename,
                                                         parentrevspec,
                                                         newrevspec))

                old_file = new_file = empty_filename

                if (changetype in ['A'] or
                    (changetype in ['C', 'I'] and
                     parentrevspec == "rev:revid:-1")):
                    # File was Added, or a Change or Merge (type I) and there
                    # is no parent revision
                    self.write_file(filename, newrevspec, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in ['C', 'I']:
                    # File was Changed or Merged (type I)
                    self.write_file(filename, parentrevspec,
                                    tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                    self.write_file(filename, newrevspec, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in ['R']:
                    # File was Removed
                    self.write_file(filename, parentrevspec,
                                    tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                else:
                    die("Don't know how to handle change type '%s' for %s" %
                        (changetype, filename))

                dl = self.diff_files(old_file, new_file, filename,
                                     newrevspec, parentrevspec, changetype)
                diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)

        return ''.join(diff_lines)
Example #43
0
    def _diff_files(self, old_file, new_file):
        """Return unified diff for file.

        Most effective and reliable way is use gnu diff.
        """

        # In snapshot view, diff can't access history clearcase file version
        # so copy cc files to tempdir by 'cleartool get -to dest-pname pname',
        # and compare diff with the new temp ones.
        if self.viewtype == 'snapshot':
            # Create temporary file first.
            tmp_old_file = make_tempfile()
            tmp_new_file = make_tempfile()

            # Delete so cleartool can write to them.
            try:
                os.remove(tmp_old_file)
            except OSError:
                pass

            try:
                os.remove(tmp_new_file)
            except OSError:
                pass

            execute(["cleartool", "get", "-to", tmp_old_file, old_file])
            execute(["cleartool", "get", "-to", tmp_new_file, new_file])
            diff_cmd = ["diff", "-uN", tmp_old_file, tmp_new_file]
        else:
            diff_cmd = ["diff", "-uN", old_file, new_file]

        dl = execute(diff_cmd,
                     extra_ignore_errors=(1, 2),
                     translate_newlines=False)

        # Replace temporary file name in diff with the one in snapshot view.
        if self.viewtype == "snapshot":
            dl = dl.replace(tmp_old_file, old_file)
            dl = dl.replace(tmp_new_file, new_file)

        # If the input file has ^M characters at end of line, lets ignore them.
        dl = dl.replace('\r\r\n', '\r\n')
        dl = dl.splitlines(True)

        # Special handling for the output of the diff tool on binary files:
        #     diff outputs "Files a and b differ"
        # and the code below expects the output to start with
        #     "Binary files "
        if (len(dl) == 1 and dl[0].startswith('Files %s and %s differ' %
                                              (old_file, new_file))):
            dl = ['Binary files %s and %s differ\n' % (old_file, new_file)]

        # We need oids of files to translate them to paths on reviewboard
        # repository.
        old_oid = execute(["cleartool", "describe", "-fmt", "%On", old_file])
        new_oid = execute(["cleartool", "describe", "-fmt", "%On", new_file])

        if dl == [] or dl[0].startswith("Binary files "):
            if dl == []:
                dl = ["File %s in your changeset is unmodified\n" % new_file]

            dl.insert(0, "==== %s %s ====\n" % (old_oid, new_oid))
            dl.append('\n')
        else:
            dl.insert(2, "==== %s %s ====\n" % (old_oid, new_oid))

        return dl
Example #44
0
    def _diff_files(self, old_file, new_file):
        """Return unified diff for file.

        Most effective and reliable way is use gnu diff.
        """

        # In snapshot view, diff can't access history clearcase file version
        # so copy cc files to tempdir by 'cleartool get -to dest-pname pname',
        # and compare diff with the new temp ones.
        if self.viewtype == 'snapshot':
            # Create temporary file first.
            tmp_old_file = make_tempfile()
            tmp_new_file = make_tempfile()

            # Delete so cleartool can write to them.
            try:
                os.remove(tmp_old_file)
            except OSError:
                pass

            try:
                os.remove(tmp_new_file)
            except OSError:
                pass

            execute(["cleartool", "get", "-to", tmp_old_file, old_file])
            execute(["cleartool", "get", "-to", tmp_new_file, new_file])
            diff_cmd = ["diff", "-uN", tmp_old_file, tmp_new_file]
        else:
            diff_cmd = ["diff", "-uN", old_file, new_file]

        dl = execute(diff_cmd, extra_ignore_errors=(1, 2),
                     translate_newlines=False)

        # Replace temporary file name in diff with the one in snapshot view.
        if self.viewtype == "snapshot":
            dl = dl.replace(tmp_old_file, old_file)
            dl = dl.replace(tmp_new_file, new_file)

        # If the input file has ^M characters at end of line, lets ignore them.
        dl = dl.replace('\r\r\n', '\r\n')
        dl = dl.splitlines(True)

        # Special handling for the output of the diff tool on binary files:
        #     diff outputs "Files a and b differ"
        # and the code below expects the output to start with
        #     "Binary files "
        if (len(dl) == 1 and
            dl[0].startswith('Files %s and %s differ' % (old_file, new_file))):
            dl = ['Binary files %s and %s differ\n' % (old_file, new_file)]

        # We need oids of files to translate them to paths on reviewboard
        # repository.
        old_oid = execute(["cleartool", "describe", "-fmt", "%On", old_file])
        new_oid = execute(["cleartool", "describe", "-fmt", "%On", new_file])

        if dl == [] or dl[0].startswith("Binary files "):
            if dl == []:
                dl = ["File %s in your changeset is unmodified\n" % new_file]

            dl.insert(0, "==== %s %s ====\n" % (old_oid, new_oid))
            dl.append('\n')
        else:
            dl.insert(2, "==== %s %s ====\n" % (old_oid, new_oid))

        return dl
Example #45
0
    def _diff_files(self, old_file, new_file):
        """Return a unified diff for file.

        Args:
            old_file (unicode):
                The name and version of the old file.

            new_file (unicode):
                The name and version of the new file.

        Returns:
            bytes:
            The diff between the two files.
        """
        # In snapshot view, diff can't access history clearcase file version
        # so copy cc files to tempdir by 'cleartool get -to dest-pname pname',
        # and compare diff with the new temp ones.
        if self.viewtype == 'snapshot':
            # Create temporary file first.
            tmp_old_file = make_tempfile()
            tmp_new_file = make_tempfile()

            # Delete so cleartool can write to them.
            try:
                os.remove(tmp_old_file)
            except OSError:
                pass

            try:
                os.remove(tmp_new_file)
            except OSError:
                pass

            execute(['cleartool', 'get', '-to', tmp_old_file, old_file])
            execute(['cleartool', 'get', '-to', tmp_new_file, new_file])
            diff_cmd = ['diff', '-uN', tmp_old_file, tmp_new_file]
        else:
            diff_cmd = ['diff', '-uN', old_file, new_file]

        dl = execute(diff_cmd,
                     extra_ignore_errors=(1, 2),
                     results_unicode=False)

        # Replace temporary file name in diff with the one in snapshot view.
        if self.viewtype == 'snapshot':
            dl = dl.replace(tmp_old_file.encode('utf-8'),
                            old_file.encode('utf-8'))
            dl = dl.replace(tmp_new_file.encode('utf-8'),
                            new_file.encode('utf-8'))

        # If the input file has ^M characters at end of line, lets ignore them.
        dl = dl.replace(b'\r\r\n', b'\r\n')
        dl = dl.splitlines(True)

        # Special handling for the output of the diff tool on binary files:
        #     diff outputs "Files a and b differ"
        # and the code below expects the output to start with
        #     "Binary files "
        if (len(dl) == 1 and
            dl[0].startswith(b'Files %s and %s differ'
                             % (old_file.encode('utf-8'),
                                new_file.encode('utf-8')))):
            dl = [b'Binary files %s and %s differ\n'
                  % (old_file.encode('utf-8'),
                     new_file.encode('utf-8'))]

        # We need oids of files to translate them to paths on reviewboard
        # repository.
        old_oid = execute(['cleartool', 'describe', '-fmt', '%On', old_file],
                          results_unicode=False)
        new_oid = execute(['cleartool', 'describe', '-fmt', '%On', new_file],
                          results_unicode=False)

        if dl == [] or dl[0].startswith(b'Binary files '):
            if dl == []:
                dl = [b'File %s in your changeset is unmodified\n' %
                      new_file.encode('utf-8')]

            dl.insert(0, b'==== %s %s ====\n' % (old_oid, new_oid))
            dl.append(b'\n')
        else:
            dl.insert(2, b'==== %s %s ====\n' % (old_oid, new_oid))

        return dl
Example #46
0
    def _path_diff(self, args):
        """
        Process a path-style diff.  See _changenum_diff for the alternate
        version that handles specific change numbers.

        Multiple paths may be specified in `args`.  The path styles supported
        are:

        //path/to/file
        Upload file as a "new" file.

        //path/to/dir/...
        Upload all files as "new" files.

        //path/to/file[@#]rev
        Upload file from that rev as a "new" file.

        //path/to/file[@#]rev,[@#]rev
        Upload a diff between revs.

        //path/to/dir/...[@#]rev,[@#]rev
        Upload a diff of all files between revs in that directory.
        """
        r_revision_range = re.compile(r'^(?P<path>//[^@#]+)' +
                                      r'(?P<revision1>[#@][^,]+)?' +
                                      r'(?P<revision2>,[#@][^,]+)?$')

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        diff_lines = []

        for path in args:
            m = r_revision_range.match(path)

            if not m:
                die('Path %r does not match a valid Perforce path.' % (path,))
            revision1 = m.group('revision1')
            revision2 = m.group('revision2')
            first_rev_path = m.group('path')

            if revision1:
                first_rev_path += revision1
            records = self._run_p4(['files', first_rev_path])

            # Make a map for convenience.
            files = {}

            # Records are:
            # 'rev': '1'
            # 'func': '...'
            # 'time': '1214418871'
            # 'action': 'edit'
            # 'type': 'ktext'
            # 'depotFile': '...'
            # 'change': '123456'
            for record in records:
                if record['action'] not in ('delete', 'move/delete'):
                    if revision2:
                        files[record['depotFile']] = [record, None]
                    else:
                        files[record['depotFile']] = [None, record]

            if revision2:
                # [1:] to skip the comma.
                second_rev_path = m.group('path') + revision2[1:]
                records = self._run_p4(['files', second_rev_path])
                for record in records:
                    if record['action'] not in ('delete', 'move/delete'):
                        try:
                            m = files[record['depotFile']]
                            m[1] = record
                        except KeyError:
                            files[record['depotFile']] = [None, record]

            old_file = new_file = empty_filename
            changetype_short = None

            for depot_path, (first_record, second_record) in files.items():
                old_file = new_file = empty_filename
                if first_record is None:
                    self._write_file(depot_path + '#' + second_record['rev'],
                                     tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                    changetype_short = 'A'
                    base_revision = 0
                elif second_record is None:
                    self._write_file(depot_path + '#' + first_record['rev'],
                                     tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                    changetype_short = 'D'
                    base_revision = int(first_record['rev'])
                elif first_record['rev'] == second_record['rev']:
                    # We when we know the revisions are the same, we don't need
                    # to do any diffing. This speeds up large revision-range
                    # diffs quite a bit.
                    continue
                else:
                    self._write_file(depot_path + '#' + first_record['rev'],
                                     tmp_diff_from_filename)
                    self._write_file(depot_path + '#' + second_record['rev'],
                                     tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                    old_file = tmp_diff_from_filename
                    changetype_short = 'M'
                    base_revision = int(first_record['rev'])

                dl = self._do_diff(old_file, new_file, depot_path,
                                   base_revision, changetype_short,
                                   ignore_unmodified=True)
                diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)
        return (''.join(diff_lines), None)
Example #47
0
    def _process_diffs(self, my_diff_entries):
        # Diff generation based on perforce client
        diff_lines = []

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        for f in my_diff_entries:
            f = f.strip()

            if not f:
                continue

            m = re.search(
                r'(?P<type>[ACMD]) (?P<file>.*) '
                r'(?P<revspec>rev:revid:[-\d]+) '
                r'(?P<parentrevspec>rev:revid:[-\d]+) '
                r'src:(?P<srcpath>.*) '
                r'dst:(?P<dstpath>.*)$', f)
            if not m:
                raise SCMError('Could not parse "cm log" response: %s' % f)

            changetype = m.group("type")
            filename = m.group("file")

            if changetype == "M":
                # Handle moved files as a delete followed by an add.
                # Clunky, but at least it works
                oldfilename = m.group("srcpath")
                oldspec = m.group("revspec")
                newfilename = m.group("dstpath")
                newspec = m.group("revspec")

                self._write_file(oldfilename, oldspec, tmp_diff_from_filename)
                dl = self._diff_files(tmp_diff_from_filename, empty_filename,
                                      oldfilename, "rev:revid:-1", oldspec,
                                      changetype)
                diff_lines += dl

                self._write_file(newfilename, newspec, tmp_diff_to_filename)
                dl = self._diff_files(empty_filename, tmp_diff_to_filename,
                                      newfilename, newspec, "rev:revid:-1",
                                      changetype)
                diff_lines += dl

            else:
                newrevspec = m.group("revspec")
                parentrevspec = m.group("parentrevspec")

                logging.debug(
                    "Type %s File %s Old %s New %s" %
                    (changetype, filename, parentrevspec, newrevspec))

                old_file = new_file = empty_filename

                if (changetype in ['A'] or
                    (changetype in ['C'] and parentrevspec == "rev:revid:-1")):
                    # There's only one content to show
                    self._write_file(filename, newrevspec,
                                     tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in ['C']:
                    self._write_file(filename, parentrevspec,
                                     tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                    self._write_file(filename, newrevspec,
                                     tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in ['D']:
                    self._write_file(filename, parentrevspec,
                                     tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                else:
                    raise SCMError("Don't know how to handle change type "
                                   "'%s' for %s" % (changetype, filename))

                dl = self._diff_files(old_file, new_file, filename, newrevspec,
                                      parentrevspec, changetype)
                diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)

        return ''.join(diff_lines)
Example #48
0
    def _changenum_diff(self, changenum):
        """
        Process a diff for a particular change number.  This handles both
        pending and submitted changelists.

        See _path_diff for the alternate version that does diffs of depot
        paths.
        """
        # TODO: It might be a good idea to enhance PerforceDiffParser to
        # understand that newFile could include a revision tag for post-submit
        # reviewing.
        cl_is_pending = False

        logging.info("Generating diff for changenum %s" % changenum)

        description = []

        if changenum == "default":
            cl_is_pending = True
        else:
            description = self.p4.describe(changenum=changenum,
                                           password=self.options.p4_passwd)

            if re.search("no such changelist", description[0]):
                die("CLN %s does not exist." % changenum)

            # Some P4 wrappers are addding an extra line before the description
            if '*pending*' in description[0] or '*pending*' in description[1]:
                cl_is_pending = True

        v = self.p4d_version

        if cl_is_pending and (v[0] < 2002 or (v[0] == "2002" and v[1] < 2)
                              or changenum == "default"):
            # Pre-2002.2 doesn't give file list in pending changelists,
            # or we don't have a description for a default changeset,
            # so we have to get it a different way.
            info = self.p4.opened(changenum)

            if (len(info) == 1 and
                info[0].startswith("File(s) not opened on this client.")):
                die("Couldn't find any affected files for this change.")

            for line in info:
                data = line.split(" ")
                description.append("... %s %s" % (data[0], data[2]))

        else:
            # Get the file list
            for line_num, line in enumerate(description):
                if 'Affected files ...' in line:
                    break
            else:
                # Got to the end of all the description lines and didn't find
                # what we were looking for.
                die("Couldn't find any affected files for this change.")

            description = description[line_num + 2:]

        diff_lines = []

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        for line in description:
            line = line.strip()
            if not line:
                continue

            m = re.search(r'\.\.\. ([^#]+)#(\d+) '
                          r'(add|edit|delete|integrate|branch|move/add'
                          r'|move/delete)',
                          line)
            if not m:
                die("Unsupported line from p4 opened: %s" % line)

            depot_path = m.group(1)
            base_revision = int(m.group(2))
            if not cl_is_pending:
                # If the changelist is pending our base revision is the one
                # that's currently in the depot. If we're not pending the base
                # revision is actually the revision prior to this one.
                base_revision -= 1

            changetype = m.group(3)

            logging.debug('Processing %s of %s' % (changetype, depot_path))

            old_file = new_file = empty_filename
            old_depot_path = new_depot_path = None
            new_depot_path = ''
            changetype_short = None
            supports_moves = (
                self.capabilities and
                self.capabilities.has_capability('scmtools', 'perforce',
                                                 'moved_files'))

            if changetype in ['edit', 'integrate']:
                # A big assumption
                new_revision = base_revision + 1

                # We have an old file, get p4 to take this old version from the
                # depot and put it into a plain old temp file for us
                old_depot_path = "%s#%s" % (depot_path, base_revision)
                self._write_file(old_depot_path, tmp_diff_from_filename)
                old_file = tmp_diff_from_filename

                # Also print out the new file into a tmpfile
                if cl_is_pending:
                    new_file = self._depot_to_local(depot_path)
                else:
                    new_depot_path = "%s#%s" % (depot_path, new_revision)
                    self._write_file(new_depot_path, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename

                changetype_short = "M"
            elif (changetype in ('add', 'branch') or
                  (changetype == 'move/add' and not supports_moves)):
                # We have a new file, get p4 to put this new file into a pretty
                # temp file for us. No old file to worry about here.
                if cl_is_pending:
                    new_file = self._depot_to_local(depot_path)
                else:
                    new_depot_path = "%s#%s" % (depot_path, 1)
                    self._write_file(new_depot_path, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename

                changetype_short = "A"
            elif (changetype == 'delete' or
                  (changetype == 'move/delete' and not supports_moves)):
                # We've deleted a file, get p4 to put the deleted file into a
                # temp file for us. The new file remains the empty file.
                old_depot_path = "%s#%s" % (depot_path, base_revision)
                self._write_file(old_depot_path, tmp_diff_from_filename)
                old_file = tmp_diff_from_filename
                changetype_short = "D"
            elif changetype == 'move/add':
                # The server supports move information. We can ignore this,
                # though, since there should be a "move/delete" that's handled
                # at some point.
                continue
            elif changetype == 'move/delete':
                # The server supports move information, and we found a moved
                # file. We'll figure out where we moved to and represent
                # that information.
                stat_info = self.p4.fstat(depot_path,
                                          ['clientFile', 'movedFile'])

                if ('clientFile' not in stat_info or
                    'movedFile' not in stat_info):
                    die('Unable to get necessary fstat information on %s' %
                        depot_path)

                old_depot_path = '%s#%s' % (depot_path, base_revision)
                self._write_file(old_depot_path, tmp_diff_from_filename)
                old_file = tmp_diff_from_filename

                # Get information on the new file.
                moved_stat_info = self.p4.fstat(stat_info['movedFile'],
                                                ['clientFile', 'depotFile'])

                if ('clientFile' not in moved_stat_info or
                    'depotFile' not in moved_stat_info):
                    die('Unable to get necessary fstat information on %s' %
                        stat_info['movedFile'])

                # This is a new file and we can just access it directly.
                # There's no revision 1 like with an add.
                new_file = moved_stat_info['clientFile']
                new_depot_path = moved_stat_info['depotFile']

                changetype_short = 'MV'
            else:
                die("Unknown change type '%s' for %s" % (changetype,
                                                         depot_path))

            dl = self._do_diff(old_file, new_file, depot_path, base_revision,
                               new_depot_path, changetype_short)
            diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)
        return (''.join(diff_lines), None)
Example #49
0
    def test_diff_for_submitted_changelist(self):
        """Testing PerforceClient.diff with a submitted changelist"""
        class TestWrapper(self.P4DiffTestWrapper):
            def change(self, changelist):
                return [{
                    'Change': '12345',
                    'Date': '2013/12/19 11:32:45',
                    'User': '******',
                    'Status': 'submitted',
                    'Description': 'My change description\n',
                }]

            def filelog(self, path):
                return [{
                    'change0': '12345',
                    'action0': 'edit',
                    'rev0': '3',
                    'depotFile': '//mydepot/test/README',
                }]

        client = PerforceClient(TestWrapper)
        client.p4.repo_files = [
            {
                'depotFile': '//mydepot/test/README',
                'rev': '2',
                'action': 'edit',
                'change': '12345',
                'text': 'This is a test.\n',
            },
            {
                'depotFile': '//mydepot/test/README',
                'rev': '3',
                'action': 'edit',
                'change': '',
                'text': 'This is a mess.\n',
            },
        ]

        readme_file = make_tempfile()
        client.p4.print_file('//mydepot/test/README#3', readme_file)

        client.p4.where_files = {
            '//mydepot/test/README': readme_file,
        }
        client.p4.repo_files = [
            {
                'depotFile': '//mydepot/test/README',
                'rev': '2',
                'action': 'edit',
                'change': '12345',
                'text': 'This is a test.\n',
            },
            {
                'depotFile': '//mydepot/test/README',
                'rev': '3',
                'action': 'edit',
                'change': '',
                'text': 'This is a mess.\n',
            },
        ]

        revisions = client.parse_revision_spec(['12345'])
        diff = client.diff(revisions)
        self._compare_diff(diff,
                           '8af5576f5192ca87731673030efb5f39',
                           expect_changenum=False)
Example #50
0
    def process_diffs(self, my_diff_entries):
        # Diff generation based on perforce client
        diff_lines = []

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        for f in my_diff_entries:
            f = f.strip()

            if not f:
                continue

            m = re.search(
                r"(?P<type>[ACMD]) (?P<file>.*) "
                r"(?P<revspec>rev:revid:[-\d]+) "
                r"(?P<parentrevspec>rev:revid:[-\d]+) "
                r"src:(?P<srcpath>.*) "
                r"dst:(?P<dstpath>.*)$",
                f,
            )
            if not m:
                die("Could not parse 'cm log' response: %s" % f)

            changetype = m.group("type")
            filename = m.group("file")

            if changetype == "M":
                # Handle moved files as a delete followed by an add.
                # Clunky, but at least it works
                oldfilename = m.group("srcpath")
                oldspec = m.group("revspec")
                newfilename = m.group("dstpath")
                newspec = m.group("revspec")

                self.write_file(oldfilename, oldspec, tmp_diff_from_filename)
                dl = self.diff_files(
                    tmp_diff_from_filename, empty_filename, oldfilename, "rev:revid:-1", oldspec, changetype
                )
                diff_lines += dl

                self.write_file(newfilename, newspec, tmp_diff_to_filename)
                dl = self.diff_files(
                    empty_filename, tmp_diff_to_filename, newfilename, newspec, "rev:revid:-1", changetype
                )
                diff_lines += dl

            else:
                newrevspec = m.group("revspec")
                parentrevspec = m.group("parentrevspec")

                logging.debug("Type %s File %s Old %s New %s" % (changetype, filename, parentrevspec, newrevspec))

                old_file = new_file = empty_filename

                if changetype in ["A"] or (changetype in ["C"] and parentrevspec == "rev:revid:-1"):
                    # There's only one content to show
                    self.write_file(filename, newrevspec, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in ["C"]:
                    self.write_file(filename, parentrevspec, tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                    self.write_file(filename, newrevspec, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                elif changetype in ["D"]:
                    self.write_file(filename, parentrevspec, tmp_diff_from_filename)
                    old_file = tmp_diff_from_filename
                else:
                    die("Don't know how to handle change type '%s' for %s" % (changetype, filename))

                dl = self.diff_files(old_file, new_file, filename, newrevspec, parentrevspec, changetype)
                diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)

        return "".join(diff_lines)
Example #51
0
    def _test_diff_with_moved_files(self, expected_diff_hash, caps={}):
        client = self._build_client()
        client.capabilities = Capabilities(caps)
        client.p4.repo_files = [
            {
                'depotFile': '//mydepot/test/README',
                'rev': '2',
                'action': 'move/delete',
                'change': '12345',
                'text': 'This is a test.\n',
            },
            {
                'depotFile': '//mydepot/test/README-new',
                'rev': '1',
                'action': 'move/add',
                'change': '12345',
                'text': 'This is a mess.\n',
            },
            {
                'depotFile': '//mydepot/test/COPYING',
                'rev': '2',
                'action': 'move/delete',
                'change': '12345',
                'text': 'Copyright 2013 Joe User.\n',
            },
            {
                'depotFile': '//mydepot/test/COPYING-new',
                'rev': '1',
                'action': 'move/add',
                'change': '12345',
                'text': 'Copyright 2013 Joe User.\n',
            },
        ]

        readme_file = make_tempfile()
        copying_file = make_tempfile()
        readme_file_new = make_tempfile()
        copying_file_new = make_tempfile()
        client.p4.print_file('//mydepot/test/README#2', readme_file)
        client.p4.print_file('//mydepot/test/COPYING#2', copying_file)
        client.p4.print_file('//mydepot/test/README-new#1', readme_file_new)
        client.p4.print_file('//mydepot/test/COPYING-new#1', copying_file_new)

        client.p4.where_files = {
            '//mydepot/test/README': readme_file,
            '//mydepot/test/COPYING': copying_file,
            '//mydepot/test/README-new': readme_file_new,
            '//mydepot/test/COPYING-new': copying_file_new,
        }

        client.p4.fstat_files = {
            '//mydepot/test/README': {
                'clientFile': readme_file,
                'movedFile': '//mydepot/test/README-new',
            },
            '//mydepot/test/README-new': {
                'clientFile': readme_file_new,
                'depotFile': '//mydepot/test/README-new',
            },
            '//mydepot/test/COPYING': {
                'clientFile': copying_file,
                'movedFile': '//mydepot/test/COPYING-new',
            },
            '//mydepot/test/COPYING-new': {
                'clientFile': copying_file_new,
                'depotFile': '//mydepot/test/COPYING-new',
            },
        }

        revisions = client.parse_revision_spec(['12345'])
        diff = client.diff(revisions)
        self._compare_diff(diff, expected_diff_hash)
Example #52
0
    def branch_diff(self, args):
        logging.debug("branch diff: %s" % (args))

        if len(args) > 0:
            branch = args[0]
        else:
            branch = args

        if not branch.startswith("br:"):
            return None

        if not self.options.branch:
            self.options.branch = branch

        files = execute(["cm", "fbc", branch, "--format={3} {4}"],
                        split_lines = True)
        logging.debug("got files: %s" % (files))

        diff_lines = []

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        for f in files:
            f = f.strip()

            if not f:
                continue

            m = re.search(r'^(?P<branch>.*)#(?P<revno>\d+) (?P<file>.*)$', f)

            if not m:
                die("Could not parse 'cm fbc' response: %s" % f)

            filename = m.group("file")
            branch = m.group("branch")
            revno = m.group("revno")

            # Get the base revision with a cm find
            basefiles = execute(["cm", "find", "revs", "where",
                                 "item='" + filename + "'", "and",
                                 "branch='" + branch + "'", "and",
                                 "revno=" + revno,
                                 "--format={item} rev:revid:{id} "
                                 "rev:revid:{parent}", "--nototal"],
                                split_lines = True)

            # We only care about the first line
            m = re.search(r'^(?P<filename>.*) '
                              r'(?P<revspec>rev:revid:[-\d]+) '
                              r'(?P<parentrevspec>rev:revid:[-\d]+)$',
                              basefiles[0])
            basefilename = m.group("filename")
            newrevspec = m.group("revspec")
            parentrevspec = m.group("parentrevspec")

            # Cope with adds/removes
            changetype = "C"

            if parentrevspec == "rev:revid:-1":
                changetype = "A"
            elif newrevspec == "rev:revid:-1":
                changetype = "R"

            logging.debug("Type %s File %s Old %s New %s" % (changetype,
                                                     basefilename,
                                                     parentrevspec,
                                                     newrevspec))

            old_file = new_file = empty_filename

            if changetype == "A":
                # File Added
                self.write_file(basefilename, newrevspec,
                                tmp_diff_to_filename)
                new_file = tmp_diff_to_filename
            elif changetype == "R":
                # File Removed
                self.write_file(basefilename, parentrevspec,
                                tmp_diff_from_filename)
                old_file = tmp_diff_from_filename
            else:
                self.write_file(basefilename, parentrevspec,
                                tmp_diff_from_filename)
                old_file = tmp_diff_from_filename

                self.write_file(basefilename, newrevspec,
                                tmp_diff_to_filename)
                new_file = tmp_diff_to_filename

            dl = self.diff_files(old_file, new_file, basefilename,
                                 newrevspec, parentrevspec, changetype)
            diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)

        return ''.join(diff_lines)
Example #53
0
    def test_edit_file(self):
        """Testing edit_file"""
        result = edit_file(make_tempfile(b'Test content'))

        self.assertEqual(result, 'TEST CONTENT')
Example #54
0
    def test_diff_for_submitted_changelist(self):
        """Testing PerforceClient.diff with a submitted changelist"""
        class TestWrapper(self.P4DiffTestWrapper):
            def change(self, changelist):
                return [{
                    'Change': '12345',
                    'Date': '2013/12/19 11:32:45',
                    'User': '******',
                    'Status': 'submitted',
                    'Description': 'My change description\n',
                }]

            def filelog(self, path):
                return [
                    {
                        'change0': '12345',
                        'action0': 'edit',
                        'rev0': '3',
                        'depotFile': '//mydepot/test/README',
                    }
                ]

        client = PerforceClient(TestWrapper)
        client.p4.repo_files = [
            {
                'depotFile': '//mydepot/test/README',
                'rev': '2',
                'action': 'edit',
                'change': '12345',
                'text': 'This is a test.\n',
            },
            {
                'depotFile': '//mydepot/test/README',
                'rev': '3',
                'action': 'edit',
                'change': '',
                'text': 'This is a mess.\n',
            },
        ]

        readme_file = make_tempfile()
        client.p4.print_file('//mydepot/test/README#3', readme_file)

        client.p4.where_files = {
            '//mydepot/test/README': readme_file,
        }
        client.p4.repo_files = [
            {
                'depotFile': '//mydepot/test/README',
                'rev': '2',
                'action': 'edit',
                'change': '12345',
                'text': 'This is a test.\n',
            },
            {
                'depotFile': '//mydepot/test/README',
                'rev': '3',
                'action': 'edit',
                'change': '',
                'text': 'This is a mess.\n',
            },
        ]

        revisions = client.parse_revision_spec(['12345'])
        diff = client.diff(revisions)
        self._compare_diff(diff, '8af5576f5192ca87731673030efb5f39',
                           expect_changenum=False)
Example #55
0
    def _changenum_diff(self, changenum):
        """
        Process a diff for a particular change number.  This handles both
        pending and submitted changelists.

        See _path_diff for the alternate version that does diffs of depot
        paths.
        """
        # TODO: It might be a good idea to enhance PerforceDiffParser to
        # understand that newFile could include a revision tag for post-submit
        # reviewing.
        cl_is_pending = False

        logging.info("Generating diff for changenum %s" % changenum)

        description = []

        if changenum == "default":
            cl_is_pending = True
        else:
            describeCmd = ["p4"]

            if self.options.p4_passwd:
                describeCmd.append("-P")
                describeCmd.append(self.options.p4_passwd)

            describeCmd = describeCmd + ["describe", "-s", changenum]

            description = execute(describeCmd, split_lines=True)

            if re.search("no such changelist", description[0]):
                die("CLN %s does not exist." % changenum)

            # Some P4 wrappers are addding an extra line before the description
            if '*pending*' in description[0] or '*pending*' in description[1]:
                cl_is_pending = True

        v = self.p4d_version

        if cl_is_pending and (v[0] < 2002 or (v[0] == "2002" and v[1] < 2)
                              or changenum == "default"):
            # Pre-2002.2 doesn't give file list in pending changelists,
            # or we don't have a description for a default changeset,
            # so we have to get it a different way.
            info = execute(["p4", "opened", "-c", str(changenum)],
                           split_lines=True)

            if (len(info) == 1 and
                info[0].startswith("File(s) not opened on this client.")):
                die("Couldn't find any affected files for this change.")

            for line in info:
                data = line.split(" ")
                description.append("... %s %s" % (data[0], data[2]))

        else:
            # Get the file list
            for line_num, line in enumerate(description):
                if 'Affected files ...' in line:
                    break
            else:
                # Got to the end of all the description lines and didn't find
                # what we were looking for.
                die("Couldn't find any affected files for this change.")

            description = description[line_num + 2:]

        diff_lines = []

        empty_filename = make_tempfile()
        tmp_diff_from_filename = make_tempfile()
        tmp_diff_to_filename = make_tempfile()

        for line in description:
            line = line.strip()
            if not line:
                continue

            m = re.search(r'\.\.\. ([^#]+)#(\d+) '
                          r'(add|edit|delete|integrate|branch|move/add'
                          r'|move/delete)',
                          line)
            if not m:
                die("Unsupported line from p4 opened: %s" % line)

            depot_path = m.group(1)
            base_revision = int(m.group(2))
            if not cl_is_pending:
                # If the changelist is pending our base revision is the one
                # that's currently in the depot. If we're not pending the base
                # revision is actually the revision prior to this one.
                base_revision -= 1

            changetype = m.group(3)

            logging.debug('Processing %s of %s' % (changetype, depot_path))

            old_file = new_file = empty_filename
            old_depot_path = new_depot_path = None
            changetype_short = None

            if changetype in ['edit', 'integrate']:
                # A big assumption
                new_revision = base_revision + 1

                # We have an old file, get p4 to take this old version from the
                # depot and put it into a plain old temp file for us
                old_depot_path = "%s#%s" % (depot_path, base_revision)
                self._write_file(old_depot_path, tmp_diff_from_filename)
                old_file = tmp_diff_from_filename

                # Also print out the new file into a tmpfile
                if cl_is_pending:
                    new_file = self._depot_to_local(depot_path)
                else:
                    new_depot_path = "%s#%s" % (depot_path, new_revision)
                    self._write_file(new_depot_path, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename

                changetype_short = "M"
            elif changetype in ['add', 'branch', 'move/add']:
                # We have a new file, get p4 to put this new file into a pretty
                # temp file for us. No old file to worry about here.
                if cl_is_pending:
                    new_file = self._depot_to_local(depot_path)
                else:
                    new_depot_path = "%s#%s" % (depot_path, 1)
                    self._write_file(new_depot_path, tmp_diff_to_filename)
                    new_file = tmp_diff_to_filename
                changetype_short = "A"
            elif changetype in ['delete', 'move/delete']:
                # We've deleted a file, get p4 to put the deleted file into a
                # temp file for us. The new file remains the empty file.
                old_depot_path = "%s#%s" % (depot_path, base_revision)
                self._write_file(old_depot_path, tmp_diff_from_filename)
                old_file = tmp_diff_from_filename
                changetype_short = "D"
            else:
                die("Unknown change type '%s' for %s" % (changetype,
                                                         depot_path))

            dl = self._do_diff(old_file, new_file, depot_path, base_revision,
                               changetype_short)
            diff_lines += dl

        os.unlink(empty_filename)
        os.unlink(tmp_diff_from_filename)
        os.unlink(tmp_diff_to_filename)
        return (''.join(diff_lines), None)
Example #56
0
    def _test_diff_with_moved_files(self, expected_diff_hash, caps={}):
        client = self._build_client()
        client.capabilities = Capabilities(caps)
        client.p4.repo_files = [
            {
                'depotFile': '//mydepot/test/README',
                'rev': '2',
                'action': 'move/delete',
                'change': '12345',
                'text': 'This is a test.\n',
            },
            {
                'depotFile': '//mydepot/test/README-new',
                'rev': '1',
                'action': 'move/add',
                'change': '12345',
                'text': 'This is a mess.\n',
            },
            {
                'depotFile': '//mydepot/test/COPYING',
                'rev': '2',
                'action': 'move/delete',
                'change': '12345',
                'text': 'Copyright 2013 Joe User.\n',
            },
            {
                'depotFile': '//mydepot/test/COPYING-new',
                'rev': '1',
                'action': 'move/add',
                'change': '12345',
                'text': 'Copyright 2013 Joe User.\n',
            },
        ]

        readme_file = make_tempfile()
        copying_file = make_tempfile()
        readme_file_new = make_tempfile()
        copying_file_new = make_tempfile()
        client.p4.print_file('//mydepot/test/README#2', readme_file)
        client.p4.print_file('//mydepot/test/COPYING#2', copying_file)
        client.p4.print_file('//mydepot/test/README-new#1', readme_file_new)
        client.p4.print_file('//mydepot/test/COPYING-new#1', copying_file_new)

        client.p4.where_files = {
            '//mydepot/test/README': readme_file,
            '//mydepot/test/COPYING': copying_file,
            '//mydepot/test/README-new': readme_file_new,
            '//mydepot/test/COPYING-new': copying_file_new,
        }

        client.p4.fstat_files = {
            '//mydepot/test/README': {
                'clientFile': readme_file,
                'movedFile': '//mydepot/test/README-new',
            },
            '//mydepot/test/README-new': {
                'clientFile': readme_file_new,
                'depotFile': '//mydepot/test/README-new',
            },
            '//mydepot/test/COPYING': {
                'clientFile': copying_file,
                'movedFile': '//mydepot/test/COPYING-new',
            },
            '//mydepot/test/COPYING-new': {
                'clientFile': copying_file_new,
                'depotFile': '//mydepot/test/COPYING-new',
            },
        }

        revisions = client.parse_revision_spec(['12345'])
        diff = client.diff(revisions)
        self._compare_diff(diff, expected_diff_hash)
Example #57
0
    def main(self, review_request_id):
        """Run the command.

        Args:
            review_request_id (int):
                The ID of the review request to patch from.

        Raises:
            rbtools.command.CommandError:
                Patching the tree has failed.
        """
        patch_stdout = self.options.patch_stdout
        revert = self.options.revert_patch

        if patch_stdout and revert:
            raise CommandError(_('--print and --revert cannot both be used.'))

        repository_info, tool = self.initialize_scm_tool(
            client_name=self.options.repository_type,
            require_repository_info=not patch_stdout)

        if revert and not tool.supports_patch_revert:
            raise CommandError(
                _('The %s backend does not support reverting patches.')
                % tool.name)

        server_url = self.get_server_url(repository_info, tool)

        api_client, api_root = self.get_api(server_url)
        self.setup_tool(tool, api_root=api_root)

        if not patch_stdout:
            # Check if the repository info on the Review Board server matches
            # the local checkout.
            repository_info = repository_info.find_server_repository_info(
                api_root)

        # Get the patch, the used patch ID and base dir for the diff
        patch_data = self.get_patch(
            tool,
            api_root,
            review_request_id,
            self.options.diff_revision,
            self.options.commit_id)

        diff_body = patch_data['diff']
        diff_revision = patch_data['revision']
        base_dir = patch_data['base_dir']

        if self.options.patch_stdout:
            if isinstance(diff_body, bytes):
                print(diff_body.decode('utf-8'))
            else:
                print(diff_body)
        else:
            try:
                if tool.has_pending_changes():
                    message = 'Working directory is not clean.'

                    if not self.options.commit:
                        print('Warning: %s' % message)
                    else:
                        raise CommandError(message)
            except NotImplementedError:
                pass

            tmp_patch_file = make_tempfile(diff_body)

            success = self.apply_patch(
                repository_info, tool, review_request_id, diff_revision,
                tmp_patch_file, base_dir, revert=self.options.revert_patch)

            if not success:
                raise CommandError('Could not apply patch')

            if self.options.commit or self.options.commit_no_edit:
                if patch_data['commit_meta'] is not None:
                    # We are patching a commit so we already have the metadata
                    # required without making additional HTTP requests.
                    meta = patch_data['commit_meta']
                    message = meta['message']

                    # Fun fact: object does not have a __dict__ so you cannot
                    # call setattr() on them. We need this ability so we are
                    # creating a type that does.
                    author = type('Author', (object,), {})()
                    author.fullname = meta['author_name']
                    author.email = meta['author_email']

                else:
                    try:
                        review_request = api_root.get_review_request(
                            review_request_id=review_request_id,
                            force_text_type='plain')
                    except APIError as e:
                        raise CommandError('Error getting review request %s: %s'
                                           % (request_id, e))

                    message = extract_commit_message(review_request)
                    author = review_request.get_submitter()

                try:
                    tool.create_commit(message, author,
                                       not self.options.commit_no_edit)
                    print('Changes committed to current branch.')
                except NotImplementedError:
                    raise CommandError('--commit is not supported with %s'
                                       % tool.name)