static svn_error_t * close_file(void *file_baton, const char *text_digest, apr_pool_t *pool) { struct file_baton *fb = file_baton; if (text_digest) { svn_checksum_t *checksum; svn_checksum_t *text_checksum; SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, fb->edit_baton->txn_root, fb->path, TRUE, pool)); SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, text_digest, pool)); if (!svn_checksum_match(text_checksum, checksum)) return svn_checksum_mismatch_err(text_checksum, checksum, pool, _("Checksum mismatch for resulting fulltext\n(%s)"), fb->path); } return SVN_NO_ERROR; }
svn_error_t * svn_fs_file_md5_checksum(unsigned char digest[], svn_fs_root_t *root, const char *path, apr_pool_t *pool) { svn_checksum_t *md5sum; SVN_ERR(svn_fs_file_checksum(&md5sum, svn_checksum_md5, root, path, TRUE, pool)); memcpy(digest, md5sum->digest, APR_MD5_DIGESTSIZE); return SVN_NO_ERROR; }
static PyObject *fs_root_file_checksum(FileSystemRootObject *self, PyObject *args) { apr_pool_t *temp_pool; bool force = false; char *path; #if ONLY_SINCE_SVN(1, 6) svn_checksum_kind_t kind; const char *cstr; svn_checksum_t *checksum; #else int kind; unsigned char checksum[APR_MD5_DIGESTSIZE]; #endif PyObject *ret; if (!PyArg_ParseTuple(args, "s|ib", &path, &kind, &force)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_fs_file_checksum( &checksum, kind, self->root, path, force?TRUE:FALSE, temp_pool)); cstr = svn_checksum_to_cstring(checksum, temp_pool); if (cstr == NULL) { ret = Py_None; Py_INCREF(ret); } else { ret = PyUnicode_FromString(cstr); } #else if (kind > 0) { PyErr_SetString(PyExc_ValueError, "Only MD5 checksums allowed with subversion < 1.6"); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_fs_file_md5_checksum(checksum, self->root, path, temp_pool)); ret = PyBytes_FromStringAndSize((char *)checksum, APR_MD5_DIGESTSIZE); #endif apr_pool_destroy(temp_pool); return ret; }
static dav_prop_insert insert_prop_internal(const dav_resource *resource, int propid, dav_prop_insert what, apr_text_header *phdr, apr_pool_t *scratch_pool, apr_pool_t *result_pool) { const char *value = NULL; const char *s; const dav_liveprop_spec *info; int global_ns; svn_error_t *serr; /* ** Almost none of the SVN provider properties are defined if the ** resource does not exist. We do need to return the one VCC ** property and baseline-relative-path on lock-null resources, ** however, so that svn clients can run 'svn unlock' and 'svn info' ** on these things. ** ** Even though we state that the SVN properties are not defined, the ** client cannot store dead values -- we deny that thru the is_writable ** hook function. */ if ((! resource->exists) && (propid != DAV_PROPID_version_controlled_configuration) && (propid != SVN_PROPID_baseline_relative_path)) return DAV_PROP_INSERT_NOTSUPP; /* ### we may want to respond to DAV_PROPID_resourcetype for PRIVATE ### resources. need to think on "proper" interaction with mod_dav */ switch (propid) { case DAV_PROPID_getlastmodified: case DAV_PROPID_creationdate: { /* In subversion terms, the date attached to a file's CR is the true "last modified" time. However, we're defining creationdate in the same way. IMO, the "creationdate" is really the date attached to the revision in which the item *first* came into existence; this would found by tracing back through the log of the file -- probably via svn_fs_revisions_changed. gstein, is it a bad thing that we're currently using 'creationdate' to mean the same thing as 'last modified date'? */ const char *datestring; apr_time_t timeval; enum time_format format; /* ### for now, our global VCC has no such property. */ if (resource->type == DAV_RESOURCE_TYPE_PRIVATE && (resource->info->restype == DAV_SVN_RESTYPE_VCC || resource->info->restype == DAV_SVN_RESTYPE_ME)) { return DAV_PROP_INSERT_NOTSUPP; } if (propid == DAV_PROPID_creationdate) { /* Return an ISO8601 date; this is what the svn client expects, and rfc2518 demands it. */ format = time_format_iso8601; } else /* propid == DAV_PROPID_getlastmodified */ { format = time_format_rfc1123; } if (0 != get_last_modified_time(&datestring, &timeval, resource, format, scratch_pool)) { return DAV_PROP_INSERT_NOTDEF; } value = apr_xml_quote_string(scratch_pool, datestring, 1); break; } case DAV_PROPID_creator_displayname: { svn_revnum_t committed_rev = SVN_INVALID_REVNUM; svn_string_t *last_author = NULL; /* ### for now, our global VCC has no such property. */ if (resource->type == DAV_RESOURCE_TYPE_PRIVATE && (resource->info->restype == DAV_SVN_RESTYPE_VCC || resource->info->restype == DAV_SVN_RESTYPE_ME)) { return DAV_PROP_INSERT_NOTSUPP; } if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION) { /* A baseline URI. */ committed_rev = resource->info->root.rev; } else if (resource->type == DAV_RESOURCE_TYPE_REGULAR || resource->type == DAV_RESOURCE_TYPE_WORKING || resource->type == DAV_RESOURCE_TYPE_VERSION) { /* Get the CR field out of the node's skel. Notice that the root object might be an ID root -or- a revision root. */ serr = svn_fs_node_created_rev(&committed_rev, resource->info->root.root, resource->info->repos_path, scratch_pool); if (serr != NULL) { /* ### what to do? */ svn_error_clear(serr); value = "###error###"; break; } } else { return DAV_PROP_INSERT_NOTSUPP; } serr = get_path_revprop(&last_author, resource, committed_rev, SVN_PROP_REVISION_AUTHOR, scratch_pool); if (serr) { /* ### what to do? */ svn_error_clear(serr); value = "###error###"; break; } if (last_author == NULL) return DAV_PROP_INSERT_NOTDEF; value = apr_xml_quote_string(scratch_pool, last_author->data, 1); break; } case DAV_PROPID_getcontentlanguage: /* ### need something here */ return DAV_PROP_INSERT_NOTSUPP; break; case DAV_PROPID_getcontentlength: { svn_filesize_t len = 0; /* our property, but not defined on collection resources */ if (resource->collection || resource->baselined) return DAV_PROP_INSERT_NOTSUPP; serr = svn_fs_file_length(&len, resource->info->root.root, resource->info->repos_path, scratch_pool); if (serr != NULL) { svn_error_clear(serr); value = "0"; /* ### what to do? */ break; } value = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, len); break; } case DAV_PROPID_getcontenttype: { /* The subversion client assumes that any file without an svn:mime-type property is of type text/plain. So it seems safe (and consistent) to assume the same on the server. */ svn_string_t *pval; const char *mime_type = NULL; if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION) return DAV_PROP_INSERT_NOTSUPP; if (resource->type == DAV_RESOURCE_TYPE_PRIVATE && (resource->info->restype == DAV_SVN_RESTYPE_VCC || resource->info->restype == DAV_SVN_RESTYPE_ME)) { return DAV_PROP_INSERT_NOTSUPP; } if (resource->collection) /* defaults for directories */ { if (resource->info->repos->xslt_uri) mime_type = "text/xml"; else mime_type = "text/html; charset=UTF-8"; } else { if ((serr = svn_fs_node_prop(&pval, resource->info->root.root, resource->info->repos_path, SVN_PROP_MIME_TYPE, scratch_pool))) { svn_error_clear(serr); pval = NULL; } if (pval) mime_type = pval->data; else if ((! resource->info->repos->is_svn_client) && resource->info->r->content_type) mime_type = resource->info->r->content_type; else mime_type = "text/plain"; if ((serr = svn_mime_type_validate(mime_type, scratch_pool))) { /* Probably serr->apr == SVN_ERR_BAD_MIME_TYPE, but there's no point even checking. No matter what the error is, we can't claim to have a mime type for this resource. */ ap_log_rerror(APLOG_MARK, APLOG_WARNING, serr->apr_err, resource->info->r, "%s", serr->message); svn_error_clear(serr); return DAV_PROP_INSERT_NOTDEF; } } value = mime_type; break; } case DAV_PROPID_getetag: if (resource->type == DAV_RESOURCE_TYPE_PRIVATE && (resource->info->restype == DAV_SVN_RESTYPE_VCC || resource->info->restype == DAV_SVN_RESTYPE_ME)) { return DAV_PROP_INSERT_NOTSUPP; } value = dav_svn__getetag(resource, scratch_pool); break; case DAV_PROPID_auto_version: /* we only support one autoversioning behavior, and thus only return this one static value; someday when we support locking, there are other possible values/behaviors for this. */ if (resource->info->repos->autoversioning) value = "DAV:checkout-checkin"; else return DAV_PROP_INSERT_NOTDEF; break; case DAV_PROPID_baseline_collection: /* only defined for Baselines */ /* ### whoops. also defined for a VCC. deal with it later. */ if (resource->type != DAV_RESOURCE_TYPE_VERSION || !resource->baselined) return DAV_PROP_INSERT_NOTSUPP; value = dav_svn__build_uri(resource->info->repos, DAV_SVN__BUILD_URI_BC, resource->info->root.rev, NULL, 1 /* add_href */, scratch_pool); break; case DAV_PROPID_checked_in: /* only defined for VCRs (in the public space and in a BC space) */ /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */ if (resource->type == DAV_RESOURCE_TYPE_PRIVATE && (resource->info->restype == DAV_SVN_RESTYPE_VCC || resource->info->restype == DAV_SVN_RESTYPE_ME)) { svn_revnum_t revnum; serr = svn_fs_youngest_rev(&revnum, resource->info->repos->fs, scratch_pool); if (serr != NULL) { /* ### what to do? */ svn_error_clear(serr); value = "###error###"; break; } s = dav_svn__build_uri(resource->info->repos, DAV_SVN__BUILD_URI_BASELINE, revnum, NULL, 0 /* add_href */, scratch_pool); value = apr_psprintf(scratch_pool, "<D:href>%s</D:href>", apr_xml_quote_string(scratch_pool, s, 1)); } else if (resource->type != DAV_RESOURCE_TYPE_REGULAR) { /* not defined for this resource type */ return DAV_PROP_INSERT_NOTSUPP; } else { svn_revnum_t rev_to_use = dav_svn__get_safe_cr(resource->info->root.root, resource->info->repos_path, scratch_pool); s = dav_svn__build_uri(resource->info->repos, DAV_SVN__BUILD_URI_VERSION, rev_to_use, resource->info->repos_path, 0 /* add_href */, scratch_pool); value = apr_psprintf(scratch_pool, "<D:href>%s</D:href>", apr_xml_quote_string(scratch_pool, s, 1)); } break; case DAV_PROPID_version_controlled_configuration: /* only defined for VCRs */ /* ### VCRs within the BC should not have this property! */ /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */ if (resource->type != DAV_RESOURCE_TYPE_REGULAR) return DAV_PROP_INSERT_NOTSUPP; value = dav_svn__build_uri(resource->info->repos, DAV_SVN__BUILD_URI_VCC, SVN_IGNORED_REVNUM, NULL, 1 /* add_href */, scratch_pool); break; case DAV_PROPID_version_name: /* only defined for Version Resources and Baselines */ /* ### whoops. also defined for VCRs. deal with it later. */ if ((resource->type != DAV_RESOURCE_TYPE_VERSION) && (! resource->versioned)) return DAV_PROP_INSERT_NOTSUPP; if (resource->type == DAV_RESOURCE_TYPE_PRIVATE && (resource->info->restype == DAV_SVN_RESTYPE_VCC || resource->info->restype == DAV_SVN_RESTYPE_ME)) { return DAV_PROP_INSERT_NOTSUPP; } if (resource->baselined) { /* just the revision number for baselines */ value = apr_psprintf(scratch_pool, "%ld", resource->info->root.rev); } else { svn_revnum_t committed_rev = SVN_INVALID_REVNUM; /* Get the CR field out of the node's skel. Notice that the root object might be an ID root -or- a revision root. */ serr = svn_fs_node_created_rev(&committed_rev, resource->info->root.root, resource->info->repos_path, scratch_pool); if (serr != NULL) { /* ### what to do? */ svn_error_clear(serr); value = "###error###"; break; } /* Convert the revision into a quoted string */ s = apr_psprintf(scratch_pool, "%ld", committed_rev); value = apr_xml_quote_string(scratch_pool, s, 1); } break; case SVN_PROPID_baseline_relative_path: /* only defined for VCRs */ /* ### VCRs within the BC should not have this property! */ /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */ if (resource->type != DAV_RESOURCE_TYPE_REGULAR) return DAV_PROP_INSERT_NOTSUPP; /* drop the leading slash, so it is relative */ s = resource->info->repos_path + 1; value = apr_xml_quote_string(scratch_pool, s, 1); break; case SVN_PROPID_md5_checksum: if ((! resource->collection) && (! resource->baselined) && (resource->type == DAV_RESOURCE_TYPE_REGULAR || resource->type == DAV_RESOURCE_TYPE_WORKING || resource->type == DAV_RESOURCE_TYPE_VERSION)) { svn_checksum_t *checksum; serr = svn_fs_file_checksum(&checksum, svn_checksum_md5, resource->info->root.root, resource->info->repos_path, TRUE, scratch_pool); if (serr != NULL) { /* ### what to do? */ svn_error_clear(serr); value = "###error###"; break; } value = svn_checksum_to_cstring(checksum, scratch_pool); if (! value) return DAV_PROP_INSERT_NOTSUPP; } else return DAV_PROP_INSERT_NOTSUPP; break; case SVN_PROPID_repository_uuid: serr = svn_fs_get_uuid(resource->info->repos->fs, &value, scratch_pool); if (serr != NULL) { /* ### what to do? */ svn_error_clear(serr); value = "###error###"; break; } break; case SVN_PROPID_deadprop_count: { unsigned int propcount; apr_hash_t *proplist; if (resource->type != DAV_RESOURCE_TYPE_REGULAR) return DAV_PROP_INSERT_NOTSUPP; serr = svn_fs_node_proplist(&proplist, resource->info->root.root, resource->info->repos_path, scratch_pool); if (serr != NULL) { /* ### what to do? */ svn_error_clear(serr); value = "###error###"; break; } propcount = apr_hash_count(proplist); value = apr_psprintf(scratch_pool, "%u", propcount); break; } default: /* ### what the heck was this property? */ return DAV_PROP_INSERT_NOTDEF; } /* assert: value != NULL */ /* get the information and global NS index for the property */ global_ns = dav_get_liveprop_info(propid, &dav_svn__liveprop_group, &info); /* assert: info != NULL && info->name != NULL */ if (what == DAV_PROP_INSERT_NAME || (what == DAV_PROP_INSERT_VALUE && *value == '\0')) { s = apr_psprintf(result_pool, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name); } else if (what == DAV_PROP_INSERT_VALUE) { s = apr_psprintf(result_pool, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR, global_ns, info->name, value, global_ns, info->name); } else { /* assert: what == DAV_PROP_INSERT_SUPPORTED */ s = apr_psprintf(result_pool, "<D:supported-live-property D:name=\"%s\" " "D:namespace=\"%s\"/>" DEBUG_CR, info->name, namespace_uris[info->ns]); } apr_text_append(result_pool, phdr, s); /* we inserted whatever was asked for */ return what; }
/* Perform a copy or a plain add. * * For a copy, also adjust the copy-from rev, check any copy-source checksum, * and send a notification. */ static svn_error_t * maybe_add_with_history(struct node_baton *nb, struct revision_baton *rb, apr_pool_t *pool) { struct parse_baton *pb = rb->pb; if ((nb->copyfrom_path == NULL) || (! pb->use_history)) { /* Add empty file or dir, without history. */ if (nb->kind == svn_node_file) SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool)); else if (nb->kind == svn_node_dir) SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool)); } else { /* Hunt down the source revision in this fs. */ svn_fs_root_t *copy_root; svn_revnum_t copyfrom_rev; /* Try to find the copyfrom revision in the revision map; failing that, fall back to the revision offset approach. */ copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev); if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) copyfrom_rev = nb->copyfrom_rev - rb->rev_offset; if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, _("Relative source revision %ld is not" " available in current repository"), copyfrom_rev); SVN_ERR(svn_fs_revision_root(©_root, pb->fs, copyfrom_rev, pool)); if (nb->copy_source_checksum) { svn_checksum_t *checksum; SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root, nb->copyfrom_path, TRUE, pool)); if (!svn_checksum_match(nb->copy_source_checksum, checksum)) return svn_checksum_mismatch_err(nb->copy_source_checksum, checksum, pool, _("Copy source checksum mismatch on copy from '%s'@%ld\n" "to '%s' in rev based on r%ld"), nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev); } SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path, rb->txn_root, nb->path, pool)); if (pb->notify_func) { /* ### TODO: Use proper scratch pool instead of pb->notify_pool */ svn_repos_notify_t *notify = svn_repos_notify_create( svn_repos_notify_load_copied_node, pb->notify_pool); pb->notify_func(pb->notify_baton, notify, pb->notify_pool); svn_pool_clear(pb->notify_pool); } } return SVN_NO_ERROR; }
/* This helper is the main "meat" of the editor -- it does all the work of writing a node record. Write out a node record for PATH of type KIND under EB->FS_ROOT. ACTION describes what is happening to the node (see enum svn_node_action). Write record to writable EB->STREAM, using EB->BUFFER to write in chunks. If the node was itself copied, IS_COPY is TRUE and the path/revision of the copy source are in CMP_PATH/CMP_REV. If IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part of a copied subtree. */ static svn_error_t * dump_node(struct edit_baton *eb, const char *path, svn_node_kind_t kind, enum svn_node_action action, svn_boolean_t is_copy, const char *cmp_path, svn_revnum_t cmp_rev, apr_pool_t *pool) { svn_stringbuf_t *propstring; svn_filesize_t content_length = 0; apr_size_t len; svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE; const char *compare_path = path; svn_revnum_t compare_rev = eb->current_rev - 1; svn_fs_root_t *compare_root = NULL; apr_file_t *delta_file = NULL; /* Maybe validate the path. */ if (eb->verify || eb->notify_func) { svn_error_t *err = svn_fs__path_valid(path, pool); if (err) { if (eb->notify_func) { char errbuf[512]; /* ### svn_strerror() magic number */ svn_repos_notify_t *notify; notify = svn_repos_notify_create(svn_repos_notify_warning, pool); notify->warning = svn_repos_notify_warning_invalid_fspath; notify->warning_str = apr_psprintf( pool, _("E%06d: While validating fspath '%s': %s"), err->apr_err, path, svn_err_best_message(err, errbuf, sizeof(errbuf))); eb->notify_func(eb->notify_baton, notify, pool); } /* Return the error in addition to notifying about it. */ if (eb->verify) return svn_error_trace(err); else svn_error_clear(err); } } /* Write out metadata headers for this file node. */ SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", path)); if (kind == svn_node_file) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_KIND ": file\n")); else if (kind == svn_node_dir) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n")); /* Remove leading slashes from copyfrom paths. */ if (cmp_path) cmp_path = svn_relpath_canonicalize(cmp_path, pool); /* Validate the comparison path/rev. */ if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev)) { compare_path = cmp_path; compare_rev = cmp_rev; } if (action == svn_node_action_change) { SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n")); /* either the text or props changed, or possibly both. */ SVN_ERR(svn_fs_revision_root(&compare_root, svn_fs_root_fs(eb->fs_root), compare_rev, pool)); SVN_ERR(svn_fs_props_changed(&must_dump_props, compare_root, compare_path, eb->fs_root, path, pool)); if (kind == svn_node_file) SVN_ERR(svn_fs_contents_changed(&must_dump_text, compare_root, compare_path, eb->fs_root, path, pool)); } else if (action == svn_node_action_replace) { if (! is_copy) { /* a simple delete+add, implied by a single 'replace' action. */ SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_ACTION ": replace\n")); /* definitely need to dump all content for a replace. */ if (kind == svn_node_file) must_dump_text = TRUE; must_dump_props = TRUE; } else { /* more complex: delete original, then add-with-history. */ /* the path & kind headers have already been printed; just add a delete action, and end the current record.*/ SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n\n")); /* recurse: print an additional add-with-history record. */ SVN_ERR(dump_node(eb, path, kind, svn_node_action_add, is_copy, compare_path, compare_rev, pool)); /* we can leave this routine quietly now, don't need to dump any content; that was already done in the second record. */ must_dump_text = FALSE; must_dump_props = FALSE; } } else if (action == svn_node_action_delete) { SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n")); /* we can leave this routine quietly now, don't need to dump any content. */ must_dump_text = FALSE; must_dump_props = FALSE; } else if (action == svn_node_action_add) { SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); if (! is_copy) { /* Dump all contents for a simple 'add'. */ if (kind == svn_node_file) must_dump_text = TRUE; must_dump_props = TRUE; } else { if (!eb->verify && cmp_rev < eb->oldest_dumped_rev && eb->notify_func) { svn_repos_notify_t *notify = svn_repos_notify_create(svn_repos_notify_warning, pool); notify->warning = svn_repos_notify_warning_found_old_reference; notify->warning_str = apr_psprintf( pool, _("Referencing data in revision %ld," " which is older than the oldest" " dumped revision (r%ld). Loading this dump" " into an empty repository" " will fail."), cmp_rev, eb->oldest_dumped_rev); eb->found_old_reference = TRUE; eb->notify_func(eb->notify_baton, notify, pool); } SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV ": %ld\n" SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH ": %s\n", cmp_rev, cmp_path)); SVN_ERR(svn_fs_revision_root(&compare_root, svn_fs_root_fs(eb->fs_root), compare_rev, pool)); /* Need to decide if the copied node had any extra textual or property mods as well. */ SVN_ERR(svn_fs_props_changed(&must_dump_props, compare_root, compare_path, eb->fs_root, path, pool)); if (kind == svn_node_file) { svn_checksum_t *checksum; const char *hex_digest; SVN_ERR(svn_fs_contents_changed(&must_dump_text, compare_root, compare_path, eb->fs_root, path, pool)); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, compare_root, compare_path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5 ": %s\n", hex_digest)); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, compare_root, compare_path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1 ": %s\n", hex_digest)); } } } if ((! must_dump_text) && (! must_dump_props)) { /* If we're not supposed to dump text or props, so be it, we can just go home. However, if either one needs to be dumped, then our dumpstream format demands that at a *minimum*, we see a lone "PROPS-END" as a divider between text and props content within the content-block. */ len = 2; return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ } /*** Start prepping content to dump... ***/ /* If we are supposed to dump properties, write out a property length header and generate a stringbuf that contains those property values here. */ if (must_dump_props) { apr_hash_t *prophash, *oldhash = NULL; apr_size_t proplen; svn_stream_t *propstream; SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool)); /* If this is a partial dump, then issue a warning if we dump mergeinfo properties that refer to revisions older than the first revision dumped. */ if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1) { svn_string_t *mergeinfo_str = apr_hash_get(prophash, SVN_PROP_MERGEINFO, APR_HASH_KEY_STRING); if (mergeinfo_str) { svn_mergeinfo_t mergeinfo, old_mergeinfo; SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data, pool)); SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( &old_mergeinfo, mergeinfo, eb->oldest_dumped_rev - 1, 0, TRUE, pool, pool)); if (apr_hash_count(old_mergeinfo)) { svn_repos_notify_t *notify = svn_repos_notify_create(svn_repos_notify_warning, pool); notify->warning = svn_repos_notify_warning_found_old_mergeinfo; notify->warning_str = apr_psprintf( pool, _("Mergeinfo referencing revision(s) prior " "to the oldest dumped revision (r%ld). " "Loading this dump may result in invalid " "mergeinfo."), eb->oldest_dumped_rev); eb->found_old_mergeinfo = TRUE; eb->notify_func(eb->notify_baton, notify, pool); } } } if (eb->use_deltas && compare_root) { /* Fetch the old property hash to diff against and output a header saying that our property contents are a delta. */ SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path, pool)); SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n")); } else oldhash = apr_hash_make(pool); propstring = svn_stringbuf_create_ensure(0, pool); propstream = svn_stream_from_stringbuf(propstring, pool); SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream, "PROPS-END", pool)); SVN_ERR(svn_stream_close(propstream)); proplen = propstring->len; content_length += proplen; SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH ": %" APR_SIZE_T_FMT "\n", proplen)); } /* If we are supposed to dump text, write out a text length header here, and an MD5 checksum (if available). */ if (must_dump_text && (kind == svn_node_file)) { svn_checksum_t *checksum; const char *hex_digest; svn_filesize_t textlen; if (eb->use_deltas) { /* Compute the text delta now and write it into a temporary file, so that we can find its length. Output a header saying our text contents are a delta. */ SVN_ERR(store_delta(&delta_file, &textlen, compare_root, compare_path, eb->fs_root, path, pool)); SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n")); if (compare_root) { SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, compare_root, compare_path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 ": %s\n", hex_digest)); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, compare_root, compare_path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1 ": %s\n", hex_digest)); } } else { /* Just fetch the length of the file. */ SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool)); } content_length += textlen; SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH ": %" SVN_FILESIZE_T_FMT "\n", textlen)); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, eb->fs_root, path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 ": %s\n", hex_digest)); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, eb->fs_root, path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1 ": %s\n", hex_digest)); } /* 'Content-length:' is the last header before we dump the content, and is the sum of the text and prop contents lengths. We write this only for the benefit of non-Subversion RFC-822 parsers. */ SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH ": %" SVN_FILESIZE_T_FMT "\n\n", content_length)); /* Dump property content if we're supposed to do so. */ if (must_dump_props) { len = propstring->len; SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len)); } /* Dump text content */ if (must_dump_text && (kind == svn_node_file)) { svn_stream_t *contents; if (delta_file) { /* Make sure to close the underlying file when the stream is closed. */ contents = svn_stream_from_aprfile2(delta_file, FALSE, pool); } else SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool)); SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool), NULL, NULL, pool)); } len = 2; return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ }
/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting the appropriate editor calls to add it and its children without any history. This is meant to be used when either a subset of the tree has been ignored and we need to copy something from that subset to the part of the tree we do care about, or if a subset of the tree is unavailable because of authz and we need to use it as the source of a copy. */ static svn_error_t * add_subdir(svn_fs_root_t *source_root, svn_fs_root_t *target_root, const svn_delta_editor_t *editor, void *edit_baton, const char *edit_path, void *parent_baton, const char *source_fspath, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_hash_t *changed_paths, apr_pool_t *pool, void **dir_baton) { apr_pool_t *subpool = svn_pool_create(pool); apr_hash_index_t *hi, *phi; apr_hash_t *dirents; apr_hash_t *props; SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL, SVN_INVALID_REVNUM, pool, dir_baton)); SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool)); for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi)) { const void *key; void *val; svn_pool_clear(subpool); apr_hash_this(phi, &key, NULL, &val); SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool)); } /* We have to get the dirents from the source path, not the target, because we want nested copies from *readable* paths to be handled by path_driver_cb_func, not add_subdir (in order to preserve history). */ SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath, pool)); for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) { svn_fs_path_change2_t *change; svn_boolean_t readable = TRUE; svn_fs_dirent_t *dent; const char *copyfrom_path = NULL; svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; const char *new_edit_path; void *val; svn_pool_clear(subpool); apr_hash_this(hi, NULL, NULL, &val); dent = val; new_edit_path = svn_relpath_join(edit_path, dent->name, subpool); /* If a file or subdirectory of the copied directory is listed as a changed path (because it was modified after the copy but before the commit), we remove it from the changed_paths hash so that future calls to path_driver_cb_func will ignore it. */ change = apr_hash_get(changed_paths, new_edit_path, APR_HASH_KEY_STRING); if (change) { apr_hash_set(changed_paths, new_edit_path, APR_HASH_KEY_STRING, NULL); /* If it's a delete, skip this entry. */ if (change->change_kind == svn_fs_path_change_delete) continue; /* If it's a replacement, check for copyfrom info (if we don't have it already. */ if (change->change_kind == svn_fs_path_change_replace) { if (! change->copyfrom_known) { SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev, &change->copyfrom_path, target_root, new_edit_path, pool)); change->copyfrom_known = TRUE; } copyfrom_path = change->copyfrom_path; copyfrom_rev = change->copyfrom_rev; } } if (authz_read_func) SVN_ERR(authz_read_func(&readable, target_root, new_edit_path, authz_read_baton, pool)); if (! readable) continue; if (dent->kind == svn_node_dir) { svn_fs_root_t *new_source_root; const char *new_source_fspath; void *new_dir_baton; if (copyfrom_path) { svn_fs_t *fs = svn_fs_root_fs(source_root); SVN_ERR(svn_fs_revision_root(&new_source_root, fs, copyfrom_rev, pool)); new_source_fspath = copyfrom_path; } else { new_source_root = source_root; new_source_fspath = svn_fspath__join(source_fspath, dent->name, subpool); } /* ### authz considerations? * * I think not; when path_driver_cb_func() calls add_subdir(), it * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable. */ if (change && change->change_kind == svn_fs_path_change_replace && copyfrom_path == NULL) { SVN_ERR(editor->add_directory(new_edit_path, *dir_baton, NULL, SVN_INVALID_REVNUM, subpool, &new_dir_baton)); } else { SVN_ERR(add_subdir(new_source_root, target_root, editor, edit_baton, new_edit_path, *dir_baton, new_source_fspath, authz_read_func, authz_read_baton, changed_paths, subpool, &new_dir_baton)); } SVN_ERR(editor->close_directory(new_dir_baton, subpool)); } else if (dent->kind == svn_node_file) { svn_txdelta_window_handler_t delta_handler; void *delta_handler_baton, *file_baton; svn_txdelta_stream_t *delta_stream; svn_checksum_t *checksum; SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL, SVN_INVALID_REVNUM, pool, &file_baton)); SVN_ERR(svn_fs_node_proplist(&props, target_root, new_edit_path, subpool)); for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi)) { const void *key; apr_hash_this(phi, &key, NULL, &val); SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool)); } SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool, &delta_handler, &delta_handler_baton)); SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL, target_root, new_edit_path, pool)); SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, delta_handler_baton, pool)); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root, new_edit_path, TRUE, pool)); SVN_ERR(editor->close_file(file_baton, svn_checksum_to_cstring(checksum, pool), pool)); } else SVN_ERR_MALFUNCTION(); } svn_pool_destroy(subpool); return SVN_NO_ERROR; }