svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream, svn_txdelta_window_handler_t handler, void *handler_baton, unsigned char *digest, apr_pool_t *pool) { svn_txdelta_stream_t *txstream; svn_error_t *err; /* ### this is a hack. we should simply read from the stream, construct ### some windows, and pass those to the handler. there isn't any reason ### to crank up a full "diff" algorithm just to copy a stream. ### ### will fix RSN. */ /* Create a delta stream which converts an *empty* bytestream into the target bytestream. */ svn_txdelta(&txstream, svn_stream_empty(pool), stream, pool); err = svn_txdelta_send_txstream(txstream, handler, handler_baton, pool); if (digest && (! err)) { const unsigned char *result_md5; result_md5 = svn_txdelta_md5_digest(txstream); /* Since err is null, result_md5 "cannot" be null. */ memcpy(digest, result_md5, APR_MD5_DIGESTSIZE); } return err; }
/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and store it into a new temporary file *TEMPFILE. OLDROOT may be NULL, in which case the delta will be computed against an empty file, as per the svn_fs_get_file_delta_stream docstring. Record the length of the temporary file in *LEN, and rewind the file before returning. */ static svn_error_t * store_delta(apr_file_t **tempfile, svn_filesize_t *len, svn_fs_root_t *oldroot, const char *oldpath, svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool) { svn_stream_t *temp_stream; apr_off_t offset = 0; svn_txdelta_stream_t *delta_stream; svn_txdelta_window_handler_t wh; void *whb; /* Create a temporary file and open a stream to it. Note that we need the file handle in order to rewind it. */ SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL, svn_io_file_del_on_pool_cleanup, pool, pool)); temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool); /* Compute the delta and send it to the temporary file. */ SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath, newroot, newpath, pool)); svn_txdelta_to_svndiff2(&wh, &whb, temp_stream, 0, pool); SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool)); /* Get the length of the temporary file and rewind it. */ SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool)); *len = offset; offset = 0; return svn_io_file_seek(*tempfile, APR_SET, &offset, pool); }
/* Deltifies a node, i.e. generates a svndiff that can be dumped */ static svn_error_t *delta_deltify_node(de_node_baton_t *node) { svn_txdelta_stream_t *stream; svn_txdelta_window_handler_t handler; void *handler_baton; svn_stream_t *source, *target, *dest; apr_file_t *source_file = NULL, *target_file = NULL, *dest_file = NULL; dump_options_t *opts = node->de_baton->opts; apr_pool_t *pool = svn_pool_create(node->pool); svn_error_t *err; DEBUG_MSG("delta_deltify_node(%s): %s -> %s\n", node->path, node->old_filename, node->filename); /* Open source and target */ apr_file_open(&target_file, node->filename, APR_READ, 0600, pool); target = svn_stream_from_aprfile2(target_file, FALSE, pool); if (node->old_filename) { apr_file_open(&source_file, node->old_filename, APR_READ, 0600, pool); source = svn_stream_from_aprfile2(source_file, FALSE, pool); } else { source = svn_stream_empty(pool); } /* Open temporary output file */ node->delta_filename = apr_psprintf(node->pool, "%s/XXXXXX", opts->temp_dir); apr_file_mktemp(&dest_file, node->delta_filename, APR_CREATE | APR_READ | APR_WRITE | APR_EXCL, pool); dest = svn_stream_from_aprfile2(dest_file, FALSE, pool); DEBUG_MSG("delta_deltify_node(%s): writing to %s\n", node->path, node->delta_filename); /* Produce delta in svndiff format */ svn_txdelta(&stream, source, target, pool); svn_txdelta_to_svndiff2(&handler, &handler_baton, dest, 0, pool); err = svn_txdelta_send_txstream(stream, handler, handler_baton, pool); svn_pool_destroy(pool); return err; }
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; }
int main(int argc, char **argv) { svn_error_t *err; apr_status_t apr_err; apr_file_t *source_file; apr_file_t *target_file; svn_stream_t *stdout_stream; svn_txdelta_stream_t *txdelta_stream; svn_txdelta_window_handler_t svndiff_handler; svn_stream_t *encoder; void *svndiff_baton; apr_pool_t *pool; int version = 0; if (argc < 3) { printf("usage: %s source target [version]\n", argv[0]); exit(0); } apr_initialize(); pool = svn_pool_create(NULL); apr_err = apr_file_open(&source_file, argv[1], (APR_READ | APR_BINARY), APR_OS_DEFAULT, pool); if (apr_err) { fprintf(stderr, "unable to open \"%s\" for reading\n", argv[1]); exit(1); } apr_err = apr_file_open(&target_file, argv[2], (APR_READ | APR_BINARY), APR_OS_DEFAULT, pool); if (apr_err) { fprintf(stderr, "unable to open \"%s\" for reading\n", argv[2]); exit(1); } if (argc == 4) version = atoi(argv[3]); svn_txdelta(&txdelta_stream, svn_stream_from_aprfile(source_file, pool), svn_stream_from_aprfile(target_file, pool), pool); err = svn_stream_for_stdout(&stdout_stream, pool); if (err) svn_handle_error2(err, stdout, TRUE, "svndiff-test: "); #ifdef QUOPRINT_SVNDIFFS encoder = svn_quoprint_encode(stdout_stream, pool); #else encoder = svn_base64_encode(stdout_stream, pool); #endif /* use maximum compression level */ svn_txdelta_to_svndiff3(&svndiff_handler, &svndiff_baton, encoder, version, 9, pool); err = svn_txdelta_send_txstream(txdelta_stream, svndiff_handler, svndiff_baton, pool); if (err) svn_handle_error2(err, stdout, TRUE, "svndiff-test: "); apr_file_close(source_file); apr_file_close(target_file); svn_pool_destroy(pool); apr_terminate(); exit(0); }
/* Implements svn_test_driver_t. */ static svn_error_t * random_test(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { static char msg_buff[256]; apr_uint32_t seed, bytes_range, maxlen; int i, iterations, dump_files, print_windows; const char *random_bytes; /* Initialize parameters and print out the seed in case we dump core or something. */ init_params(&seed, &maxlen, &iterations, &dump_files, &print_windows, &random_bytes, &bytes_range, pool); sprintf(msg_buff, "random delta test, seed = %lu", (unsigned long) seed); *msg = msg_buff; if (msg_only) return SVN_NO_ERROR; else printf("SEED: %s\n", msg_buff); for (i = 0; i < iterations; i++) { /* Generate source and target for the delta and its application. */ apr_uint32_t subseed_base = svn_test_rand(&seed); apr_file_t *source = generate_random_file(maxlen, subseed_base, &seed, random_bytes, bytes_range, dump_files, pool); apr_file_t *target = generate_random_file(maxlen, subseed_base, &seed, random_bytes, bytes_range, dump_files, pool); apr_file_t *source_copy = copy_tempfile(source, pool); apr_file_t *target_regen = open_tempfile(NULL, pool); svn_txdelta_stream_t *txdelta_stream; svn_txdelta_window_handler_t handler; svn_stream_t *stream; void *handler_baton; /* Set up a four-stage pipeline: create a delta, convert it to svndiff format, parse it back into delta format, and apply it to a copy of the source file to see if we get the same target back. */ apr_pool_t *delta_pool = svn_pool_create(pool); /* Make stage 4: apply the text delta. */ svn_txdelta_apply(svn_stream_from_aprfile(source_copy, delta_pool), svn_stream_from_aprfile(target_regen, delta_pool), NULL, NULL, delta_pool, &handler, &handler_baton); /* Make stage 3: reparse the text delta. */ stream = svn_txdelta_parse_svndiff(handler, handler_baton, TRUE, delta_pool); /* Make stage 2: encode the text delta in svndiff format. */ svn_txdelta_to_svndiff2(&handler, &handler_baton, stream, 1, delta_pool); /* Make stage 1: create the text delta. */ svn_txdelta(&txdelta_stream, svn_stream_from_aprfile(source, delta_pool), svn_stream_from_aprfile(target, delta_pool), delta_pool); SVN_ERR(svn_txdelta_send_txstream(txdelta_stream, handler, handler_baton, delta_pool)); svn_pool_destroy(delta_pool); SVN_ERR(compare_files(target, target_regen, dump_files)); apr_file_close(source); apr_file_close(target); apr_file_close(source_copy); apr_file_close(target_regen); } return SVN_NO_ERROR; }
/* 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; }
svn_error_t * svn_ra__file_revs_from_log(svn_ra_session_t *ra_session, const char *path, svn_revnum_t start, svn_revnum_t end, svn_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool) { svn_node_kind_t kind; const char *repos_url, *session_url, *fs_path; apr_array_header_t *condensed_targets; struct fr_log_message_baton lmb; struct rev *rev; apr_hash_t *last_props; svn_stream_t *last_stream; apr_pool_t *currpool, *lastpool; /* Fetch the absolute FS path associated with PATH. */ SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool)); /* Check to make sure we're dealing with a file. */ SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool)); if (kind == svn_node_dir) return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file"), fs_path); condensed_targets = apr_array_make(pool, 1, sizeof(const char *)); APR_ARRAY_PUSH(condensed_targets, const char *) = path; lmb.path = fs_path; lmb.eldest = NULL; lmb.pool = pool; /* Accumulate revision metadata by walking the revisions backwards; this allows us to follow moves/copies correctly. */ SVN_ERR(svn_ra_get_log2(ra_session, condensed_targets, end, start, 0, /* no limit */ TRUE, FALSE, FALSE, NULL, fr_log_message_receiver, &lmb, pool)); /* Reparent the session while we go back through the history. */ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool)); SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool)); currpool = svn_pool_create(pool); lastpool = svn_pool_create(pool); /* We want the first txdelta to be against the empty file. */ last_props = apr_hash_make(lastpool); last_stream = svn_stream_empty(lastpool); /* Walk the revision list in chronological order, downloading each fulltext, diffing it with its predecessor, and calling the file_revs handler for each one. Use two iteration pools rather than one, because the diff routines need to look at a sliding window of revisions. Two pools gives us a ring buffer of sorts. */ for (rev = lmb.eldest; rev; rev = rev->next) { const char *temp_path; apr_pool_t *tmppool; apr_hash_t *props; apr_file_t *file; svn_stream_t *stream; apr_array_header_t *prop_diffs; svn_txdelta_stream_t *delta_stream; svn_txdelta_window_handler_t delta_handler = NULL; void *delta_baton = NULL; svn_pool_clear(currpool); /* Get the contents of the file from the repository, and put them in a temporary local file. */ SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL, svn_io_file_del_on_pool_cleanup, currpool, currpool)); SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision, stream, NULL, &props, currpool)); SVN_ERR(svn_stream_close(stream)); /* Open up a stream to the local file. */ SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT, currpool)); stream = svn_stream_from_aprfile2(file, FALSE, currpool); /* Calculate the property diff */ SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool)); /* Call the file_rev handler */ SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props, FALSE, /* merged revision */ &delta_handler, &delta_baton, prop_diffs, lastpool)); /* Compute and send delta if client asked for it. */ if (delta_handler) { /* Get the content delta. Don't calculate checksums as we don't * use them. */ svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool); /* And send. */ SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, delta_baton, lastpool)); } /* Switch the pools and data for the next iteration */ tmppool = currpool; currpool = lastpool; lastpool = tmppool; SVN_ERR(svn_stream_close(last_stream)); last_stream = stream; last_props = props; } SVN_ERR(svn_stream_close(last_stream)); svn_pool_destroy(currpool); svn_pool_destroy(lastpool); /* Reparent the session back to the original URL. */ return svn_ra_reparent(ra_session, session_url, pool); }
/* Implements svn_test_driver_t. */ static svn_error_t * random_test(apr_pool_t *pool) { apr_uint32_t seed, maxlen; apr_size_t bytes_range; int i, iterations, dump_files, print_windows; const char *random_bytes; /* Initialize parameters and print out the seed in case we dump core or something. */ init_params(&seed, &maxlen, &iterations, &dump_files, &print_windows, &random_bytes, &bytes_range, pool); for (i = 0; i < iterations; i++) { /* Generate source and target for the delta and its application. */ apr_uint32_t subseed_base = svn_test_rand(&seed); apr_file_t *source = generate_random_file(maxlen, subseed_base, &seed, random_bytes, bytes_range, dump_files, pool); apr_file_t *target = generate_random_file(maxlen, subseed_base, &seed, random_bytes, bytes_range, dump_files, pool); apr_file_t *source_copy = copy_tempfile(source, pool); apr_file_t *target_regen = open_tempfile(NULL, pool); svn_txdelta_stream_t *txdelta_stream; svn_txdelta_window_handler_t handler; svn_stream_t *stream; void *handler_baton; /* Set up a four-stage pipeline: create a delta, convert it to svndiff format, parse it back into delta format, and apply it to a copy of the source file to see if we get the same target back. */ apr_pool_t *delta_pool = svn_pool_create(pool); /* Make stage 4: apply the text delta. */ svn_txdelta_apply(svn_stream_from_aprfile(source_copy, delta_pool), svn_stream_from_aprfile(target_regen, delta_pool), NULL, NULL, delta_pool, &handler, &handler_baton); /* Make stage 3: reparse the text delta. */ stream = svn_txdelta_parse_svndiff(handler, handler_baton, TRUE, delta_pool); /* Make stage 2: encode the text delta in svndiff format using varying compression levels. */ svn_txdelta_to_svndiff3(&handler, &handler_baton, stream, 1, i % 10, delta_pool); /* Make stage 1: create the text delta. */ svn_txdelta2(&txdelta_stream, svn_stream_from_aprfile(source, delta_pool), svn_stream_from_aprfile(target, delta_pool), FALSE, delta_pool); SVN_ERR(svn_txdelta_send_txstream(txdelta_stream, handler, handler_baton, delta_pool)); svn_pool_destroy(delta_pool); SVN_ERR(compare_files(target, target_regen, dump_files)); apr_file_close(source); apr_file_close(target); apr_file_close(source_copy); apr_file_close(target_regen); } return SVN_NO_ERROR; }