| /* |
| * adm_crawler.c: report local WC mods to an Editor. |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2007 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| /* ==================================================================== */ |
| |
| |
| #include <string.h> |
| |
| #include <apr_pools.h> |
| #include <apr_file_io.h> |
| #include <apr_hash.h> |
| #include <apr_md5.h> |
| |
| #include <assert.h> |
| |
| #include "svn_types.h" |
| #include "svn_pools.h" |
| #include "svn_wc.h" |
| #include "svn_io.h" |
| #include "svn_md5.h" |
| #include "svn_base64.h" |
| #include "svn_delta.h" |
| #include "svn_path.h" |
| |
| #include "private/svn_wc_private.h" |
| |
| #include "wc.h" |
| #include "adm_files.h" |
| #include "props.h" |
| #include "translate.h" |
| #include "entries.h" |
| #include "lock.h" |
| |
| #include "svn_private_config.h" |
| |
| |
| /* Helper for report_revisions_and_depths(). |
| |
| Perform an atomic restoration of the file FILE_PATH; that is, copy |
| the file's text-base to the administrative tmp area, and then move |
| that file to FILE_PATH with possible translations/expansions. If |
| USE_COMMIT_TIMES is set, then set working file's timestamp to |
| last-commit-time. Either way, set entry-timestamp to match that of |
| the working file when all is finished. */ |
| static svn_error_t * |
| restore_file(const char *file_path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t use_commit_times, |
| apr_pool_t *pool) |
| { |
| const char *tmp_file, *text_base_path; |
| svn_wc_entry_t newentry; |
| const char *bname; |
| svn_boolean_t special; |
| |
| text_base_path = svn_wc__text_base_path(file_path, FALSE, pool); |
| bname = svn_path_basename(file_path, pool); |
| |
| /* Copy / translate into a temporary file, which afterwards can |
| be atomically moved over the original working copy file. */ |
| |
| SVN_ERR(svn_wc_translated_file2(&tmp_file, |
| text_base_path, file_path, adm_access, |
| SVN_WC_TRANSLATE_FROM_NF |
| | SVN_WC_TRANSLATE_FORCE_COPY, pool)); |
| |
| SVN_ERR(svn_io_file_rename(tmp_file, file_path, pool)); |
| |
| SVN_ERR(svn_wc__maybe_set_read_only(NULL, file_path, adm_access, pool)); |
| |
| /* If necessary, tweak the new working file's executable bit. */ |
| SVN_ERR(svn_wc__maybe_set_executable(NULL, file_path, adm_access, pool)); |
| |
| /* Remove any text conflict */ |
| SVN_ERR(svn_wc_resolved_conflict3(file_path, adm_access, TRUE, FALSE, |
| svn_depth_empty, |
| svn_wc_conflict_choose_merged, |
| NULL, NULL, NULL, NULL, pool)); |
| |
| if (use_commit_times) |
| { |
| SVN_ERR(svn_wc__get_special(&special, file_path, adm_access, pool)); |
| } |
| |
| /* Possibly set timestamp to last-commit-time. */ |
| if (use_commit_times && (! special)) |
| { |
| const svn_wc_entry_t *entry; |
| |
| SVN_ERR(svn_wc_entry(&entry, file_path, adm_access, FALSE, pool)); |
| assert(entry != NULL); |
| |
| SVN_ERR(svn_io_set_file_affected_time(entry->cmt_date, |
| file_path, pool)); |
| |
| newentry.text_time = entry->cmt_date; |
| } |
| else |
| { |
| SVN_ERR(svn_io_file_affected_time(&newentry.text_time, |
| file_path, pool)); |
| } |
| |
| /* Modify our entry's text-timestamp to match the working file. */ |
| SVN_ERR(svn_wc__entry_modify(adm_access, bname, |
| &newentry, SVN_WC__ENTRY_MODIFY_TEXT_TIME, |
| TRUE /* do_sync now */, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* The recursive crawler that describes a mixed-revision working |
| copy to an RA layer. Used to initiate updates. |
| |
| This is a depth-first recursive walk of DIR_PATH under ADM_ACCESS. |
| Look at each entry and check if its revision is different than |
| DIR_REV. If so, report this fact to REPORTER. If an entry is |
| missing from disk, report its absence to REPORTER. If an entry has |
| a different URL than expected, report that to REPORTER. If an |
| entry has a different depth than its parent, report that to |
| REPORTER. |
| |
| Alternatively, if REPORT_EVERYTHING is set, then report all |
| children unconditionally. |
| |
| DEPTH is actually the *requested* depth for the update-like |
| operation for which we are reporting working copy state. However, |
| certain requested depths affect the depth of the report crawl. For |
| example, if the requested depth is svn_depth_empty, there's no |
| point descending into subdirs, no matter what their depths. So: |
| |
| If DEPTH is svn_depth_empty, don't report any files and don't |
| descend into any subdirs. If svn_depth_files, report files but |
| still don't descend into subdirs. If svn_depth_immediates, report |
| files, and report subdirs themselves but not their entries. If |
| svn_depth_infinity or svn_depth_unknown, report everything all the |
| way down. (That last sentence might sound counterintuitive, but |
| since you can't go deeper than the local ambient depth anyway, |
| requesting svn_depth_infinity really means "as deep as the various |
| parts of this working copy go". Of course, the information that |
| comes back from the server will be different for svn_depth_unknown |
| than for svn_depth_infinity.) |
| |
| DEPTH_COMPATIBILITY_TRICK means the same thing here as it does |
| in svn_wc_crawl_revisions3(). |
| |
| If TRAVERSAL_INFO is non-null, record this directory's |
| value of svn:externals in both TRAVERSAL_INFO->externals_old and |
| TRAVERSAL_INFO->externals_new, using wc_path + dir_path as the key, |
| and the raw (unparsed) value of the property as the value; store |
| this directory's depth in TRAVERSAL_INFO->depths, using the same |
| key and svn_depth_to_word(depth) as the value. (Note: We set the |
| property value in both places, because its absence in just one or |
| the other place signals that the property was added or deleted; |
| thus, storing it in both places signals that it is present and, by |
| default, unchanged.) |
| |
| If RESTORE_FILES is set, then unexpectedly missing working files |
| will be restored from text-base and NOTIFY_FUNC/NOTIFY_BATON |
| will be called to report the restoration. USE_COMMIT_TIMES is |
| passed to restore_file() helper. */ |
| static svn_error_t * |
| report_revisions_and_depths(svn_wc_adm_access_t *adm_access, |
| const char *dir_path, |
| svn_revnum_t dir_rev, |
| const svn_ra_reporter3_t *reporter, |
| void *report_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| svn_boolean_t restore_files, |
| svn_depth_t depth, |
| svn_boolean_t depth_compatibility_trick, |
| svn_boolean_t report_everything, |
| svn_boolean_t use_commit_times, |
| svn_wc_traversal_info_t *traversal_info, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *entries, *dirents; |
| apr_hash_index_t *hi; |
| apr_pool_t *subpool = svn_pool_create(pool), *iterpool; |
| const svn_wc_entry_t *dot_entry; |
| const char *this_url, *this_path, *full_path, *this_full_path; |
| svn_wc_adm_access_t *dir_access; |
| svn_wc_notify_t *notify; |
| |
| /* Get both the SVN Entries and the actual on-disk entries. Also |
| notice that we're picking up hidden entries too. */ |
| full_path = svn_path_join(svn_wc_adm_access_path(adm_access), |
| dir_path, subpool); |
| SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, full_path, subpool)); |
| SVN_ERR(svn_wc_entries_read(&entries, dir_access, TRUE, subpool)); |
| SVN_ERR(svn_io_get_dir_filenames(&dirents, full_path, subpool)); |
| |
| /*** Do the real reporting and recursing. ***/ |
| |
| /* First, look at "this dir" to see what its URL is. */ |
| dot_entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, |
| APR_HASH_KEY_STRING); |
| |
| /* If "this dir" has "svn:externals" property set on it, store its name |
| and depth in traversal_info. */ |
| if (traversal_info) |
| { |
| const svn_string_t *val; |
| SVN_ERR(svn_wc_prop_get(&val, SVN_PROP_EXTERNALS, full_path, adm_access, |
| subpool)); |
| if (val) |
| { |
| apr_pool_t *dup_pool = traversal_info->pool; |
| const char *dup_path = apr_pstrdup(dup_pool, full_path); |
| const char *dup_val = apr_pstrmemdup(dup_pool, val->data, val->len); |
| apr_hash_set(traversal_info->externals_old, |
| dup_path, APR_HASH_KEY_STRING, dup_val); |
| apr_hash_set(traversal_info->externals_new, |
| dup_path, APR_HASH_KEY_STRING, dup_val); |
| apr_hash_set(traversal_info->depths, |
| dup_path, APR_HASH_KEY_STRING, |
| svn_depth_to_word(dot_entry->depth)); |
| } |
| } |
| |
| /* Looping over current directory's SVN entries: */ |
| iterpool = svn_pool_create(subpool); |
| |
| for (hi = apr_hash_first(subpool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| apr_ssize_t klen; |
| void *val; |
| const svn_wc_entry_t *current_entry; |
| svn_io_dirent_t *dirent; |
| svn_node_kind_t dirent_kind; |
| svn_boolean_t missing = FALSE; |
| |
| /* Clear the iteration subpool here because the loop has a bunch |
| of 'continue' jump statements. */ |
| svn_pool_clear(iterpool); |
| |
| /* Get the next entry */ |
| apr_hash_this(hi, &key, &klen, &val); |
| current_entry = val; |
| |
| /* Compute the name of the entry. Skip THIS_DIR altogether. */ |
| if (! strcmp(key, SVN_WC_ENTRY_THIS_DIR)) |
| continue; |
| |
| /* Compute the paths and URLs we need. */ |
| this_url = svn_path_url_add_component(dot_entry->url, key, iterpool); |
| this_path = svn_path_join(dir_path, key, iterpool); |
| this_full_path = svn_path_join(full_path, key, iterpool); |
| |
| /*** The Big Tests: ***/ |
| |
| /* If the entry is 'deleted' or 'absent', make sure the server |
| knows it's gone... */ |
| if (current_entry->deleted || current_entry->absent) |
| { |
| /* ...unless we're reporting everything, in which case we're |
| going to report it missing later anyway. */ |
| if (! report_everything) |
| SVN_ERR(reporter->delete_path(report_baton, this_path, iterpool)); |
| continue; |
| } |
| |
| /* Is the entry on disk? Set a flag if not. */ |
| dirent = apr_hash_get(dirents, key, klen); |
| if (! dirent) |
| { |
| /* It is possible on a case insensitive system that the |
| entry is not really missing, so we call our trusty but |
| expensive friend svn_io_check_path to be sure. */ |
| SVN_ERR(svn_io_check_path(this_full_path, &dirent_kind, |
| iterpool)); |
| if (dirent_kind == svn_node_none) |
| missing = TRUE; |
| } |
| |
| /* From here on out, ignore any entry scheduled for addition */ |
| if (current_entry->schedule == svn_wc_schedule_add) |
| continue; |
| |
| /*** Files ***/ |
| if (current_entry->kind == svn_node_file) |
| { |
| /* If the item is missing from disk, and we're supposed to |
| restore missing things, and it isn't missing as a result |
| of a scheduling operation, then ... */ |
| if (missing |
| && restore_files |
| && (current_entry->schedule != svn_wc_schedule_delete) |
| && (current_entry->schedule != svn_wc_schedule_replace)) |
| { |
| /* ... recreate file from text-base, and ... */ |
| SVN_ERR(restore_file(this_full_path, dir_access, |
| use_commit_times, iterpool)); |
| |
| /* ... report the restoration to the caller. */ |
| if (notify_func != NULL) |
| { |
| notify = svn_wc_create_notify(this_full_path, |
| svn_wc_notify_restore, |
| iterpool); |
| notify->kind = svn_node_file; |
| (*notify_func)(notify_baton, notify, iterpool); |
| } |
| } |
| |
| if (report_everything) |
| { |
| /* Report the file unconditionally, one way or another. */ |
| if (strcmp(current_entry->url, this_url) != 0) |
| SVN_ERR(reporter->link_path(report_baton, this_path, |
| current_entry->url, |
| current_entry->revision, |
| current_entry->depth, |
| FALSE, current_entry->lock_token, |
| iterpool)); |
| else |
| SVN_ERR(reporter->set_path(report_baton, this_path, |
| current_entry->revision, |
| current_entry->depth, |
| FALSE, current_entry->lock_token, |
| iterpool)); |
| } |
| |
| /* Possibly report a disjoint URL ... */ |
| else if ((current_entry->schedule != svn_wc_schedule_add) |
| && (current_entry->schedule != svn_wc_schedule_replace) |
| && (strcmp(current_entry->url, this_url) != 0)) |
| SVN_ERR(reporter->link_path(report_baton, |
| this_path, |
| current_entry->url, |
| current_entry->revision, |
| current_entry->depth, |
| FALSE, |
| current_entry->lock_token, |
| iterpool)); |
| /* ... or perhaps just a differing revision or lock token, |
| or the mere presence of the file in a depth-empty dir. */ |
| else if (current_entry->revision != dir_rev |
| || current_entry->lock_token |
| || dot_entry->depth == svn_depth_empty) |
| SVN_ERR(reporter->set_path(report_baton, |
| this_path, |
| current_entry->revision, |
| current_entry->depth, |
| FALSE, |
| current_entry->lock_token, |
| iterpool)); |
| } /* end file case */ |
| |
| /*** Directories (in recursive mode) ***/ |
| else if (current_entry->kind == svn_node_dir |
| && (depth > svn_depth_files |
| || depth == svn_depth_unknown)) |
| { |
| svn_wc_adm_access_t *subdir_access; |
| const svn_wc_entry_t *subdir_entry; |
| svn_boolean_t start_empty; |
| |
| /* If a directory is missing from disk, we have no way to |
| recreate it locally, so report as missing and move |
| along. Again, don't bother if we're reporting |
| everything, because the dir is already missing on the server. */ |
| if (missing) |
| { |
| if (! report_everything) |
| SVN_ERR(reporter->delete_path(report_baton, this_path, |
| iterpool)); |
| continue; |
| } |
| |
| /* We need to read the full entry of the directory from its |
| own "this dir", if available. */ |
| if (svn_wc__adm_missing(adm_access, this_full_path)) |
| continue; |
| SVN_ERR(svn_wc_adm_retrieve(&subdir_access, adm_access, |
| this_full_path, iterpool)); |
| SVN_ERR(svn_wc_entry(&subdir_entry, this_full_path, subdir_access, |
| TRUE, iterpool)); |
| |
| start_empty = subdir_entry->incomplete; |
| if (depth_compatibility_trick |
| && subdir_entry->depth <= svn_depth_files |
| && depth > subdir_entry->depth) |
| { |
| start_empty = TRUE; |
| } |
| |
| if (report_everything) |
| { |
| /* Report the dir unconditionally, one way or another. */ |
| if (strcmp(subdir_entry->url, this_url) != 0) |
| SVN_ERR(reporter->link_path(report_baton, this_path, |
| subdir_entry->url, |
| subdir_entry->revision, |
| subdir_entry->depth, |
| start_empty, |
| subdir_entry->lock_token, |
| iterpool)); |
| else |
| SVN_ERR(reporter->set_path(report_baton, this_path, |
| subdir_entry->revision, |
| subdir_entry->depth, |
| start_empty, |
| subdir_entry->lock_token, |
| iterpool)); |
| } |
| |
| /* Possibly report a disjoint URL ... */ |
| else if (strcmp(subdir_entry->url, this_url) != 0) |
| SVN_ERR(reporter->link_path(report_baton, |
| this_path, |
| subdir_entry->url, |
| subdir_entry->revision, |
| subdir_entry->depth, |
| start_empty, |
| subdir_entry->lock_token, |
| iterpool)); |
| /* ... or perhaps just a differing revision, lock token, incomplete |
| subdir, the mere presence of the directory in a depth-empty or |
| depth-files dir, or if the parent dir is at depth-immediates but |
| the child is not at depth-empty. */ |
| else if (subdir_entry->revision != dir_rev |
| || subdir_entry->lock_token |
| || subdir_entry->incomplete |
| || dot_entry->depth == svn_depth_empty |
| || dot_entry->depth == svn_depth_files |
| || (dot_entry->depth == svn_depth_immediates |
| && subdir_entry->depth != svn_depth_empty)) |
| SVN_ERR(reporter->set_path(report_baton, |
| this_path, |
| subdir_entry->revision, |
| subdir_entry->depth, |
| start_empty, |
| subdir_entry->lock_token, |
| iterpool)); |
| |
| if (SVN_DEPTH_IS_RECURSIVE(depth)) |
| SVN_ERR(report_revisions_and_depths(adm_access, this_path, |
| subdir_entry->revision, |
| reporter, report_baton, |
| notify_func, notify_baton, |
| restore_files, depth, |
| depth_compatibility_trick, |
| start_empty, |
| use_commit_times, |
| traversal_info, |
| iterpool)); |
| } /* end directory case */ |
| } /* end main entries loop */ |
| |
| /* We're done examining this dir's entries, so free everything. */ |
| svn_pool_destroy(subpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /*------------------------------------------------------------------*/ |
| /*** Public Interfaces ***/ |
| |
| |
| svn_error_t * |
| svn_wc_crawl_revisions3(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| const svn_ra_reporter3_t *reporter, |
| void *report_baton, |
| svn_boolean_t restore_files, |
| svn_depth_t depth, |
| svn_boolean_t depth_compatibility_trick, |
| svn_boolean_t use_commit_times, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| svn_wc_traversal_info_t *traversal_info, |
| apr_pool_t *pool) |
| { |
| svn_error_t *fserr, *err = SVN_NO_ERROR; |
| const svn_wc_entry_t *entry; |
| svn_revnum_t base_rev = SVN_INVALID_REVNUM; |
| svn_boolean_t missing = FALSE; |
| const svn_wc_entry_t *parent_entry = NULL; |
| svn_wc_notify_t *notify; |
| svn_boolean_t start_empty; |
| |
| /* The first thing we do is get the base_rev from the working copy's |
| ROOT_DIRECTORY. This is the first revnum that entries will be |
| compared to. */ |
| SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool)); |
| |
| if ((! entry) || ((entry->schedule == svn_wc_schedule_add) |
| && (entry->kind == svn_node_dir))) |
| { |
| /* There aren't any versioned paths to crawl which are known to |
| the repository. */ |
| SVN_ERR(svn_wc__entry_versioned(&parent_entry, |
| svn_path_dirname(path, pool), |
| adm_access, FALSE, pool)); |
| |
| base_rev = parent_entry->revision; |
| |
| /* If no versioned path exists, we use the requested depth, which |
| is the depth at which the new path should be brought in. Default |
| to infinity if no explicit depth was given. */ |
| if (depth == svn_depth_unknown) |
| depth = svn_depth_infinity; |
| |
| SVN_ERR(reporter->set_path(report_baton, "", base_rev, depth, |
| entry ? entry->incomplete : TRUE, |
| NULL, pool)); |
| SVN_ERR(reporter->delete_path(report_baton, "", pool)); |
| |
| /* Finish the report, which causes the update editor to be |
| driven. */ |
| SVN_ERR(reporter->finish_report(report_baton, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| base_rev = entry->revision; |
| |
| start_empty = entry->incomplete; |
| if (depth_compatibility_trick |
| && entry->depth <= svn_depth_immediates |
| && depth > entry->depth) |
| { |
| start_empty = TRUE; |
| } |
| |
| if (base_rev == SVN_INVALID_REVNUM) |
| { |
| const char *dirname = svn_path_dirname(path, pool); |
| SVN_ERR(svn_wc__entry_versioned(&parent_entry, dirname, adm_access, |
| FALSE, pool)); |
| base_rev = parent_entry->revision; |
| } |
| |
| /* The first call to the reporter merely informs it that the |
| top-level directory being updated is at BASE_REV. Its PATH |
| argument is ignored. */ |
| SVN_ERR(reporter->set_path(report_baton, "", base_rev, entry->depth, |
| start_empty, NULL, pool)); |
| |
| if (entry->schedule != svn_wc_schedule_delete) |
| { |
| apr_finfo_t info; |
| err = svn_io_stat(&info, path, APR_FINFO_MIN, pool); |
| if (err) |
| { |
| if (APR_STATUS_IS_ENOENT(err->apr_err)) |
| missing = TRUE; |
| svn_error_clear(err); |
| err = NULL; |
| } |
| } |
| |
| if (entry->kind == svn_node_dir) |
| { |
| if (missing) |
| { |
| /* Always report directories as missing; we can't recreate |
| them locally. */ |
| err = reporter->delete_path(report_baton, "", pool); |
| if (err) |
| goto abort_report; |
| } |
| else if (depth != svn_depth_empty) |
| { |
| /* Recursively crawl ROOT_DIRECTORY and report differing |
| revisions. */ |
| err = report_revisions_and_depths(adm_access, |
| "", |
| base_rev, |
| reporter, report_baton, |
| notify_func, notify_baton, |
| restore_files, depth, |
| depth_compatibility_trick, |
| start_empty, |
| use_commit_times, |
| traversal_info, |
| pool); |
| if (err) |
| goto abort_report; |
| } |
| } |
| |
| else if (entry->kind == svn_node_file) |
| { |
| const char *pdir, *bname; |
| |
| if (missing && restore_files) |
| { |
| /* Recreate file from text-base. */ |
| err = restore_file(path, adm_access, use_commit_times, pool); |
| if (err) |
| goto abort_report; |
| |
| /* Report the restoration to the caller. */ |
| if (notify_func != NULL) |
| { |
| notify = svn_wc_create_notify(path, svn_wc_notify_restore, |
| pool); |
| notify->kind = svn_node_file; |
| (*notify_func)(notify_baton, notify, pool); |
| } |
| } |
| |
| /* Split PATH into parent PDIR and basename BNAME. */ |
| svn_path_split(path, &pdir, &bname, pool); |
| if (! parent_entry) |
| { |
| err = svn_wc_entry(&parent_entry, pdir, adm_access, FALSE, pool); |
| if (err) |
| goto abort_report; |
| } |
| |
| if (parent_entry |
| && parent_entry->url |
| && entry->url |
| && strcmp(entry->url, |
| svn_path_url_add_component(parent_entry->url, |
| bname, pool))) |
| { |
| /* This file is disjoint with respect to its parent |
| directory. Since we are looking at the actual target of |
| the report (not some file in a subdirectory of a target |
| directory), and that target is a file, we need to pass an |
| empty string to link_path. */ |
| err = reporter->link_path(report_baton, |
| "", |
| entry->url, |
| entry->revision, |
| entry->depth, |
| FALSE, |
| entry->lock_token, |
| pool); |
| if (err) |
| goto abort_report; |
| } |
| else if (entry->revision != base_rev || entry->lock_token) |
| { |
| /* If this entry is a file node, we just want to report that |
| node's revision. Since we are looking at the actual target |
| of the report (not some file in a subdirectory of a target |
| directory), and that target is a file, we need to pass an |
| empty string to set_path. */ |
| err = reporter->set_path(report_baton, "", base_rev, entry->depth, |
| FALSE, |
| entry->lock_token, pool); |
| if (err) |
| goto abort_report; |
| } |
| } |
| |
| /* Finish the report, which causes the update editor to be driven. */ |
| return reporter->finish_report(report_baton, pool); |
| |
| abort_report: |
| /* Clean up the fs transaction. */ |
| if ((fserr = reporter->abort_report(report_baton, pool))) |
| { |
| fserr = svn_error_quick_wrap(fserr, _("Error aborting report")); |
| svn_error_compose(err, fserr); |
| } |
| return err; |
| } |
| |
| |
| /*** Compatibility wrapper: turns an svn_ra_reporter2_t into an |
| svn_ra_reporter3_t. |
| |
| This code looks like it duplicates code in libsvn_ra/ra_loader.c, |
| but it does not. That code makes an new thing look like an old |
| thing; this code makes an old thing look like a new thing. ***/ |
| |
| struct wrap_3to2_report_baton { |
| const svn_ra_reporter2_t *reporter; |
| void *baton; |
| }; |
| |
| static svn_error_t *wrap_3to2_set_path(void *report_baton, |
| const char *path, |
| svn_revnum_t revision, |
| svn_depth_t depth, |
| svn_boolean_t start_empty, |
| const char *lock_token, |
| apr_pool_t *pool) |
| { |
| struct wrap_3to2_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->set_path(wrb->baton, path, revision, start_empty, |
| lock_token, pool); |
| } |
| |
| static svn_error_t *wrap_3to2_delete_path(void *report_baton, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| struct wrap_3to2_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->delete_path(wrb->baton, path, pool); |
| } |
| |
| static svn_error_t *wrap_3to2_link_path(void *report_baton, |
| const char *path, |
| const char *url, |
| svn_revnum_t revision, |
| svn_depth_t depth, |
| svn_boolean_t start_empty, |
| const char *lock_token, |
| apr_pool_t *pool) |
| { |
| struct wrap_3to2_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->link_path(wrb->baton, path, url, revision, |
| start_empty, lock_token, pool); |
| } |
| |
| static svn_error_t *wrap_3to2_finish_report(void *report_baton, |
| apr_pool_t *pool) |
| { |
| struct wrap_3to2_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->finish_report(wrb->baton, pool); |
| } |
| |
| static svn_error_t *wrap_3to2_abort_report(void *report_baton, |
| apr_pool_t *pool) |
| { |
| struct wrap_3to2_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->abort_report(wrb->baton, pool); |
| } |
| |
| static const svn_ra_reporter3_t wrap_3to2_reporter = { |
| wrap_3to2_set_path, |
| wrap_3to2_delete_path, |
| wrap_3to2_link_path, |
| wrap_3to2_finish_report, |
| wrap_3to2_abort_report |
| }; |
| |
| svn_error_t * |
| svn_wc_crawl_revisions2(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| const svn_ra_reporter2_t *reporter, |
| void *report_baton, |
| svn_boolean_t restore_files, |
| svn_boolean_t recurse, |
| svn_boolean_t use_commit_times, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| svn_wc_traversal_info_t *traversal_info, |
| apr_pool_t *pool) |
| { |
| struct wrap_3to2_report_baton wrb; |
| wrb.reporter = reporter; |
| wrb.baton = report_baton; |
| |
| return svn_wc_crawl_revisions3(path, |
| adm_access, |
| &wrap_3to2_reporter, &wrb, |
| restore_files, |
| SVN_DEPTH_INFINITY_OR_FILES(recurse), |
| FALSE, |
| use_commit_times, |
| notify_func, |
| notify_baton, |
| traversal_info, |
| pool); |
| } |
| |
| |
| /*** Compatibility wrapper: turns an svn_ra_reporter_t into an |
| svn_ra_reporter2_t. |
| |
| This code looks like it duplicates code in libsvn_ra/ra_loader.c, |
| but it does not. That code makes an new thing look like an old |
| thing; this code makes an old thing look like a new thing. ***/ |
| |
| struct wrap_2to1_report_baton { |
| const svn_ra_reporter_t *reporter; |
| void *baton; |
| }; |
| |
| static svn_error_t *wrap_2to1_set_path(void *report_baton, |
| const char *path, |
| svn_revnum_t revision, |
| svn_boolean_t start_empty, |
| const char *lock_token, |
| apr_pool_t *pool) |
| { |
| struct wrap_2to1_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->set_path(wrb->baton, path, revision, start_empty, |
| pool); |
| } |
| |
| static svn_error_t *wrap_2to1_delete_path(void *report_baton, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| struct wrap_2to1_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->delete_path(wrb->baton, path, pool); |
| } |
| |
| static svn_error_t *wrap_2to1_link_path(void *report_baton, |
| const char *path, |
| const char *url, |
| svn_revnum_t revision, |
| svn_boolean_t start_empty, |
| const char *lock_token, |
| apr_pool_t *pool) |
| { |
| struct wrap_2to1_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->link_path(wrb->baton, path, url, revision, |
| start_empty, pool); |
| } |
| |
| static svn_error_t *wrap_2to1_finish_report(void *report_baton, |
| apr_pool_t *pool) |
| { |
| struct wrap_2to1_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->finish_report(wrb->baton, pool); |
| } |
| |
| static svn_error_t *wrap_2to1_abort_report(void *report_baton, |
| apr_pool_t *pool) |
| { |
| struct wrap_2to1_report_baton *wrb = report_baton; |
| |
| return wrb->reporter->abort_report(wrb->baton, pool); |
| } |
| |
| static const svn_ra_reporter2_t wrap_2to1_reporter = { |
| wrap_2to1_set_path, |
| wrap_2to1_delete_path, |
| wrap_2to1_link_path, |
| wrap_2to1_finish_report, |
| wrap_2to1_abort_report |
| }; |
| |
| svn_error_t * |
| svn_wc_crawl_revisions(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| const svn_ra_reporter_t *reporter, |
| void *report_baton, |
| svn_boolean_t restore_files, |
| svn_boolean_t recurse, |
| svn_boolean_t use_commit_times, |
| svn_wc_notify_func_t notify_func, |
| void *notify_baton, |
| svn_wc_traversal_info_t *traversal_info, |
| apr_pool_t *pool) |
| { |
| struct wrap_2to1_report_baton wrb; |
| svn_wc__compat_notify_baton_t nb; |
| |
| wrb.reporter = reporter; |
| wrb.baton = report_baton; |
| |
| nb.func = notify_func; |
| nb.baton = notify_baton; |
| |
| return svn_wc_crawl_revisions2(path, adm_access, &wrap_2to1_reporter, &wrb, |
| restore_files, recurse, use_commit_times, |
| svn_wc__compat_call_notify_func, &nb, |
| traversal_info, |
| pool); |
| } |
| |
| |
| /*** Copying stream ***/ |
| |
| /* A copying stream is a bit like the unix tee utility: |
| * |
| * It reads the SOURCE when asked for data and while returning it, |
| * also writes the same data to TARGET. |
| */ |
| struct copying_stream_baton |
| { |
| /* Stream to read input from. */ |
| svn_stream_t *source; |
| |
| /* Stream to write all data read to. */ |
| svn_stream_t *target; |
| }; |
| |
| |
| static svn_error_t * |
| read_handler_copy(void *baton, char *buffer, apr_size_t *len) |
| { |
| struct copying_stream_baton *btn = baton; |
| |
| SVN_ERR(svn_stream_read(btn->source, buffer, len)); |
| |
| return svn_stream_write(btn->target, buffer, len); |
| } |
| |
| static svn_error_t * |
| close_handler_copy(void *baton) |
| { |
| struct copying_stream_baton *btn = baton; |
| |
| SVN_ERR(svn_stream_close(btn->target)); |
| return svn_stream_close(btn->source); |
| } |
| |
| |
| /* Return a stream - allocated in POOL - which reads its input |
| * from SOURCE and, while returning that to the caller, at the |
| * same time writes that to TARGET. |
| */ |
| static svn_stream_t * |
| copying_stream(svn_stream_t *source, |
| svn_stream_t *target, |
| apr_pool_t *pool) |
| { |
| struct copying_stream_baton *baton; |
| svn_stream_t *stream; |
| |
| baton = apr_palloc(pool, sizeof (*baton)); |
| baton->source = source; |
| baton->target = target; |
| |
| stream = svn_stream_create(baton, pool); |
| svn_stream_set_read(stream, read_handler_copy); |
| svn_stream_set_close(stream, close_handler_copy); |
| |
| return stream; |
| } |
| |
| svn_error_t * |
| svn_wc_transmit_text_deltas2(const char **tempfile, |
| unsigned char digest[], |
| const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t fulltext, |
| const svn_delta_editor_t *editor, |
| void *file_baton, |
| apr_pool_t *pool) |
| { |
| const char *tmp_base; |
| svn_txdelta_window_handler_t handler; |
| void *wh_baton; |
| svn_txdelta_stream_t *txdelta_stream; |
| apr_file_t *basefile = NULL; |
| apr_file_t *tempbasefile; |
| const char *base_digest_hex = NULL; |
| const unsigned char *base_digest = NULL; |
| const unsigned char *local_digest = NULL; |
| svn_error_t *err; |
| const svn_wc_entry_t *ent; |
| svn_stream_t *base_stream; |
| svn_stream_t *local_stream; |
| apr_time_t wf_time; |
| |
| SVN_ERR(svn_wc_entry(&ent, path, adm_access, FALSE, pool)); |
| |
| /* Get timestamp of working file, to check for modifications during |
| commit. */ |
| SVN_ERR(svn_io_file_affected_time(&wf_time, path, pool)); |
| |
| /* Translated input */ |
| SVN_ERR(svn_wc_translated_stream(&local_stream, path, path, |
| adm_access, SVN_WC_TRANSLATE_TO_NF, pool)); |
| |
| |
| |
| tmp_base = svn_wc__text_base_path(path, TRUE, pool); |
| /* Alert the caller that we have created a temporary file that might |
| need to be cleaned up, if he asked for one. */ |
| if (tempfile) |
| { |
| *tempfile = tmp_base; |
| |
| /* Make an untranslated copy of the working file in the |
| administrative tmp area because a) we need to detranslate eol |
| and keywords anyway, and b) after the commit, we're going to |
| copy the tmp file to become the new text base anyway. */ |
| SVN_ERR(svn_io_file_open(&tempbasefile, tmp_base, |
| APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool)); |
| |
| /* Wrap the translated stream with a new stream that writes the |
| translated contents into the new text base file as we read from it. |
| Note that the new text base file will be closed when the new stream |
| is closed. */ |
| local_stream |
| = copying_stream(local_stream, |
| svn_stream_from_aprfile2(tempbasefile, FALSE, pool), |
| pool); |
| } |
| |
| if (! fulltext) |
| { |
| if (! ent->checksum) |
| { |
| /*### FIXME: The entries file should hold a checksum */ |
| unsigned char tmp_digest[APR_MD5_DIGESTSIZE]; |
| |
| /* If there's no checksum in this entry, calculate one */ |
| const char *tb = svn_wc__text_base_path (path, FALSE, pool); |
| |
| SVN_ERR (svn_io_file_checksum (tmp_digest, tb, pool)); |
| base_digest_hex = svn_md5_digest_to_cstring_display(tmp_digest, pool); |
| } |
| else |
| base_digest_hex = ent->checksum; |
| |
| SVN_ERR(svn_wc__open_text_base(&basefile, path, APR_READ, pool)); |
| } |
| |
| /* Tell the editor that we're about to apply a textdelta to the |
| file baton; the editor returns to us a window consumer and baton. */ |
| SVN_ERR(editor->apply_textdelta |
| (file_baton, base_digest_hex, pool, &handler, &wh_baton)); |
| |
| /* Create a text-delta stream object that pulls |
| data out of the two files. */ |
| base_stream = svn_stream_from_aprfile2(basefile, TRUE, pool); |
| if (! fulltext) |
| base_stream |
| = svn_stream_checksummed(base_stream, &base_digest, NULL, TRUE, pool); |
| |
| svn_txdelta(&txdelta_stream, base_stream, local_stream, pool); |
| |
| /* Pull windows from the delta stream and feed to the consumer. */ |
| err = svn_txdelta_send_txstream(txdelta_stream, handler, wh_baton, pool); |
| |
| /* Close the two streams to force writing the digest, |
| if we already have an error, ignore this one. */ |
| if (err) |
| { |
| svn_error_clear(svn_stream_close(base_stream)); |
| svn_error_clear(svn_stream_close(local_stream)); |
| } |
| else |
| { |
| SVN_ERR(svn_stream_close(base_stream)); |
| SVN_ERR(svn_stream_close(local_stream)); |
| } |
| |
| /* If we have an error, it may be caused by a corrupt text base. |
| Check the checksum and discard `err' if they don't match. */ |
| if (! fulltext && ent->checksum && base_digest) |
| { |
| /*### FIXME: The entries file should hold a checksum, |
| meaning the above condition should not include ent->checksum */ |
| |
| base_digest_hex = svn_md5_digest_to_cstring_display(base_digest, pool); |
| |
| if (strcmp(base_digest_hex, ent->checksum) != 0) |
| { |
| /* The entry checksum does not match the actual text |
| base checksum. Extreme badness. Of course, |
| theoretically we could just switch to |
| fulltext transmission here, and everything would |
| work fine; after all, we're going to replace the |
| text base with a new one in a moment anyway, and |
| we'd fix the checksum then. But it's better to |
| error out. People should know that their text |
| bases are getting corrupted, so they can |
| investigate. Other commands could be affected, |
| too, such as `svn diff'. */ |
| |
| /* Deliberately ignore errors; the error about the |
| checksum mismatch is more important to return. */ |
| svn_error_clear(err); |
| svn_error_clear(svn_io_remove_file(tmp_base, pool)); |
| |
| return svn_error_createf |
| (SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL, |
| _("Checksum mismatch for '%s'; " |
| "expected: '%s', actual: '%s'"), |
| svn_path_local_style(svn_wc__text_base_path(path, FALSE, pool), |
| pool), |
| ent->checksum, base_digest_hex); |
| } |
| } |
| |
| /* Now, handle that delta transmission error if any, so we can stop |
| thinking about it after this point. */ |
| SVN_ERR_W(err, apr_psprintf(pool, |
| _("While preparing '%s' for commit"), |
| svn_path_local_style(path, pool))); |
| |
| /* Close base file, if it was opened. */ |
| if (basefile) |
| SVN_ERR(svn_wc__close_text_base(basefile, path, 0, pool)); |
| |
| local_digest = svn_txdelta_md5_digest(txdelta_stream); |
| |
| if (digest) |
| memcpy(digest, local_digest, APR_MD5_DIGESTSIZE); |
| |
| /* Close the file baton, and get outta here. */ |
| return editor->close_file |
| (file_baton, svn_md5_digest_to_cstring(local_digest, pool), pool); |
| } |
| |
| svn_error_t * |
| svn_wc_transmit_text_deltas(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t fulltext, |
| const svn_delta_editor_t *editor, |
| void *file_baton, |
| const char **tempfile, |
| apr_pool_t *pool) |
| { |
| return svn_wc_transmit_text_deltas2(tempfile, NULL, path, adm_access, |
| fulltext, editor, file_baton, pool); |
| } |
| |
| |
| svn_error_t * |
| svn_wc_transmit_prop_deltas(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| const svn_wc_entry_t *entry, |
| const svn_delta_editor_t *editor, |
| void *baton, |
| const char **tempfile, |
| apr_pool_t *pool) |
| { |
| int i; |
| apr_array_header_t *propmods; |
| |
| if (tempfile) |
| *tempfile = NULL; |
| |
| /* Get an array of local changes by comparing the hashes. */ |
| SVN_ERR(svn_wc_get_prop_diffs(&propmods, NULL, |
| path, adm_access, pool)); |
| |
| /* Apply each local change to the baton */ |
| for (i = 0; i < propmods->nelts; i++) |
| { |
| const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t); |
| if (entry->kind == svn_node_file) |
| SVN_ERR(editor->change_file_prop(baton, p->name, p->value, pool)); |
| else |
| SVN_ERR(editor->change_dir_prop(baton, p->name, p->value, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |