svn_error_t * svn_string_from_stream(svn_string_t **result, svn_stream_t *stream, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_stringbuf_t *work = svn_stringbuf_create_ensure(SVN__STREAM_CHUNK_SIZE, result_pool); char *buffer = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); while (1) { apr_size_t len = SVN__STREAM_CHUNK_SIZE; SVN_ERR(svn_stream_read(stream, buffer, &len)); svn_stringbuf_appendbytes(work, buffer, len); if (len < SVN__STREAM_CHUNK_SIZE) break; } SVN_ERR(svn_stream_close(stream)); *result = apr_palloc(result_pool, sizeof(**result)); (*result)->data = work->data; (*result)->len = work->len; return SVN_NO_ERROR; }
/* If PATH needs to be escaped, return an escaped version of it, allocated * from RESULT_POOL. Otherwise, return PATH directly. */ static const char * auto_escape_path(const char *path, apr_pool_t *result_pool) { apr_size_t len = strlen(path); apr_size_t i; const char esc = '\x1b'; for (i = 0; i < len; ++i) if (path[i] < ' ') { svn_stringbuf_t *escaped = svn_stringbuf_create_ensure(2 * len, result_pool); for (i = 0; i < len; ++i) if (path[i] < ' ') { svn_stringbuf_appendbyte(escaped, esc); svn_stringbuf_appendbyte(escaped, path[i] + 'A' - 1); } else { svn_stringbuf_appendbyte(escaped, path[i]); } return escaped->data; } return path; }
/* Generate a string of exactly LEN chars (plus terminating NUL). KEY is * an arbitrary integer that will be transformed into a character sequence * using entries of BASIC_STRINGS. The result will be allocated in POOL. */ static svn_stringbuf_t * generate_string(apr_uint64_t key, apr_size_t len, apr_pool_t *pool) { svn_stringbuf_t *result = svn_stringbuf_create_ensure(len, pool); apr_uint64_t temp = key; apr_uint64_t run = 0; while (len) { apr_size_t idx; apr_size_t add_len; if (temp == 0) { temp = key; run++; } idx = (temp + run) % STRING_COUNT; temp /= STRING_COUNT; add_len = strlen(basic_strings[idx]); add_len = MIN(len, add_len); svn_stringbuf_appendbytes(result, basic_strings[idx], add_len); len -= add_len; } return result; }
/* Read a line of original or modified hunk text from the specified * RANGE within FILE. FILE is expected to contain unidiff text. * Leading unidiff symbols ('+', '-', and ' ') are removed from the line, * Any lines commencing with the VERBOTEN character are discarded. * VERBOTEN should be '+' or '-', depending on which form of hunk text * is being read. * * All other parameters are as in svn_diff_hunk_readline_original_text() * and svn_diff_hunk_readline_modified_text(). */ static svn_error_t * hunk_readline_original_or_modified(apr_file_t *file, struct svn_diff__hunk_range *range, svn_stringbuf_t **stringbuf, const char **eol, svn_boolean_t *eof, char verboten, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_size_t max_len; svn_boolean_t filtered; apr_off_t pos; svn_stringbuf_t *str; if (range->current >= range->end) { /* We're past the range. Indicate that no bytes can be read. */ *eof = TRUE; if (eol) *eol = NULL; *stringbuf = svn_stringbuf_create("", result_pool); return SVN_NO_ERROR; } pos = 0; SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); SVN_ERR(svn_io_file_seek(file, APR_SET, &range->current, scratch_pool)); do { max_len = range->end - range->current; SVN_ERR(readline(file, &str, eol, eof, max_len, result_pool, scratch_pool)); range->current = 0; SVN_ERR(svn_io_file_seek(file, APR_CUR, &range->current, scratch_pool)); filtered = (str->data[0] == verboten || str->data[0] == '\\'); } while (filtered && ! *eof); if (filtered) { /* EOF, return an empty string. */ *stringbuf = svn_stringbuf_create_ensure(0, result_pool); } else if (str->data[0] == '+' || str->data[0] == '-' || str->data[0] == ' ') { /* Shave off leading unidiff symbols. */ *stringbuf = svn_stringbuf_create(str->data + 1, result_pool); } else { /* Return the line as-is. */ *stringbuf = svn_stringbuf_dup(str, result_pool); } SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); return SVN_NO_ERROR; }
/* Helper for svn_repos_dump_fs. Write a revision record of REV in FS to writable STREAM, using POOL. */ static svn_error_t * write_revision_record(svn_stream_t *stream, svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) { apr_size_t len; apr_hash_t *props; svn_stringbuf_t *encoded_prophash; apr_time_t timetemp; svn_string_t *datevalue; svn_stream_t *propstream; /* Read the revision props even if we're aren't going to dump them for verification purposes */ SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool)); /* Run revision date properties through the time conversion to canonicalize them. */ /* ### Remove this when it is no longer needed for sure. */ datevalue = apr_hash_get(props, SVN_PROP_REVISION_DATE, APR_HASH_KEY_STRING); if (datevalue) { SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool)); datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool), pool); apr_hash_set(props, SVN_PROP_REVISION_DATE, APR_HASH_KEY_STRING, datevalue); } encoded_prophash = svn_stringbuf_create_ensure(0, pool); propstream = svn_stream_from_stringbuf(encoded_prophash, pool); SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool)); SVN_ERR(svn_stream_close(propstream)); /* ### someday write a revision-content-checksum */ SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_REVISION_NUMBER ": %ld\n", rev)); SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH ": %" APR_SIZE_T_FMT "\n", encoded_prophash->len)); /* Write out a regular Content-length header for the benefit of non-Subversion RFC-822 parsers. */ SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH ": %" APR_SIZE_T_FMT "\n\n", encoded_prophash->len)); len = encoded_prophash->len; SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len)); len = 1; return svn_stream_write(stream, "\n", &len); }
svn_stringbuf_t * svn_skel__unparse(const svn_skel_t *skel, apr_pool_t *pool) { svn_stringbuf_t *str = svn_stringbuf_create_ensure(estimate_unparsed_size(skel) + 200, pool); return unparse(skel, str); }
apr_status_t svn_subr__win32_xlate_to_stringbuf(win32_xlate_t *handle, const char *src_data, apr_size_t src_length, svn_stringbuf_t **dest, apr_pool_t *pool) { WCHAR * wide_str; int retval, wide_size; if (src_length == 0) { *dest = svn_stringbuf_create_empty(pool); return APR_SUCCESS; } retval = MultiByteToWideChar(handle->from_page_id, 0, src_data, src_length, NULL, 0); if (retval == 0) return apr_get_os_error(); wide_size = retval; /* Allocate temporary buffer for small strings on stack instead of heap. */ if (wide_size <= MAX_PATH) { wide_str = alloca(wide_size * sizeof(WCHAR)); } else { wide_str = apr_palloc(pool, wide_size * sizeof(WCHAR)); } retval = MultiByteToWideChar(handle->from_page_id, 0, src_data, src_length, wide_str, wide_size); if (retval == 0) return apr_get_os_error(); retval = WideCharToMultiByte(handle->to_page_id, 0, wide_str, wide_size, NULL, 0, NULL, NULL); if (retval == 0) return apr_get_os_error(); /* Ensure that buffer is enough to hold result string and termination character. */ *dest = svn_stringbuf_create_ensure(retval + 1, pool); (*dest)->len = retval; retval = WideCharToMultiByte(handle->to_page_id, 0, wide_str, wide_size, (*dest)->data, (*dest)->len, NULL, NULL); if (retval == 0) return apr_get_os_error(); (*dest)->len = retval; return APR_SUCCESS; }
inline Try<std::string> patch(const std::string& s, const Diff& diff) { // Initialize the Apache Portable Runtime subsystem, as necessary // for using the svn library. initialize(); // Note that svn_pool_create wraps apr_pool_create_ex, which is // thread safe, see: http://goo.gl/NX0hps. apr_pool_t* pool = svn_pool_create(NULL); // We want to apply the svndiff format diff to the source trying to // produce a result. First setup a handler for applying a text delta // to the source stream. svn_string_t source; source.data = s.data(); source.len = s.length(); svn_txdelta_window_handler_t handler; void* baton = NULL; svn_stringbuf_t* patched = svn_stringbuf_create_ensure(s.length(), pool); svn_txdelta_apply( svn_stream_from_string(&source, pool), svn_stream_from_stringbuf(patched, pool), NULL, NULL, pool, &handler, &baton); // Setup a stream that converts an svndiff format diff to a text // delta, so that we can use our handler to patch the source string. svn_stream_t* stream = svn_txdelta_parse_svndiff( handler, baton, TRUE, pool); // Now feed the diff into the stream to compute the patched result. const char* data = diff.data.data(); apr_size_t length = diff.data.length(); svn_error_t* error = svn_stream_write(stream, data, &length); if (error != NULL) { char buffer[1024]; std::string message(svn_err_best_message(error, buffer, 1024)); svn_pool_destroy(pool); return Error(message); } std::string result(patched->data, patched->len); svn_pool_destroy(pool); return result; }
svn_stringbuf_t * svn_stringbuf_ncreate(const char *bytes, apr_size_t size, apr_pool_t *pool) { svn_stringbuf_t *strbuf = svn_stringbuf_create_ensure(size, pool); memcpy(strbuf->data, bytes, size); /* Null termination is the convention -- even if we suspect the data to be binary, it's not up to us to decide, it's the caller's call. Heck, that's why they call it the caller! */ strbuf->data[size] = '\0'; strbuf->len = size; return strbuf; }
/* Print dumpstream-formatted information about REVISION. * Implements the `svn_ra_replay_revstart_callback_t' interface. */ static svn_error_t * replay_revstart(svn_revnum_t revision, void *replay_baton, const svn_delta_editor_t **editor, void **edit_baton, apr_hash_t *rev_props, apr_pool_t *pool) { struct replay_baton *rb = replay_baton; apr_hash_t *normal_props; svn_stringbuf_t *propstring; svn_stream_t *stdout_stream; svn_stream_t *revprop_stream; SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); /* Revision-number: 19 */ SVN_ERR(svn_stream_printf(stdout_stream, pool, SVN_REPOS_DUMPFILE_REVISION_NUMBER ": %ld\n", revision)); SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool)); propstring = svn_stringbuf_create_ensure(0, pool); revprop_stream = svn_stream_from_stringbuf(propstring, pool); SVN_ERR(svn_hash_write2(normal_props, revprop_stream, "PROPS-END", pool)); SVN_ERR(svn_stream_close(revprop_stream)); /* Prop-content-length: 13 */ SVN_ERR(svn_stream_printf(stdout_stream, pool, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH ": %" APR_SIZE_T_FMT "\n", propstring->len)); /* Content-length: 29 */ SVN_ERR(svn_stream_printf(stdout_stream, pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH ": %" APR_SIZE_T_FMT "\n\n", propstring->len)); /* Property data. */ SVN_ERR(svn_stream_write(stdout_stream, propstring->data, &(propstring->len))); SVN_ERR(svn_stream_printf(stdout_stream, pool, "\n")); SVN_ERR(svn_stream_close(stdout_stream)); SVN_ERR(svn_rdump__get_dump_editor(editor, edit_baton, revision, rb->stdout_stream, rb->extra_ra_session, check_cancel, NULL, pool)); return SVN_NO_ERROR; }
/* Guts of svn_stream_readline(). * Returns the line read from STREAM in *STRINGBUF, and indicates * end-of-file in *EOF. If DETECT_EOL is TRUE, the end-of-line indicator * is detected automatically and returned in *EOL. * If DETECT_EOL is FALSE, *EOL must point to the desired end-of-line * indicator. STRINGBUF is allocated in POOL. */ static svn_error_t * stream_readline_bytewise(svn_stringbuf_t **stringbuf, svn_boolean_t *eof, const char *eol, svn_stream_t *stream, apr_pool_t *pool) { svn_stringbuf_t *str; apr_size_t numbytes; const char *match; char c; /* Since we're reading one character at a time, let's at least optimize for the 90% case. 90% of the time, we can avoid the stringbuf ever having to realloc() itself if we start it out at 80 chars. */ str = svn_stringbuf_create_ensure(LINE_CHUNK_SIZE, pool); /* Read into STR up to and including the next EOL sequence. */ match = eol; while (*match) { numbytes = 1; SVN_ERR(svn_stream_read(stream, &c, &numbytes)); if (numbytes != 1) { /* a 'short' read means the stream has run out. */ *eof = TRUE; *stringbuf = str; return SVN_NO_ERROR; } if (c == *match) match++; else match = eol; svn_stringbuf_appendbyte(str, c); } *eof = FALSE; svn_stringbuf_chop(str, match - eol); *stringbuf = str; return SVN_NO_ERROR; }
/* If the footer data in FILE has not been read, yet, do so now. * Index locations will only be read upon request as we assume they get * cached and the FILE is usually used for REP data access only. * Hence, the separate step. */ static svn_error_t * auto_read_footer(svn_fs_x__revision_file_t *file) { if (file->l2p_info.start == -1) { apr_off_t filesize = 0; unsigned char footer_length; svn_stringbuf_t *footer; /* Determine file size. */ SVN_ERR(auto_open(file)); SVN_ERR(svn_io_file_seek(file->file, APR_END, &filesize, file->pool)); /* Read last byte (containing the length of the footer). */ SVN_ERR(svn_io_file_aligned_seek(file->file, file->block_size, NULL, filesize - 1, file->pool)); SVN_ERR(svn_io_file_read_full2(file->file, &footer_length, sizeof(footer_length), NULL, NULL, file->pool)); /* Read footer. */ footer = svn_stringbuf_create_ensure(footer_length, file->pool); SVN_ERR(svn_io_file_aligned_seek(file->file, file->block_size, NULL, filesize - 1 - footer_length, file->pool)); SVN_ERR(svn_io_file_read_full2(file->file, footer->data, footer_length, &footer->len, NULL, file->pool)); footer->data[footer->len] = '\0'; /* Extract index locations. */ SVN_ERR(svn_fs_x__parse_footer(&file->l2p_info.start, &file->l2p_info.checksum, &file->p2l_info.start, &file->p2l_info.checksum, footer, file->file_info.start_revision, filesize - footer_length - 1, file->pool)); file->l2p_info.end = file->p2l_info.start; file->p2l_info.end = filesize - footer_length - 1; } return SVN_NO_ERROR; }
/* If PATH has been escaped, return the un-escaped version of it, allocated * from RESULT_POOL. Otherwise, return PATH directly. */ static const char * auto_unescape_path(const char *path, apr_pool_t *result_pool) { const char esc = '\x1b'; if (strchr(path, esc)) { apr_size_t len = strlen(path); apr_size_t i; svn_stringbuf_t *unescaped = svn_stringbuf_create_ensure(len, result_pool); for (i = 0; i < len; ++i) if (path[i] == esc) svn_stringbuf_appendbyte(unescaped, path[++i] + 1 - 'A'); else svn_stringbuf_appendbyte(unescaped, path[i]); return unescaped->data; } return path; }
/* Take the ORIGINAL string and replace all occurrences of ":" without * limiting the key space. Allocate the result in POOL. */ static const char * normalize_key_part(const char *original, apr_pool_t *pool) { apr_size_t i; apr_size_t len = strlen(original); svn_stringbuf_t *normalized = svn_stringbuf_create_ensure(len, pool); for (i = 0; i < len; ++i) { char c = original[i]; switch (c) { case ':': svn_stringbuf_appendbytes(normalized, "%_", 2); break; case '%': svn_stringbuf_appendbytes(normalized, "%%", 2); break; default : svn_stringbuf_appendbyte(normalized, c); } } return normalized->data; }
/* Process the strace output stored in FILE. */ static void parse_file(apr_file_t *file) { apr_pool_t *pool = svn_pool_create(NULL); apr_pool_t *iterpool = svn_pool_create(pool); /* limit lines to 4k (usually, we need less than 200 bytes) */ svn_stringbuf_t *line = svn_stringbuf_create_ensure(4096, pool); do { svn_error_t *err = NULL; line->len = line->blocksize-1; err = svn_io_read_length_line(file, line->data, &line->len, iterpool); svn_error_clear(err); if (err) break; parse_line(line); svn_pool_clear(iterpool); } while (line->len > 0); }
/* 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; }
/* Try to parse a hunk header in string HEADER, putting parsed information * into HUNK. Return TRUE if the header parsed correctly. ATAT is the * character string used to delimit the hunk header. * Do all allocations in POOL. */ static svn_boolean_t parse_hunk_header(const char *header, svn_diff_hunk_t *hunk, const char *atat, apr_pool_t *pool) { const char *p; const char *start; svn_stringbuf_t *range; p = header + strlen(atat); if (*p != ' ') /* No. */ return FALSE; p++; if (*p != '-') /* Nah... */ return FALSE; /* OK, this may be worth allocating some memory for... */ range = svn_stringbuf_create_ensure(31, pool); start = ++p; while (*p && *p != ' ') { p++; } if (*p != ' ') /* No no no... */ return FALSE; svn_stringbuf_appendbytes(range, start, p - start); /* Try to parse the first range. */ if (! parse_range(&hunk->original_start, &hunk->original_length, range->data)) return FALSE; /* Clear the stringbuf so we can reuse it for the second range. */ svn_stringbuf_setempty(range); p++; if (*p != '+') /* Eeek! */ return FALSE; /* OK, this may be worth copying... */ start = ++p; while (*p && *p != ' ') { p++; } if (*p != ' ') /* No no no... */ return FALSE; svn_stringbuf_appendbytes(range, start, p - start); /* Check for trailing @@ */ p++; if (! starts_with(p, atat)) return FALSE; /* There may be stuff like C-function names after the trailing @@, * but we ignore that. */ /* Try to parse the second range. */ if (! parse_range(&hunk->modified_start, &hunk->modified_length, range->data)) return FALSE; /* Hunk header is good. */ return TRUE; }
svn_stringbuf_t * svn_stringbuf_create_empty(apr_pool_t *pool) { return svn_stringbuf_create_ensure(0, pool); }
inline Try<Diff> diff(const std::string& from, const std::string& to) { // Initialize the Apache Portable Runtime subsystem, as necessary // for using the svn library. initialize(); // Note that svn_pool_create wraps apr_pool_create_ex, which is // thread safe, see: http://goo.gl/NX0hps. apr_pool_t* pool = svn_pool_create(NULL); // First we need to produce a text delta stream by diffing 'source' // against 'target'. svn_string_t source; source.data = from.data(); source.len = from.length(); svn_string_t target; target.data = to.data(); target.len = to.length(); svn_txdelta_stream_t* delta; #if SVN_VER_MAJOR >= 1 && SVN_VER_MINOR >= 8 svn_txdelta2( &delta, svn_stream_from_string(&source, pool), svn_stream_from_string(&target, pool), false, pool); #else svn_txdelta( &delta, svn_stream_from_string(&source, pool), svn_stream_from_string(&target, pool), pool); #endif // Now we want to convert this text delta stream into an svndiff // format based diff. Setup the handler that will consume the text // delta and produce the svndiff. svn_txdelta_window_handler_t handler; void* baton = NULL; svn_stringbuf_t* diff = svn_stringbuf_create_ensure(1024, pool); #if SVN_VER_MAJOR >= 1 && SVN_VER_MINOR >= 7 svn_txdelta_to_svndiff3( &handler, &baton, svn_stream_from_stringbuf(diff, pool), 0, SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); #elif SVN_VER_MAJOR >= 1 && SVN_VER_MINOR >= 4 svn_txdelta_to_svndiff2( &handler, &baton, svn_stream_from_stringbuf(diff, pool), 0, pool); #else svn_txdelta_to_svndiff( svn_stream_from_stringbuf(diff, pool), pool, &handler, &baton); #endif // Now feed the text delta to the handler. svn_error_t* error = svn_txdelta_send_txstream(delta, handler, baton, pool); if (error != NULL) { char buffer[1024]; std::string message(svn_err_best_message(error, buffer, 1024)); svn_pool_destroy(pool); return Error(message); } Diff d(std::string(diff->data, diff->len)); svn_pool_destroy(pool); return d; }
/* A helper for reading a line of text from a range in the patch file. * * Allocate *STRINGBUF in RESULT_POOL, and read into it one line from FILE. * Reading stops either after a line-terminator was found or after MAX_LEN * bytes have been read. The line-terminator is not stored in *STRINGBUF. * * The line-terminator is detected automatically and stored in *EOL * if EOL is not NULL. If EOF is reached and FILE does not end * with a newline character, and EOL is not NULL, *EOL is set to NULL. * * SCRATCH_POOL is used for temporary allocations. */ static svn_error_t * readline(apr_file_t *file, svn_stringbuf_t **stringbuf, const char **eol, svn_boolean_t *eof, apr_size_t max_len, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_stringbuf_t *str; const char *eol_str; apr_size_t numbytes; char c; apr_size_t len; svn_boolean_t found_eof; str = svn_stringbuf_create_ensure(80, result_pool); /* Read bytes into STR up to and including, but not storing, * the next EOL sequence. */ eol_str = NULL; numbytes = 1; len = 0; found_eof = FALSE; while (!found_eof) { if (len < max_len) SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, &found_eof, scratch_pool)); len++; if (numbytes != 1 || len > max_len) { found_eof = TRUE; break; } if (c == '\n') { eol_str = "\n"; } else if (c == '\r') { eol_str = "\r"; if (!found_eof && len < max_len) { apr_off_t pos; /* Check for "\r\n" by peeking at the next byte. */ pos = 0; SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, &found_eof, scratch_pool)); if (numbytes == 1 && c == '\n') { eol_str = "\r\n"; len++; } else { /* Pretend we never peeked. */ SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); found_eof = FALSE; numbytes = 1; } } } else svn_stringbuf_appendbyte(str, c); if (eol_str) break; } if (eol) *eol = eol_str; if (eof) *eof = found_eof; *stringbuf = str; return SVN_NO_ERROR; }
static svn_error_t * stream_readline_chunky(svn_stringbuf_t **stringbuf, svn_boolean_t *eof, const char *eol, svn_stream_t *stream, apr_pool_t *pool) { /* Read larger chunks of data at once into this buffer and scan * that for EOL. A good chunk size should be about 80 chars since * most text lines will be shorter. However, don't use a much * larger value because filling the buffer from the stream takes * time as well. */ char buffer[LINE_CHUNK_SIZE+1]; /* variables */ svn_stream_mark_t *mark; apr_size_t numbytes; const char *eol_pos; apr_size_t total_parsed = 0; /* invariant for this call */ const size_t eol_len = strlen(eol); /* Remember the line start so this plus the line length will be * the position to move to at the end of this function. */ SVN_ERR(svn_stream_mark(stream, &mark, pool)); /* Read the first chunk. */ numbytes = LINE_CHUNK_SIZE; SVN_ERR(svn_stream_read(stream, buffer, &numbytes)); buffer[numbytes] = '\0'; /* Look for the EOL in this first chunk. If we find it, we are done here. */ eol_pos = strstr(buffer, eol); if (eol_pos != NULL) { *stringbuf = svn_stringbuf_ncreate(buffer, eol_pos - buffer, pool); total_parsed = eol_pos - buffer + eol_len; } else if (numbytes < LINE_CHUNK_SIZE) { /* We hit EOF but not EOL. */ *stringbuf = svn_stringbuf_ncreate(buffer, numbytes, pool); *eof = TRUE; return SVN_NO_ERROR; } else { /* A larger buffer for the string is needed. */ svn_stringbuf_t *str; str = svn_stringbuf_create_ensure(2*LINE_CHUNK_SIZE, pool); svn_stringbuf_appendbytes(str, buffer, numbytes); *stringbuf = str; /* Loop reading chunks until an EOL was found. If we hit EOF, fall * back to the standard implementation. */ do { /* Append the next chunk to the string read so far. */ svn_stringbuf_ensure(str, str->len + LINE_CHUNK_SIZE); numbytes = LINE_CHUNK_SIZE; SVN_ERR(svn_stream_read(stream, str->data + str->len, &numbytes)); str->len += numbytes; str->data[str->len] = '\0'; /* Look for the EOL in the new data plus the last part of the * previous chunk because the EOL may span over the boundary * between both chunks. */ eol_pos = strstr(str->data + str->len - numbytes - (eol_len-1), eol); if ((numbytes < LINE_CHUNK_SIZE) && (eol_pos == NULL)) { /* We hit EOF instead of EOL. */ *eof = TRUE; return SVN_NO_ERROR; } } while (eol_pos == NULL); /* Number of bytes we actually consumed (i.e. line + EOF). * We need to "return" the rest to the stream by moving its * read pointer. */ total_parsed = eol_pos - str->data + eol_len; /* Terminate the string at the EOL postion and return it. */ str->len = eol_pos - str->data; str->data[str->len] = 0; } /* Move the stream read pointer to the first position behind the EOL. */ SVN_ERR(svn_stream_seek(stream, mark)); return svn_error_trace(svn_stream_skip(stream, total_parsed)); }
/* 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? */ }