| /* |
| * dump_editor.c: The svn_delta_editor_t editor used by svnrdump to |
| * dump revisions. |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include "svn_hash.h" |
| #include "svn_pools.h" |
| #include "svn_repos.h" |
| #include "svn_path.h" |
| #include "svn_props.h" |
| #include "svn_subst.h" |
| #include "svn_dirent_uri.h" |
| |
| #include "private/svn_fspath.h" |
| |
| #include "dump_editor.h" |
| |
| #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) |
| |
| #if 0 |
| #define LDR_DBG(x) SVN_DBG(x) |
| #else |
| #define LDR_DBG(x) while(0) |
| #endif |
| |
| /* The baton used by the dump editor. */ |
| struct dump_edit_baton { |
| /* The output stream we write the dumpfile to */ |
| svn_stream_t *stream; |
| |
| /* Pool for per-revision allocations */ |
| apr_pool_t *pool; |
| |
| /* Properties which were modified during change_file_prop |
| * or change_dir_prop. */ |
| apr_hash_t *props; |
| |
| /* Properties which were deleted during change_file_prop |
| * or change_dir_prop. */ |
| apr_hash_t *deleted_props; |
| |
| /* Temporary buffer to write property hashes to in human-readable |
| * form. ### Is this really needed? */ |
| svn_stringbuf_t *propstring; |
| |
| /* Temporary file used for textdelta application along with its |
| absolute path; these two variables should be allocated in the |
| per-edit-session pool */ |
| const char *delta_abspath; |
| apr_file_t *delta_file; |
| |
| /* The checksum of the file the delta is being applied to */ |
| const char *base_checksum; |
| |
| /* Flags to trigger dumping props and text */ |
| svn_boolean_t dump_text; |
| svn_boolean_t dump_props; |
| svn_boolean_t dump_newlines; |
| }; |
| |
| /* Normalize the line ending style of the values of properties in PROPS |
| * that "need translation" (according to svn_prop_needs_translation(), |
| * currently all svn:* props) so that they contain only LF (\n) line endings. |
| */ |
| svn_error_t * |
| normalize_props(apr_hash_t *props, |
| apr_pool_t *pool) |
| { |
| apr_hash_index_t *hi; |
| const char *key, *cstring; |
| const svn_string_t *value; |
| |
| for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) |
| { |
| key = svn__apr_hash_index_key(hi); |
| value = svn__apr_hash_index_val(hi); |
| |
| if (svn_prop_needs_translation(key)) |
| { |
| SVN_ERR(svn_subst_translate_cstring2(value->data, &cstring, |
| "\n", TRUE, |
| NULL, FALSE, |
| pool)); |
| value = svn_string_create(cstring, pool); |
| apr_hash_set(props, key, APR_HASH_KEY_STRING, value); |
| } |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| /* Make a directory baton to represent the directory at PATH (relative |
| * to the EDIT_BATON). |
| * |
| * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this |
| * directory should be compared for changes. If the copyfrom |
| * information is valid, the directory will be compared against its |
| * copy source. |
| * |
| * PARENT_DIR_BATON is the directory baton of this directory's parent, |
| * or NULL if this is the top-level directory of the edit. ADDED |
| * indicates if this directory is newly added in this revision. |
| * Perform all allocations in POOL. */ |
| static struct dir_baton * |
| make_dir_baton(const char *path, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| void *edit_baton, |
| void *parent_dir_baton, |
| svn_boolean_t added, |
| apr_pool_t *pool) |
| { |
| struct dump_edit_baton *eb = edit_baton; |
| struct dir_baton *pb = parent_dir_baton; |
| struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); |
| const char *abspath; |
| |
| /* Construct the full path of this node. */ |
| /* ### FIXME: Not sure why we use an abspath here. If I understand |
| ### correctly, the only place we used this path is in dump_node(), |
| ### which immediately converts it into a relpath. -- cmpilato. */ |
| if (pb) |
| abspath = svn_fspath__canonicalize(path, pool); |
| else |
| abspath = "/"; |
| |
| /* Strip leading slash from copyfrom_path so that the path is |
| canonical and svn_relpath_join can be used */ |
| if (copyfrom_path) |
| copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool); |
| |
| new_db->eb = eb; |
| new_db->parent_dir_baton = pb; |
| new_db->abspath = abspath; |
| new_db->copyfrom_path = copyfrom_path; |
| new_db->copyfrom_rev = copyfrom_rev; |
| new_db->added = added; |
| new_db->written_out = FALSE; |
| new_db->deleted_entries = apr_hash_make(pool); |
| |
| return new_db; |
| } |
| |
| /* Extract and dump properties stored in edit baton EB, using POOL for |
| * any temporary allocations. If TRIGGER_VAR is not NULL, it is set to FALSE. |
| * Unless DUMP_DATA_TOO is set, only property headers are dumped. |
| */ |
| static svn_error_t * |
| dump_props(struct dump_edit_baton *eb, |
| svn_boolean_t *trigger_var, |
| svn_boolean_t dump_data_too, |
| apr_pool_t *pool) |
| { |
| svn_stream_t *propstream; |
| |
| if (trigger_var && !*trigger_var) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(normalize_props(eb->props, eb->pool)); |
| svn_stringbuf_setempty(eb->propstring); |
| propstream = svn_stream_from_stringbuf(eb->propstring, eb->pool); |
| SVN_ERR(svn_hash_write_incremental(eb->props, eb->deleted_props, |
| propstream, "PROPS-END", pool)); |
| SVN_ERR(svn_stream_close(propstream)); |
| |
| /* Prop-delta: true */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_PROP_DELTA |
| ": true\n")); |
| |
| /* Prop-content-length: 193 */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH |
| ": %" APR_SIZE_T_FMT "\n", eb->propstring->len)); |
| |
| if (dump_data_too) |
| { |
| /* Content-length: 14 */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_CONTENT_LENGTH |
| ": %" APR_SIZE_T_FMT "\n\n", |
| eb->propstring->len)); |
| |
| /* The properties. */ |
| SVN_ERR(svn_stream_write(eb->stream, eb->propstring->data, |
| &(eb->propstring->len))); |
| |
| /* No text is going to be dumped. Write a couple of newlines and |
| wait for the next node/ revision. */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n")); |
| |
| /* Cleanup so that data is never dumped twice. */ |
| SVN_ERR(svn_hash__clear(eb->props, eb->pool)); |
| SVN_ERR(svn_hash__clear(eb->deleted_props, eb->pool)); |
| if (trigger_var) |
| *trigger_var = FALSE; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| dump_newlines(struct dump_edit_baton *eb, |
| svn_boolean_t *trigger_var, |
| apr_pool_t *pool) |
| { |
| if (trigger_var && *trigger_var) |
| { |
| SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n")); |
| *trigger_var = FALSE; |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| /* |
| * 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 COPYFROM_PATH/COPYFROM_REV. |
| * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this |
| * node is part of a copied subtree. |
| */ |
| static svn_error_t * |
| dump_node(struct dump_edit_baton *eb, |
| const char *path, /* an absolute path. */ |
| svn_node_kind_t kind, |
| enum svn_node_action action, |
| svn_boolean_t is_copy, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| apr_pool_t *pool) |
| { |
| /* Remove leading slashes from path and copyfrom_path */ |
| if (path) |
| path = svn_relpath_canonicalize(path, pool); |
| |
| if (copyfrom_path) |
| copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool); |
| |
| /* Node-path: commons/STATUS */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", path)); |
| |
| /* Node-kind: file */ |
| 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")); |
| |
| |
| /* Write the appropriate Node-action header */ |
| switch (action) |
| { |
| case svn_node_action_change: |
| /* We are here after a change_file_prop or change_dir_prop. They |
| set up whatever dump_props they needed to- nothing to |
| do here but print node action information */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_NODE_ACTION |
| ": change\n")); |
| break; |
| |
| case svn_node_action_replace: |
| if (!is_copy) |
| { |
| /* Node-action: replace */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_NODE_ACTION |
| ": replace\n")); |
| |
| /* Wait for a change_*_prop to be called before dumping |
| anything */ |
| eb->dump_props = TRUE; |
| break; |
| } |
| /* More complex case: is_copy is true, and copyfrom_path/ |
| copyfrom_rev are present: delete the original, and then re-add |
| it */ |
| |
| 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, copyfrom_path, copyfrom_rev, pool)); |
| |
| /* We can leave this routine quietly now, don't need to dump any |
| content; that was already done in the second record. */ |
| break; |
| |
| case 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. Nothing more to do- |
| print a couple of newlines because we're not dumping props or |
| text. */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n")); |
| break; |
| |
| case svn_node_action_add: |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); |
| |
| if (!is_copy) |
| { |
| /* eb->dump_props for files is handled in close_file |
| which is called immediately. However, directories are not |
| closed until all the work inside them has been done; |
| eb->dump_props for directories is handled in all the |
| functions that can possibly be called after add_directory: |
| add_directory, open_directory, delete_entry, close_directory, |
| add_file, open_file. change_dir_prop is a special case. */ |
| |
| /* Wait for a change_*_prop to be called before dumping |
| anything */ |
| eb->dump_props = TRUE; |
| break; |
| } |
| |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV |
| ": %ld\n" |
| SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH |
| ": %s\n", |
| copyfrom_rev, copyfrom_path)); |
| |
| /* Ugly hack: If a directory was copied from a previous |
| revision, nothing like close_file will be called to write two |
| blank lines. If change_dir_prop is called, props are dumped |
| (along with the necessary PROPS-END\n\n and we're good. So |
| set a dump_newlines here to print the newlines unless |
| change_dir_prop is called next otherwise the `svnadmin load` |
| parser will fail. */ |
| if (kind == svn_node_dir) |
| eb->dump_newlines = TRUE; |
| |
| break; |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| open_root(void *edit_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **root_baton) |
| { |
| struct dump_edit_baton *eb = edit_baton; |
| |
| /* Clear the per-revision pool after each revision */ |
| svn_pool_clear(eb->pool); |
| |
| eb->props = apr_hash_make(eb->pool); |
| eb->deleted_props = apr_hash_make(eb->pool); |
| eb->propstring = svn_stringbuf_create("", eb->pool); |
| |
| *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, |
| edit_baton, NULL, FALSE, eb->pool); |
| LDR_DBG(("open_root %p\n", *root_baton)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| delete_entry(const char *path, |
| svn_revnum_t revision, |
| void *parent_baton, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *pb = parent_baton; |
| |
| LDR_DBG(("delete_entry %s\n", path)); |
| |
| /* Some pending properties to dump? */ |
| SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); |
| |
| /* Some pending newlines to dump? */ |
| SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); |
| |
| /* Add this path to the deleted_entries of the parent directory |
| baton. */ |
| apr_hash_set(pb->deleted_entries, apr_pstrdup(pb->eb->pool, path), |
| APR_HASH_KEY_STRING, pb); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| add_directory(const char *path, |
| void *parent_baton, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| apr_pool_t *pool, |
| void **child_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| void *val; |
| struct dir_baton *new_db; |
| svn_boolean_t is_copy; |
| |
| LDR_DBG(("add_directory %s\n", path)); |
| |
| new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, |
| pb, TRUE, pb->eb->pool); |
| |
| /* Some pending properties to dump? */ |
| SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); |
| |
| /* Some pending newlines to dump? */ |
| SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); |
| |
| /* This might be a replacement -- is the path already deleted? */ |
| val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING); |
| |
| /* Detect an add-with-history */ |
| is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); |
| |
| /* Dump the node */ |
| SVN_ERR(dump_node(pb->eb, path, |
| svn_node_dir, |
| val ? svn_node_action_replace : svn_node_action_add, |
| is_copy, |
| is_copy ? copyfrom_path : NULL, |
| is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, |
| pool)); |
| |
| if (val) |
| /* Delete the path, it's now been dumped */ |
| apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL); |
| |
| new_db->written_out = TRUE; |
| |
| *child_baton = new_db; |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| open_directory(const char *path, |
| void *parent_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **child_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct dir_baton *new_db; |
| const char *copyfrom_path = NULL; |
| svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; |
| |
| LDR_DBG(("open_directory %s\n", path)); |
| |
| /* Some pending properties to dump? */ |
| SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); |
| |
| /* Some pending newlines to dump? */ |
| SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); |
| |
| /* If the parent directory has explicit comparison path and rev, |
| record the same for this one. */ |
| if (pb && ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev)) |
| { |
| copyfrom_path = svn_relpath_join(pb->copyfrom_path, |
| svn_relpath_basename(path, NULL), |
| pb->eb->pool); |
| copyfrom_rev = pb->copyfrom_rev; |
| } |
| |
| new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb, |
| FALSE, pb->eb->pool); |
| *child_baton = new_db; |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| close_directory(void *dir_baton, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *db = dir_baton; |
| struct dump_edit_baton *eb = db->eb; |
| apr_hash_index_t *hi; |
| |
| LDR_DBG(("close_directory %p\n", dir_baton)); |
| |
| /* Some pending properties to dump? */ |
| SVN_ERR(dump_props(eb, &(eb->dump_props), TRUE, pool)); |
| |
| /* Some pending newlines to dump? */ |
| SVN_ERR(dump_newlines(eb, &(eb->dump_newlines), pool)); |
| |
| /* Dump the deleted directory entries */ |
| for (hi = apr_hash_first(pool, db->deleted_entries); hi; |
| hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| const char *path; |
| apr_hash_this(hi, &key, NULL, NULL); |
| path = key; |
| |
| SVN_ERR(dump_node(db->eb, path, svn_node_unknown, svn_node_action_delete, |
| FALSE, NULL, SVN_INVALID_REVNUM, pool)); |
| } |
| |
| SVN_ERR(svn_hash__clear(db->deleted_entries, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| add_file(const char *path, |
| void *parent_baton, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| apr_pool_t *pool, |
| void **file_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| void *val; |
| svn_boolean_t is_copy; |
| |
| LDR_DBG(("add_file %s\n", path)); |
| |
| /* Some pending properties to dump? */ |
| SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); |
| |
| /* Some pending newlines to dump? */ |
| SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); |
| |
| /* This might be a replacement -- is the path already deleted? */ |
| val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING); |
| |
| /* Detect add-with-history. */ |
| is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); |
| |
| /* Dump the node. */ |
| SVN_ERR(dump_node(pb->eb, path, |
| svn_node_file, |
| val ? svn_node_action_replace : svn_node_action_add, |
| is_copy, |
| is_copy ? copyfrom_path : NULL, |
| is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, |
| pool)); |
| |
| if (val) |
| /* delete the path, it's now been dumped. */ |
| apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL); |
| |
| /* Build a nice file baton to pass to change_file_prop and |
| apply_textdelta */ |
| *file_baton = pb->eb; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| open_file(const char *path, |
| void *parent_baton, |
| svn_revnum_t ancestor_revision, |
| apr_pool_t *pool, |
| void **file_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| const char *copyfrom_path = NULL; |
| svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; |
| |
| LDR_DBG(("open_file %s\n", path)); |
| |
| /* Some pending properties to dump? */ |
| SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); |
| |
| /* Some pending newlines to dump? */ |
| SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); |
| |
| /* If the parent directory has explicit copyfrom path and rev, |
| record the same for this one. */ |
| if (pb && ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev)) |
| { |
| copyfrom_path = svn_relpath_join(pb->copyfrom_path, |
| svn_relpath_basename(path, NULL), |
| pb->eb->pool); |
| copyfrom_rev = pb->copyfrom_rev; |
| } |
| |
| SVN_ERR(dump_node(pb->eb, path, svn_node_file, svn_node_action_change, |
| FALSE, copyfrom_path, copyfrom_rev, pool)); |
| |
| /* Build a nice file baton to pass to change_file_prop and |
| apply_textdelta */ |
| *file_baton = pb->eb; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| change_dir_prop(void *parent_baton, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *db = parent_baton; |
| |
| LDR_DBG(("change_dir_prop %p\n", parent_baton)); |
| |
| if (svn_property_kind(NULL, name) != svn_prop_regular_kind) |
| return SVN_NO_ERROR; |
| |
| if (value) |
| apr_hash_set(db->eb->props, apr_pstrdup(db->eb->pool, name), |
| APR_HASH_KEY_STRING, svn_string_dup(value, db->eb->pool)); |
| else |
| apr_hash_set(db->eb->deleted_props, apr_pstrdup(db->eb->pool, name), |
| APR_HASH_KEY_STRING, ""); |
| |
| if (! db->written_out) |
| { |
| /* If db->written_out is set, it means that the node information |
| corresponding to this directory has already been written: don't |
| do anything; dump_props will take care of dumping the |
| props. If it not, dump the node itself before dumping the |
| props. */ |
| |
| SVN_ERR(dump_node(db->eb, db->abspath, svn_node_dir, |
| svn_node_action_change, FALSE, db->copyfrom_path, |
| db->copyfrom_rev, pool)); |
| db->written_out = TRUE; |
| } |
| |
| /* Dump props whether or not the directory has been written |
| out. Then disable printing a couple of extra newlines */ |
| SVN_ERR(dump_props(db->eb, NULL, TRUE, pool)); |
| db->eb->dump_newlines = FALSE; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| change_file_prop(void *file_baton, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| struct dump_edit_baton *eb = file_baton; |
| |
| LDR_DBG(("change_file_prop %p\n", file_baton)); |
| |
| if (svn_property_kind(NULL, name) != svn_prop_regular_kind) |
| return SVN_NO_ERROR; |
| |
| if (value) |
| apr_hash_set(eb->props, apr_pstrdup(eb->pool, name), |
| APR_HASH_KEY_STRING, svn_string_dup(value, eb->pool)); |
| else |
| apr_hash_set(eb->deleted_props, apr_pstrdup(eb->pool, name), |
| APR_HASH_KEY_STRING, ""); |
| |
| /* Dump the property headers and wait; close_file might need |
| to write text headers too depending on whether |
| apply_textdelta is called */ |
| eb->dump_props = TRUE; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| window_handler(svn_txdelta_window_t *window, void *baton) |
| { |
| struct handler_baton *hb = baton; |
| static svn_error_t *err; |
| |
| err = hb->apply_handler(window, hb->apply_baton); |
| if (window != NULL && !err) |
| return SVN_NO_ERROR; |
| |
| if (err) |
| SVN_ERR(err); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| apply_textdelta(void *file_baton, const char *base_checksum, |
| apr_pool_t *pool, |
| svn_txdelta_window_handler_t *handler, |
| void **handler_baton) |
| { |
| struct dump_edit_baton *eb = file_baton; |
| |
| /* Custom handler_baton allocated in a separate pool */ |
| struct handler_baton *hb; |
| svn_stream_t *delta_filestream; |
| |
| hb = apr_pcalloc(eb->pool, sizeof(*hb)); |
| |
| LDR_DBG(("apply_textdelta %p\n", file_baton)); |
| |
| /* Use a temporary file to measure the text-content-length */ |
| delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool); |
| |
| /* Prepare to write the delta to the delta_filestream */ |
| svn_txdelta_to_svndiff2(&(hb->apply_handler), &(hb->apply_baton), |
| delta_filestream, 0, pool); |
| |
| eb->dump_text = TRUE; |
| eb->base_checksum = apr_pstrdup(eb->pool, base_checksum); |
| SVN_ERR(svn_stream_close(delta_filestream)); |
| |
| /* The actual writing takes place when this function has |
| finished. Set handler and handler_baton now so for |
| window_handler() */ |
| *handler = window_handler; |
| *handler_baton = hb; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| close_file(void *file_baton, |
| const char *text_checksum, |
| apr_pool_t *pool) |
| { |
| struct dump_edit_baton *eb = file_baton; |
| svn_stream_t *delta_filestream; |
| apr_finfo_t *info = apr_pcalloc(pool, sizeof(apr_finfo_t)); |
| apr_off_t offset; |
| apr_status_t err; |
| |
| LDR_DBG(("close_file %p\n", file_baton)); |
| |
| /* Some pending properties to dump? Dump just the headers- dump the |
| props only after dumping the text headers too (if present) */ |
| SVN_ERR(dump_props(eb, &(eb->dump_props), FALSE, pool)); |
| |
| /* Dump the text headers */ |
| if (eb->dump_text) |
| { |
| /* Text-delta: true */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_TEXT_DELTA |
| ": true\n")); |
| |
| err = apr_file_info_get(info, APR_FINFO_SIZE, eb->delta_file); |
| if (err) |
| SVN_ERR(svn_error_wrap_apr(err, NULL)); |
| |
| if (eb->base_checksum) |
| /* Text-delta-base-md5: */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 |
| ": %s\n", |
| eb->base_checksum)); |
| |
| /* Text-content-length: 39 */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH |
| ": %lu\n", |
| (unsigned long)info->size)); |
| |
| /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 |
| ": %s\n", |
| text_checksum)); |
| } |
| |
| /* Content-length: 1549 */ |
| /* If both text and props are absent, skip this header */ |
| if (eb->dump_props) |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_CONTENT_LENGTH |
| ": %ld\n\n", |
| (unsigned long)info->size + eb->propstring->len)); |
| else if (eb->dump_text) |
| SVN_ERR(svn_stream_printf(eb->stream, pool, |
| SVN_REPOS_DUMPFILE_CONTENT_LENGTH |
| ": %ld\n\n", |
| (unsigned long)info->size)); |
| |
| /* Dump the props now */ |
| if (eb->dump_props) |
| { |
| SVN_ERR(svn_stream_write(eb->stream, eb->propstring->data, |
| &(eb->propstring->len))); |
| |
| /* Cleanup */ |
| eb->dump_props = FALSE; |
| SVN_ERR(svn_hash__clear(eb->props, eb->pool)); |
| SVN_ERR(svn_hash__clear(eb->deleted_props, eb->pool)); |
| } |
| |
| /* Dump the text */ |
| if (eb->dump_text) |
| { |
| /* Seek to the beginning of the delta file, map it to a stream, |
| and copy the stream to eb->stream. Then close the stream and |
| truncate the file so we can reuse it for the next textdelta |
| application. Note that the file isn't created, opened or |
| closed here */ |
| offset = 0; |
| SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool)); |
| delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool); |
| SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL, pool)); |
| |
| /* Cleanup */ |
| SVN_ERR(svn_stream_close(delta_filestream)); |
| SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool)); |
| eb->dump_text = FALSE; |
| } |
| |
| /* Write a couple of blank lines for matching output with `svnadmin |
| dump` */ |
| SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| close_edit(void *edit_baton, apr_pool_t *pool) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| get_dump_editor(const svn_delta_editor_t **editor, |
| void **edit_baton, |
| svn_stream_t *stream, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| struct dump_edit_baton *eb; |
| svn_delta_editor_t *de; |
| |
| eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton)); |
| eb->stream = stream; |
| |
| /* Create a special per-revision pool */ |
| eb->pool = svn_pool_create(pool); |
| |
| /* Open a unique temporary file for all textdelta applications in |
| this edit session. The file is automatically closed and cleaned |
| up when the edit session is done. */ |
| SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath), |
| NULL, svn_io_file_del_on_close, pool, pool)); |
| |
| de = svn_delta_default_editor(pool); |
| de->open_root = open_root; |
| de->delete_entry = delete_entry; |
| de->add_directory = add_directory; |
| de->open_directory = open_directory; |
| de->close_directory = close_directory; |
| de->change_dir_prop = change_dir_prop; |
| de->change_file_prop = change_file_prop; |
| de->apply_textdelta = apply_textdelta; |
| de->add_file = add_file; |
| de->open_file = open_file; |
| de->close_file = close_file; |
| de->close_edit = close_edit; |
| |
| /* Set the edit_baton and editor. */ |
| *edit_baton = eb; |
| *editor = de; |
| |
| /* Wrap this editor in a cancellation editor. */ |
| return svn_delta_get_cancellation_editor(cancel_func, cancel_baton, |
| de, eb, editor, edit_baton, pool); |
| } |