def create_filediffs(diff_file_contents, parent_diff_file_contents, repository, basedir, base_commit_id, diffset, request=None, check_existence=True, get_file_exists=None, diffcommit=None, validate_only=False): """Create FileDiffs from the given data. Args: diff_file_contents (bytes): The contents of the diff file. parent_diff_file_contents (bytes): The contents of the parent diff file. repository (reviewboard.scmtools.models.Repository): The repository the diff is being posted against. basedir (unicode): The base directory to prepend to all file paths in the diff. base_commit_id (unicode): The ID of the commit that the diff is based upon. This is needed by some SCMs or hosting services to properly look up files, if the diffs represent blob IDs instead of commit IDs and the service doesn't support those lookups. diffset (reviewboard.diffviewer.models.diffset.DiffSet): The DiffSet to attach the created FileDiffs to. request (django.http.HttpRequest, optional): The current HTTP request. check_existence (bool, optional): Whether or not existence checks should be performed against the upstream repository. This argument defaults to ``True``. get_file_exists (callable, optional): A callable that is used to determine if a file exists. This must be provided if ``check_existence`` is ``True``. diffcommit (reviewboard.diffviewer.models.diffcommit.DiffCommit, optional): The DiffCommit to attach the created FileDiffs to. validate_only (bool, optional): Whether to just validate and not save. If ``True``, then this won't populate the database at all and will return ``None`` upon success. This defaults to ``False``. Returns: list of reviewboard.diffviewer.models.filediff.FileDiff: The created FileDiffs. If ``validate_only`` is ``True``, the returned list will be empty. """ from reviewboard.diffviewer.diffutils import convert_to_unicode from reviewboard.diffviewer.models import FileDiff diff_info = _prepare_diff_info( diff_file_contents=diff_file_contents, parent_diff_file_contents=parent_diff_file_contents, repository=repository, request=request, basedir=basedir, check_existence=check_existence, get_file_exists=get_file_exists, base_commit_id=base_commit_id) parent_files = diff_info['parent_files'] parsed_diff = diff_info['parsed_diff'] parsed_parent_diff = diff_info['parsed_parent_diff'] parser = diff_info['parser'] encoding_list = repository.get_encoding_list() # Copy over any extra_data for the DiffSet and DiffCommit, if any were # set by the parser. # # We'll do this even if we're validating, to ensure the data can be # copied over fine. main_extra_data = deepcopy(parsed_diff.extra_data) change_extra_data = deepcopy(parsed_diff.changes[0].extra_data) if change_extra_data: if diffcommit is not None: # We've already checked in _parse_diff that there's only a single # change in the diff, so we can assume that here. diffcommit.extra_data.update(change_extra_data) else: main_extra_data['change_extra_data'] = change_extra_data if main_extra_data: diffset.extra_data.update(main_extra_data) if parsed_parent_diff is not None: parent_extra_data = deepcopy(parsed_parent_diff.extra_data) parent_change_extra_data = deepcopy( parsed_parent_diff.changes[0].extra_data) if parent_change_extra_data: if diffcommit is not None: diffcommit.extra_data['parent_extra_data'] = \ parent_change_extra_data else: parent_extra_data['change_extra_data'] = \ parent_change_extra_data if parent_extra_data: diffset.extra_data['parent_extra_data'] = parent_extra_data # Convert the list of parsed files into FileDiffs. filediffs = [] for f in diff_info['files']: parent_file = None parent_content = b'' extra_data = f.extra_data.copy() if parsed_parent_diff is not None: parent_file = parent_files.get(f.orig_filename) if parent_file is not None: parent_content = parent_file.data # Store the information on the parent's filename and revision. # It's important we force these to text, since they may be # byte strings and the revision may be a Revision instance. parent_source_filename = parent_file.orig_filename parent_source_revision = parent_file.orig_file_details parent_is_empty = (parent_file.insert_count == 0 and parent_file.delete_count == 0) if parent_file.moved or parent_file.copied: extra_data['parent_moved'] = True if parent_file.extra_data: extra_data['parent_extra_data'] = \ parent_file.extra_data.copy() else: # We don't have an entry, but we still want to record the # parent ID, so we have something in common for all the files # when looking up the source revision to fetch from the # repository. parent_is_empty = True parent_source_filename = f.orig_filename parent_source_revision = f.orig_file_details if (parent_source_revision != PRE_CREATION and parsed_diff.uses_commit_ids_as_revisions): # Since the file wasn't explicitly provided in the parent # diff, but the ParsedDiff says that commit IDs are used # as revisions, we can use its parent commit ID as the # parent revision here. parent_commit_id = \ parsed_parent_diff.changes[0].parent_commit_id assert parent_commit_id parent_source_revision = parent_commit_id # Store the information on the parent's filename and revision. # It's important we force these to text, since they may be # byte strings and the revision may be a Revision instance. # # Starting in Review Board 4.0.5, we store this any time there's # a parent diff, whether or not the file existed in the parent # diff. extra_data.update({ FileDiff._IS_PARENT_EMPTY_KEY: parent_is_empty, 'parent_source_filename': convert_to_unicode(parent_source_filename, encoding_list)[1], 'parent_source_revision': convert_to_unicode(parent_source_revision, encoding_list)[1], }) orig_file = convert_to_unicode(f.orig_filename, encoding_list)[1] dest_file = convert_to_unicode(f.modified_filename, encoding_list)[1] if f.deleted: status = FileDiff.DELETED elif f.moved: status = FileDiff.MOVED elif f.copied: status = FileDiff.COPIED else: status = FileDiff.MODIFIED filediff = FileDiff( diffset=diffset, commit=diffcommit, source_file=parser.normalize_diff_filename(orig_file), dest_file=parser.normalize_diff_filename(dest_file), source_revision=force_text(f.orig_file_details), dest_detail=force_text(f.modified_file_details), binary=f.binary, status=status, extra_data=extra_data) # Set this unconditionally, for backwards-compatibility purposes. # Review Board 4.0.6 introduced attribute wrappers in FileDiff and # introduced symlink targets. We ideally would not set this unless # it's True, but we don't want to risk breaking any assumptions on # its presence at this time. filediff.is_symlink = f.is_symlink if f.is_symlink: if f.old_symlink_target: filediff.old_symlink_target = \ convert_to_unicode(f.old_symlink_target, encoding_list)[1] if f.new_symlink_target: filediff.new_symlink_target = \ convert_to_unicode(f.new_symlink_target, encoding_list)[1] filediff.old_unix_mode = f.old_unix_mode filediff.new_unix_mode = f.new_unix_mode if not validate_only: # This state all requires making modifications to the database. # We only want to do this if we're saving. filediff.diff = f.data filediff.parent_diff = parent_content filediff.set_line_counts(raw_insert_count=f.insert_count, raw_delete_count=f.delete_count) filediffs.append(filediff) if not validate_only: FileDiff.objects.bulk_create(filediffs) if diffset.extra_data: diffset.save(update_fields=('extra_data', )) if diffcommit is not None and diffcommit.extra_data: diffcommit.save(update_fields=('extra_data', )) return filediffs