/* ### 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; }
/* 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; }