static svn_error_t * end_element(void *baton, int state, const char *nspace, const char *elt_name) { struct mergeinfo_baton *mb = baton; const svn_ra_neon__xml_elm_t *elm = svn_ra_neon__lookup_xml_elem(mergeinfo_report_elements, nspace, elt_name); if (! elm) return UNEXPECTED_ELEMENT(nspace, elt_name); if (elm->id == ELEM_mergeinfo_item) { if (mb->curr_info && mb->curr_path) { svn_mergeinfo_t path_mergeinfo; const char *path; SVN_ERR_ASSERT(mb->curr_path->data); path = apr_pstrdup(mb->pool, mb->curr_path->data); SVN_ERR((mb->err = svn_mergeinfo_parse(&path_mergeinfo, mb->curr_info->data, mb->pool))); /* Correct for naughty servers that send "relative" paths with leading slashes! */ apr_hash_set(mb->catalog, path[0] == '/' ? path + 1 : path, APR_HASH_KEY_STRING, path_mergeinfo); } } return SVN_NO_ERROR; }
/* ### FIXME: Consider somehow sharing code with ### libsvn_repos/load-fs-vtable.c:prefix_mergeinfo_paths() */ static svn_error_t * prefix_mergeinfo_paths(svn_string_t **mergeinfo_val, const svn_string_t *mergeinfo_orig, const char *parent_dir, apr_pool_t *pool) { apr_hash_t *prefixed_mergeinfo, *mergeinfo; apr_hash_index_t *hi; void *rangelist; SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool)); prefixed_mergeinfo = apr_hash_make(pool); for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) { const void *key; const char *path, *merge_source; apr_hash_this(hi, &key, NULL, &rangelist); merge_source = svn_relpath_canonicalize(key, pool); /* The svn:mergeinfo property syntax demands a repos abspath */ path = svn_fspath__canonicalize(svn_relpath_join(parent_dir, merge_source, pool), pool); svn_hash_sets(prefixed_mergeinfo, path, rangelist); } return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool); }
/* Verify the mergeinfo property value VALUE and return an error if it * is invalid. The PATH on which that property is set is used for error * messages only. Use SCRATCH_POOL for temporary allocations. */ static svn_error_t * verify_mergeinfo(const svn_string_t *value, const char *path, apr_pool_t *scratch_pool) { svn_error_t *err; svn_mergeinfo_t mergeinfo; /* It's okay to delete svn:mergeinfo. */ if (value == NULL) return SVN_NO_ERROR; /* Mergeinfo is UTF-8 encoded so the number of bytes returned by strlen() * should match VALUE->LEN. Prevents trailing garbage in the property. */ if (strlen(value->data) != value->len) return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, _("Commit rejected because mergeinfo on '%s' " "contains unexpected string terminator"), path); err = svn_mergeinfo_parse(&mergeinfo, value->data, scratch_pool); if (err) return svn_error_createf(err->apr_err, err, _("Commit rejected because mergeinfo on '%s' " "is syntactically invalid"), path); return SVN_NO_ERROR; }
/* Conforms to svn_ra_serf__xml_closed_t */ static svn_error_t * mergeinfo_closed(svn_ra_serf__xml_estate_t *xes, void *baton, int leaving_state, const svn_string_t *cdata, apr_hash_t *attrs, apr_pool_t *scratch_pool) { mergeinfo_context_t *mergeinfo_ctx = baton; if (leaving_state == MERGEINFO_ITEM) { /* Placed here from the child elements. */ const char *path = apr_hash_get(attrs, "path", APR_HASH_KEY_STRING); const char *info = apr_hash_get(attrs, "info", APR_HASH_KEY_STRING); if (path != NULL && info != NULL) { svn_mergeinfo_t path_mergeinfo; /* Correct for naughty servers that send "relative" paths with leading slashes! */ if (path[0] == '/') ++path; SVN_ERR(svn_mergeinfo_parse(&path_mergeinfo, info, mergeinfo_ctx->pool)); apr_hash_set(mergeinfo_ctx->result_catalog, apr_pstrdup(mergeinfo_ctx->pool, path), APR_HASH_KEY_STRING, path_mergeinfo); } } else { SVN_ERR_ASSERT(leaving_state == MERGEINFO_PATH || leaving_state == MERGEINFO_INFO); /* Stash the value onto the parent MERGEINFO_ITEM. */ svn_ra_serf__xml_note(xes, MERGEINFO_ITEM, leaving_state == MERGEINFO_PATH ? "path" : "info", cdata->data); } return SVN_NO_ERROR; }
static svn_error_t * end_element(svn_ra_serf__xml_parser_t *parser, void *userData, svn_ra_serf__dav_props_t name) { mergeinfo_context_t *mergeinfo_ctx = userData; mergeinfo_state_e state; state = parser->state->current_state; if (state == MERGEINFO_REPORT && strcmp(name.name, SVN_DAV__MERGEINFO_REPORT) == 0) { svn_ra_serf__xml_pop_state(parser); } else if (state == MERGEINFO_ITEM && strcmp(name.name, SVN_DAV__MERGEINFO_ITEM) == 0) { if (mergeinfo_ctx->curr_info && mergeinfo_ctx->curr_path) { svn_mergeinfo_t path_mergeinfo; const char *path; SVN_ERR_ASSERT(mergeinfo_ctx->curr_path->data); path = apr_pstrdup(mergeinfo_ctx->pool, mergeinfo_ctx->curr_path->data); SVN_ERR(svn_mergeinfo_parse(&path_mergeinfo, mergeinfo_ctx->curr_info->data, mergeinfo_ctx->pool)); /* Correct for naughty servers that send "relative" paths with leading slashes! */ apr_hash_set(mergeinfo_ctx->result_catalog, path[0] == '/' ? path + 1 : path, APR_HASH_KEY_STRING, path_mergeinfo); } svn_ra_serf__xml_pop_state(parser); } else if (state == MERGEINFO_PATH && strcmp(name.name, SVN_DAV__MERGEINFO_PATH) == 0) { svn_ra_serf__xml_pop_state(parser); } else if (state == MERGEINFO_INFO && strcmp(name.name, SVN_DAV__MERGEINFO_INFO) == 0) { svn_ra_serf__xml_pop_state(parser); } return SVN_NO_ERROR; }
static svn_error_t * test_elide_mergeinfo_catalog(apr_pool_t *pool) { int i; apr_pool_t *iterpool; iterpool = svn_pool_create(pool); for (i = 0; i < sizeof(elide_testcases) / sizeof(elide_testcases[0]); i++) { apr_hash_t *catalog; mergeinfo_catalog_item *item; svn_pool_clear(iterpool); catalog = apr_hash_make(iterpool); for (item = elide_testcases[i]; item->path; item++) { apr_hash_t *mergeinfo; SVN_ERR(svn_mergeinfo_parse(&mergeinfo, item->unparsed_mergeinfo, iterpool)); apr_hash_set(catalog, item->path, APR_HASH_KEY_STRING, mergeinfo); } SVN_ERR(svn_client__elide_mergeinfo_catalog(catalog, iterpool)); for (item = elide_testcases[i]; item->path; item++) { apr_hash_t *mergeinfo = apr_hash_get(catalog, item->path, APR_HASH_KEY_STRING); if (item->remains && !mergeinfo) return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, "Elision for test case #%d incorrectly " "elided '%s'", i, item->path); if (!item->remains && mergeinfo) return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, "Elision for test case #%d failed to " "elide '%s'", i, item->path); } } svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
/* ### FIXME: Consider somehow sharing code with ### libsvn_repos/load-fs-vtable.c:renumber_mergeinfo_revs() */ static svn_error_t * renumber_mergeinfo_revs(svn_string_t **final_val, const svn_string_t *initial_val, struct revision_baton *rb, apr_pool_t *pool) { apr_pool_t *subpool = svn_pool_create(pool); svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo; svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool); apr_hash_index_t *hi; SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool)); /* Issue #3020 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16 Remove mergeinfo older than the oldest revision in the dump stream and adjust its revisions by the difference between the head rev of the target repository and the current dump stream rev. */ if (rb->pb->oldest_dumpstream_rev > 1) { SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( &predates_stream_mergeinfo, mergeinfo, rb->pb->oldest_dumpstream_rev - 1, 0, TRUE, subpool, subpool)); SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( &mergeinfo, mergeinfo, rb->pb->oldest_dumpstream_rev - 1, 0, FALSE, subpool, subpool)); SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists( &predates_stream_mergeinfo, predates_stream_mergeinfo, -rb->rev_offset, subpool, subpool)); } else { predates_stream_mergeinfo = NULL; } for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi)) { svn_rangelist_t *rangelist; struct parse_baton *pb = rb->pb; int i; const void *path; apr_ssize_t pathlen; void *val; apr_hash_this(hi, &path, &pathlen, &val); rangelist = val; /* Possibly renumber revisions in merge source's rangelist. */ for (i = 0; i < rangelist->nelts; i++) { svn_revnum_t rev_from_map; svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); rev_from_map = get_revision_mapping(pb->rev_map, range->start); if (SVN_IS_VALID_REVNUM(rev_from_map)) { range->start = rev_from_map; } else if (range->start == pb->oldest_dumpstream_rev - 1) { /* Since the start revision of svn_merge_range_t are not inclusive there is one possible valid start revision that won't be found in the PB->REV_MAP mapping of load stream revsions to loaded revisions: The revision immediately preceeding the oldest revision from the load stream. This is a valid revision for mergeinfo, but not a valid copy from revision (which PB->REV_MAP also maps for) so it will never be in the mapping. If that is what we have here, then find the mapping for the oldest rev from the load stream and subtract 1 to get the renumbered, non-inclusive, start revision. */ rev_from_map = get_revision_mapping(pb->rev_map, pb->oldest_dumpstream_rev); if (SVN_IS_VALID_REVNUM(rev_from_map)) range->start = rev_from_map - 1; } else { /* If we can't remap the start revision then don't even bother trying to remap the end revision. It's possible we might actually succeed at the latter, which can result in invalid mergeinfo with a start rev > end rev. If that gets into the repository then a world of bustage breaks loose anytime that bogus mergeinfo is parsed. See http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16. */ continue; } rev_from_map = get_revision_mapping(pb->rev_map, range->end); if (SVN_IS_VALID_REVNUM(rev_from_map)) range->end = rev_from_map; } apr_hash_set(final_mergeinfo, path, pathlen, rangelist); } if (predates_stream_mergeinfo) { SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo, subpool, subpool)); } SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool)); SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool)); svn_pool_destroy(subpool); return SVN_NO_ERROR; }
static svn_error_t * change_dir_prop(void *dir_baton, const char *name, const svn_string_t *value, apr_pool_t *pool) { node_baton_t *db = dir_baton; edit_baton_t *eb = db->edit_baton; /* Only regular properties can pass over libsvn_ra */ if (svn_property_kind2(name) != svn_prop_regular_kind) return SVN_NO_ERROR; /* Maybe drop svn:mergeinfo. */ if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0)) { eb->mergeinfo_stripped = TRUE; return SVN_NO_ERROR; } /* Maybe convert svnmerge-integrated data into svn:mergeinfo. (We ignore svnmerge-blocked for now.) */ /* ### FIXME: Consult the mirror repository's HEAD prop values and ### merge svn:mergeinfo, svnmerge-integrated, and svnmerge-blocked. */ if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0)) { if (value) { /* svnmerge-integrated differs from svn:mergeinfo in a pair of ways. First, it can use tabs, newlines, or spaces to delimit source information. Secondly, the source paths are relative URLs, whereas svn:mergeinfo uses relative paths (not URI-encoded). */ svn_error_t *err; svn_stringbuf_t *mergeinfo_buf = svn_stringbuf_create_empty(pool); svn_mergeinfo_t mergeinfo; int i; apr_array_header_t *sources = svn_cstring_split(value->data, " \t\n", TRUE, pool); svn_string_t *new_value; for (i = 0; i < sources->nelts; i++) { const char *rel_path; apr_array_header_t *path_revs = svn_cstring_split(APR_ARRAY_IDX(sources, i, const char *), ":", TRUE, pool); /* ### TODO: Warn? */ if (path_revs->nelts != 2) continue; /* Append this source's mergeinfo data. */ rel_path = APR_ARRAY_IDX(path_revs, 0, const char *); rel_path = svn_path_uri_decode(rel_path, pool); svn_stringbuf_appendcstr(mergeinfo_buf, rel_path); svn_stringbuf_appendcstr(mergeinfo_buf, ":"); svn_stringbuf_appendcstr(mergeinfo_buf, APR_ARRAY_IDX(path_revs, 1, const char *)); svn_stringbuf_appendcstr(mergeinfo_buf, "\n"); } /* Try to parse the mergeinfo string we've created, just to check for bogosity. If all goes well, we'll unparse it again and use that as our property value. */ err = svn_mergeinfo_parse(&mergeinfo, mergeinfo_buf->data, pool); if (err) { svn_error_clear(err); return SVN_NO_ERROR; } SVN_ERR(svn_mergeinfo_to_string(&new_value, mergeinfo, pool)); value = new_value; } name = SVN_PROP_MERGEINFO; eb->svnmerge_migrated = TRUE; } /* Remember if we see any svnmerge-blocked properties. */ if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0)) { eb->svnmerge_blocked = TRUE; } /* Normalize svn:* properties as necessary. */ if (svn_prop_needs_translation(name)) { svn_boolean_t was_normalized; svn_boolean_t mergeinfo_tweaked = FALSE; /* Normalize encoding to UTF-8, and EOL style to LF. */ SVN_ERR(normalize_string(&value, &was_normalized, eb->source_prop_encoding, pool, pool)); /* Maybe adjust svn:mergeinfo. */ if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0) { SVN_ERR(remove_r0_mergeinfo(&value, &mergeinfo_tweaked, pool, pool)); if (mergeinfo_tweaked) eb->mergeinfo_tweaked = TRUE; } if (was_normalized) (*(eb->normalized_node_props_counter))++; } return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton, name, value, pool); }
/* 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? */ }
/* A helper function to parse svn:mergeinfo diffs. * * These diffs use a special pretty-print format, for instance: * * Added: svn:mergeinfo * ## -0,0 +0,1 ## * Merged /trunk:r2-3 * * The hunk header has the following format: * ## -0,NUMBER_OF_REVERSE_MERGES +0,NUMBER_OF_FORWARD_MERGES ## * * At this point, the number of reverse merges has already been * parsed into HUNK->ORIGINAL_LENGTH, and the number of forward * merges has been parsed into HUNK->MODIFIED_LENGTH. * * The header is followed by a list of mergeinfo, one path per line. * This function parses such lines. Lines describing reverse merges * appear first, and then all lines describing forward merges appear. * * Parts of the line are affected by i18n. The words 'Merged' * and 'Reverse-merged' can appear in any language and at any * position within the line. We can only assume that a leading * '/' starts the merge source path, the path is followed by * ":r", which in turn is followed by a mergeinfo revision range, * which is terminated by whitespace or end-of-string. * * If the current line meets the above criteria and we're able * to parse valid mergeinfo from it, the resulting mergeinfo * is added to patch->mergeinfo or patch->reverse_mergeinfo, * and we proceed to the next line. */ static svn_error_t * parse_mergeinfo(svn_boolean_t *found_mergeinfo, svn_stringbuf_t *line, svn_diff_hunk_t *hunk, svn_patch_t *patch, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { char *slash = strchr(line->data, '/'); char *colon = strrchr(line->data, ':'); *found_mergeinfo = FALSE; if (slash && colon && colon[1] == 'r' && slash < colon) { svn_stringbuf_t *input; svn_mergeinfo_t mergeinfo = NULL; char *s; svn_error_t *err; input = svn_stringbuf_create_ensure(line->len, scratch_pool); /* Copy the merge source path + colon */ s = slash; while (s <= colon) { svn_stringbuf_appendbyte(input, *s); s++; } /* skip 'r' after colon */ s++; /* Copy the revision range. */ while (s < line->data + line->len) { if (svn_ctype_isspace(*s)) break; svn_stringbuf_appendbyte(input, *s); s++; } err = svn_mergeinfo_parse(&mergeinfo, input->data, result_pool); if (err && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) { svn_error_clear(err); mergeinfo = NULL; } else SVN_ERR(err); if (mergeinfo) { if (hunk->original_length > 0) /* reverse merges */ { if (patch->reverse) { if (patch->mergeinfo == NULL) patch->mergeinfo = mergeinfo; else SVN_ERR(svn_mergeinfo_merge2(patch->mergeinfo, mergeinfo, result_pool, scratch_pool)); } else { if (patch->reverse_mergeinfo == NULL) patch->reverse_mergeinfo = mergeinfo; else SVN_ERR(svn_mergeinfo_merge2(patch->reverse_mergeinfo, mergeinfo, result_pool, scratch_pool)); } hunk->original_length--; } else if (hunk->modified_length > 0) /* forward merges */ { if (patch->reverse) { if (patch->reverse_mergeinfo == NULL) patch->reverse_mergeinfo = mergeinfo; else SVN_ERR(svn_mergeinfo_merge2(patch->reverse_mergeinfo, mergeinfo, result_pool, scratch_pool)); } else { if (patch->mergeinfo == NULL) patch->mergeinfo = mergeinfo; else SVN_ERR(svn_mergeinfo_merge2(patch->mergeinfo, mergeinfo, result_pool, scratch_pool)); } hunk->modified_length--; } *found_mergeinfo = TRUE; } } return SVN_NO_ERROR; }