/* Return in *LINK_TARGET_ABSPATH the absolute path the symlink at * LOCAL_ABSPATH is pointing to. Perform all allocations in POOL. */ static svn_error_t * read_link_target(const char **link_target_abspath, const char *local_abspath, apr_pool_t *pool) { svn_string_t *link_target; const char *canon_link_target; SVN_ERR(svn_io_read_link(&link_target, local_abspath, pool)); if (link_target->len == 0) return svn_error_createf(SVN_ERR_WC_NOT_SYMLINK, NULL, _("The symlink at '%s' points nowhere"), svn_dirent_local_style(local_abspath, pool)); canon_link_target = svn_dirent_canonicalize(link_target->data, pool); /* Treat relative symlinks as relative to LOCAL_ABSPATH's parent. */ if (!svn_dirent_is_absolute(canon_link_target)) canon_link_target = svn_dirent_join(svn_dirent_dirname(local_abspath, pool), canon_link_target, pool); /* Collapse any .. in the symlink part of the path. */ if (svn_path_is_backpath_present(canon_link_target)) SVN_ERR(svn_dirent_get_absolute(link_target_abspath, canon_link_target, pool)); else *link_target_abspath = canon_link_target; return SVN_NO_ERROR; }
/* Remove the conflict markers of NODE_ABSPATH, that were left over after copying NODE_ABSPATH from SRC_ABSPATH. Only use this function when you know what you're doing. This function explicitly ignores some case insensitivity issues! */ static svn_error_t * remove_node_conflict_markers(svn_wc__db_t *db, const char *src_abspath, const char *node_abspath, apr_pool_t *scratch_pool) { svn_skel_t *conflict; SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath, scratch_pool, scratch_pool)); /* Do we have conflict markers that should be removed? */ if (conflict != NULL) { const apr_array_header_t *markers; int i; const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool); const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool); SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath, conflict, scratch_pool, scratch_pool)); /* No iterpool: Maximum number of possible conflict markers is 4 */ for (i = 0; markers && (i < markers->nelts); i++) { const char *marker_abspath; const char *child_relpath; const char *child_abpath; marker_abspath = APR_ARRAY_IDX(markers, i, const char *); child_relpath = svn_dirent_is_child(src_dir, marker_abspath, NULL); if (child_relpath) { child_abpath = svn_dirent_join(dst_dir, child_relpath, scratch_pool); SVN_ERR(svn_io_remove_file2(child_abpath, TRUE, scratch_pool)); } } } return SVN_NO_ERROR; }
/* Public Interface */ svn_error_t * svn_wc_diff6(svn_wc_context_t *wc_ctx, const char *local_abspath, const svn_wc_diff_callbacks4_t *callbacks, void *callback_baton, svn_depth_t depth, svn_boolean_t ignore_ancestry, svn_boolean_t show_copies_as_adds, svn_boolean_t use_git_diff_format, const apr_array_header_t *changelist_filter, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool) { struct diff_baton eb = { 0 }; svn_kind_t kind; svn_boolean_t get_all; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, FALSE, scratch_pool)); if (kind == svn_kind_dir) eb.anchor_abspath = local_abspath; else eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); eb.db = wc_ctx->db; eb.callbacks = callbacks; eb.callback_baton = callback_baton; eb.ignore_ancestry = ignore_ancestry; eb.show_copies_as_adds = show_copies_as_adds; eb.use_git_diff_format = use_git_diff_format; eb.empty_file = NULL; eb.pool = scratch_pool; if (changelist_filter && changelist_filter->nelts) SVN_ERR(svn_hash_from_cstring_keys(&eb.changelist_hash, changelist_filter, scratch_pool)); if (show_copies_as_adds || use_git_diff_format) get_all = TRUE; /* We need unmodified descendants of copies */ else get_all = FALSE; /* Walk status handles files and directories */ SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth, get_all, TRUE /* no_ignore */, FALSE /* ignore_text_mods */, NULL /* ignore_patterns */, diff_status_callback, &eb, cancel_func, cancel_baton, scratch_pool)); return SVN_NO_ERROR; }
void JNIUtil::putErrorsInTrace(svn_error_t *err, std::vector<jobject> &stackTrace) { if (!err) return; JNIEnv *env = getEnv(); // First, put all our child errors in the stack trace putErrorsInTrace(err->child, stackTrace); // Now, put our own error in the stack trace jclass stClazz = env->FindClass("java/lang/StackTraceElement"); if (isJavaExceptionThrown()) return; static jmethodID ctor_mid = 0; if (ctor_mid == 0) { ctor_mid = env->GetMethodID(stClazz, "<init>", "(Ljava/lang/String;Ljava/lang/String;" "Ljava/lang/String;I)V"); if (isJavaExceptionThrown()) return; } jstring jdeclClass = makeJString("native"); if (isJavaExceptionThrown()) return; char *tmp_path; char *path = svn_dirent_dirname(err->file, err->pool); while ((tmp_path = strchr(path, '/'))) *tmp_path = '.'; jstring jmethodName = makeJString(path); if (isJavaExceptionThrown()) return; jstring jfileName = makeJString(svn_dirent_basename(err->file, err->pool)); if (isJavaExceptionThrown()) return; jobject jelement = env->NewObject(stClazz, ctor_mid, jdeclClass, jmethodName, jfileName, (jint) err->line); stackTrace.push_back(jelement); env->DeleteLocalRef(stClazz); env->DeleteLocalRef(jdeclClass); env->DeleteLocalRef(jmethodName); env->DeleteLocalRef(jfileName); }
svn_error_t * svn_client_add4(const char *path, svn_depth_t depth, svn_boolean_t force, svn_boolean_t no_ignore, svn_boolean_t add_parents, svn_client_ctx_t *ctx, apr_pool_t *pool) { const char *parent_abspath; const char *local_abspath; const char *existing_parent_abspath; if (svn_path_is_url(path)) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("'%s' is not a local path"), path); SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); /* ### this is a hack. ### before we switched to absolute paths, if a user tried to do ### 'svn add .', PATH would be "" and PARENT_PATH would also be "", ### thus emulating the behavior below. Now that we are using ### absolute paths, svn_dirent_dirname() doesn't behave the same way ### w.r.t. '.', so we need to include the following hack. This ### behavior is tested in schedule_tests-11. */ if (path[0] == 0) parent_abspath = local_abspath; else parent_abspath = svn_dirent_dirname(local_abspath, pool); existing_parent_abspath = NULL; if (add_parents) { apr_pool_t *subpool; const char *existing_parent_abspath2; subpool = svn_pool_create(pool); SVN_ERR(find_existing_parent(&existing_parent_abspath2, ctx, parent_abspath, pool, subpool)); if (strcmp(existing_parent_abspath2, parent_abspath) != 0) existing_parent_abspath = existing_parent_abspath2; svn_pool_destroy(subpool); } SVN_WC__CALL_WITH_WRITE_LOCK( add(local_abspath, depth, force, no_ignore, existing_parent_abspath, ctx, pool), ctx->wc_ctx, existing_parent_abspath ? existing_parent_abspath : parent_abspath, FALSE /* lock_anchor */, pool); return SVN_NO_ERROR; }
svn_error_t * sbox_wc_add(svn_test__sandbox_t *b, const char *path) { const char *parent_abspath; path = sbox_wc_path(b, path); parent_abspath = svn_dirent_dirname(path, b->pool); SVN_ERR(svn_wc__acquire_write_lock(NULL, b->wc_ctx, parent_abspath, FALSE, b->pool, b->pool)); SVN_ERR(svn_wc_add_from_disk2(b->wc_ctx, path, NULL /*props*/, NULL, NULL, b->pool)); SVN_ERR(svn_wc__release_write_lock(b->wc_ctx, parent_abspath, b->pool)); return SVN_NO_ERROR; }
svn_error_t * sbox_wc_copy(svn_test__sandbox_t *b, const char *from_path, const char *to_path) { const char *parent_abspath; from_path = sbox_wc_path(b, from_path); to_path = sbox_wc_path(b, to_path); parent_abspath = svn_dirent_dirname(to_path, b->pool); SVN_ERR(svn_wc__acquire_write_lock(NULL, b->wc_ctx, parent_abspath, FALSE, b->pool, b->pool)); SVN_ERR(svn_wc_copy3(b->wc_ctx, from_path, to_path, FALSE, NULL, NULL, NULL, NULL, b->pool)); SVN_ERR(svn_wc__release_write_lock(b->wc_ctx, parent_abspath, b->pool)); return SVN_NO_ERROR; }
/* Go up the directory tree from LOCAL_ABSPATH, looking for a versioned * directory. If found, return its path in *EXISTING_PARENT_ABSPATH. * Otherwise, return SVN_ERR_CLIENT_NO_VERSIONED_PARENT. */ static svn_error_t * find_existing_parent(const char **existing_parent_abspath, svn_client_ctx_t *ctx, const char *local_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_node_kind_t kind; const char *parent_abspath; svn_wc_context_t *wc_ctx = ctx->wc_ctx; SVN_ERR(svn_wc_read_kind(&kind, wc_ctx, local_abspath, FALSE, scratch_pool)); if (kind == svn_node_dir) { svn_boolean_t is_deleted; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, local_abspath, scratch_pool)); if (!is_deleted) { *existing_parent_abspath = apr_pstrdup(result_pool, local_abspath); return SVN_NO_ERROR; } } if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) return svn_error_create(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, NULL); if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, scratch_pool), scratch_pool)) return svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, NULL, _("'%s' ends in a reserved name"), svn_dirent_local_style(local_abspath, scratch_pool)); parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); SVN_ERR(find_existing_parent(existing_parent_abspath, ctx, parent_abspath, result_pool, scratch_pool)); return SVN_NO_ERROR; }
svn_error_t * sbox_wc_delete(svn_test__sandbox_t *b, const char *path) { const char *abspath = sbox_wc_path(b, path); const char *dir_abspath = svn_dirent_dirname(abspath, b->pool); const char *lock_root_abspath; SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, b->wc_ctx, dir_abspath, FALSE, b->pool, b->pool)); SVN_ERR(svn_wc_delete4(b->wc_ctx, abspath, FALSE, TRUE, NULL, NULL, /* cancel baton + func */ NULL, NULL, /* notify baton + func */ b->pool)); SVN_ERR(svn_wc__release_write_lock(b->wc_ctx, lock_root_abspath, b->pool)); return SVN_NO_ERROR; }
/* Write the format number and maximum number of files per directory to a new format file in PATH, overwriting a previously existing file. Use POOL for temporary allocation. (This implementation is largely stolen from libsvn_fs_fs/fs_fs.c.) */ static svn_error_t * write_format(const char *path, int format, int max_files_per_dir, apr_pool_t *pool) { const char *contents; path = svn_dirent_join(path, "format", pool); if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) { if (max_files_per_dir) contents = apr_psprintf(pool, "%d\n" "layout sharded %d\n", format, max_files_per_dir); else contents = apr_psprintf(pool, "%d\n" "layout linear", format); } else { contents = apr_psprintf(pool, "%d\n", format); } { const char *path_tmp; SVN_ERR(svn_io_write_unique(&path_tmp, svn_dirent_dirname(path, pool), contents, strlen(contents), svn_io_file_del_none, pool)); /* rename the temp file as the real destination */ SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); } /* And set the perms to make it read only */ return svn_io_set_file_read_only(path, FALSE, pool); }
svn_error_t * svn_config_write_auth_data(apr_hash_t *hash, const char *cred_kind, const char *realmstring, const char *config_dir, apr_pool_t *pool) { svn_stream_t *stream; const char *auth_path, *tmp_path; SVN_ERR(svn_auth__file_path(&auth_path, cred_kind, realmstring, config_dir, pool)); if (! auth_path) return svn_error_create(SVN_ERR_NO_AUTH_FILE_PATH, NULL, _("Unable to locate auth file")); /* Add the realmstring to the hash, so programs (or users) can verify exactly which set of credentials this file holds. ### What if realmstring key is already in the hash? */ svn_hash_sets(hash, SVN_CONFIG_REALMSTRING_KEY, svn_string_create(realmstring, pool)); SVN_ERR_W(svn_stream_open_unique(&stream, &tmp_path, svn_dirent_dirname(auth_path, pool), svn_io_file_del_on_pool_cleanup, pool, pool), _("Unable to open auth file for writing")); SVN_ERR_W(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool), apr_psprintf(pool, _("Error writing hash to '%s'"), svn_dirent_local_style(auth_path, pool))); SVN_ERR(svn_stream_close(stream)); SVN_ERR(svn_io_file_rename2(tmp_path, auth_path, FALSE, pool)); /* To be nice, remove the realmstring from the hash again, just in case the caller wants their hash unchanged. ### Should we also do this when a write error occurs? */ svn_hash_sets(hash, SVN_CONFIG_REALMSTRING_KEY, NULL); return SVN_NO_ERROR; }
svn_error_t * sbox_wc_revert(svn_test__sandbox_t *b, const char *path, svn_depth_t depth) { const char *abspath = sbox_wc_path(b, path); const char *dir_abspath; const char *lock_root_abspath; if (strcmp(abspath, b->wc_abspath)) dir_abspath = svn_dirent_dirname(abspath, b->pool); else dir_abspath = abspath; SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, b->wc_ctx, dir_abspath, FALSE /* lock_anchor */, b->pool, b->pool)); SVN_ERR(svn_wc_revert4(b->wc_ctx, abspath, depth, FALSE, NULL, NULL, NULL, /* cancel baton + func */ NULL, NULL, /* notify baton + func */ b->pool)); SVN_ERR(svn_wc__release_write_lock(b->wc_ctx, lock_root_abspath, b->pool)); return SVN_NO_ERROR; }
static svn_error_t * file_textdelta(void *file_baton, const char *base_checksum, apr_pool_t *result_pool, svn_txdelta_window_handler_t *handler, void **handler_baton) { struct file_baton_t *fb = file_baton; const char *target_dir = svn_dirent_dirname(fb->local_abspath, fb->pool); svn_error_t *err; svn_stream_t *source; svn_stream_t *target; SVN_ERR_ASSERT(! fb->writing_file); err = svn_stream_open_readonly(&source, fb->local_abspath, fb->pool, fb->pool); if (err && APR_STATUS_IS_ENOENT(err->apr_err)) { svn_error_clear(err); source = svn_stream_empty(fb->pool); } else SVN_ERR(err); SVN_ERR(svn_stream_open_unique(&target, &fb->writing_file, target_dir, svn_io_file_del_none, fb->pool, fb->pool)); svn_txdelta_apply(source, target, fb->digest, fb->local_abspath, fb->pool, /* Provide the handler directly */ handler, handler_baton); return SVN_NO_ERROR; }
svn_error_t * svn_wc_copy3(svn_wc_context_t *wc_ctx, const char *src_abspath, const char *dst_abspath, svn_boolean_t metadata_only, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *scratch_pool) { /* Verify that we have the required write lock. */ SVN_ERR(svn_wc__write_check(wc_ctx->db, svn_dirent_dirname(dst_abspath, scratch_pool), scratch_pool)); return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath, metadata_only, FALSE /* is_move */, TRUE /* allow_mixed_revisions */, cancel_func, cancel_baton, notify_func, notify_baton, scratch_pool)); }
/* * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, * either return an error to be displayed, or set *EXIT_CODE to non-zero and * return SVN_NO_ERROR. */ static svn_error_t * sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) { enum run_mode run_mode = run_mode_unspecified; svn_boolean_t foreground = FALSE; apr_socket_t *sock; apr_sockaddr_t *sa; svn_error_t *err; apr_getopt_t *os; int opt; serve_params_t params; const char *arg; apr_status_t status; #ifndef WIN32 apr_proc_t proc; #endif svn_boolean_t is_multi_threaded; enum connection_handling_mode handling_mode = CONNECTION_DEFAULT; svn_boolean_t cache_fulltexts = TRUE; svn_boolean_t cache_txdeltas = TRUE; svn_boolean_t cache_revprops = FALSE; svn_boolean_t use_block_read = FALSE; apr_uint16_t port = SVN_RA_SVN_PORT; const char *host = NULL; int family = APR_INET; apr_int32_t sockaddr_info_flags = 0; #if APR_HAVE_IPV6 svn_boolean_t prefer_v6 = FALSE; #endif svn_boolean_t quiet = FALSE; svn_boolean_t is_version = FALSE; int mode_opt_count = 0; int handling_opt_count = 0; const char *config_filename = NULL; const char *pid_filename = NULL; const char *log_filename = NULL; svn_node_kind_t kind; apr_size_t min_thread_count = THREADPOOL_MIN_SIZE; apr_size_t max_thread_count = THREADPOOL_MAX_SIZE; #ifdef SVN_HAVE_SASL SVN_ERR(cyrus_init(pool)); #endif /* Check library versions */ SVN_ERR(check_lib_versions()); /* Initialize the FS library. */ SVN_ERR(svn_fs_initialize(pool)); SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); params.root = "/"; params.tunnel = FALSE; params.tunnel_user = NULL; params.read_only = FALSE; params.base = NULL; params.cfg = NULL; params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT; params.logger = NULL; params.config_pool = NULL; params.authz_pool = NULL; params.fs_config = NULL; params.vhost = FALSE; params.username_case = CASE_ASIS; params.memory_cache_size = (apr_uint64_t)-1; params.zero_copy_limit = 0; params.error_check_interval = 4096; while (1) { status = apr_getopt_long(os, svnserve__options, &opt, &arg); if (APR_STATUS_IS_EOF(status)) break; if (status != APR_SUCCESS) { usage(argv[0], pool); *exit_code = EXIT_FAILURE; return SVN_NO_ERROR; } switch (opt) { case '6': #if APR_HAVE_IPV6 prefer_v6 = TRUE; #endif /* ### Maybe error here if we don't have IPV6 support? */ break; case 'h': help(pool); return SVN_NO_ERROR; case 'q': quiet = TRUE; break; case SVNSERVE_OPT_VERSION: is_version = TRUE; break; case 'd': if (run_mode != run_mode_daemon) { run_mode = run_mode_daemon; mode_opt_count++; } break; case SVNSERVE_OPT_FOREGROUND: foreground = TRUE; break; case SVNSERVE_OPT_SINGLE_CONN: handling_mode = connection_mode_single; handling_opt_count++; break; case 'i': if (run_mode != run_mode_inetd) { run_mode = run_mode_inetd; mode_opt_count++; } break; case SVNSERVE_OPT_LISTEN_PORT: { apr_uint64_t val; err = svn_cstring_strtoui64(&val, arg, 0, APR_UINT16_MAX, 10); if (err) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err, _("Invalid port '%s'"), arg); port = (apr_uint16_t)val; } break; case SVNSERVE_OPT_LISTEN_HOST: host = arg; break; case 't': if (run_mode != run_mode_tunnel) { run_mode = run_mode_tunnel; mode_opt_count++; } break; case SVNSERVE_OPT_TUNNEL_USER: params.tunnel_user = arg; break; case 'X': if (run_mode != run_mode_listen_once) { run_mode = run_mode_listen_once; mode_opt_count++; } break; case 'r': SVN_ERR(svn_utf_cstring_to_utf8(¶ms.root, arg, pool)); SVN_ERR(svn_io_check_resolved_path(params.root, &kind, pool)); if (kind != svn_node_dir) { return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("Root path '%s' does not exist " "or is not a directory"), params.root); } params.root = svn_dirent_internal_style(params.root, pool); SVN_ERR(svn_dirent_get_absolute(¶ms.root, params.root, pool)); break; case 'R': params.read_only = TRUE; break; case 'T': handling_mode = connection_mode_thread; handling_opt_count++; break; case 'c': params.compression_level = atoi(arg); if (params.compression_level < SVN_DELTA_COMPRESSION_LEVEL_NONE) params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE; if (params.compression_level > SVN_DELTA_COMPRESSION_LEVEL_MAX) params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_MAX; break; case 'M': params.memory_cache_size = 0x100000 * apr_strtoi64(arg, NULL, 0); break; case SVNSERVE_OPT_CACHE_TXDELTAS: cache_txdeltas = svn_tristate__from_word(arg) == svn_tristate_true; break; case SVNSERVE_OPT_CACHE_FULLTEXTS: cache_fulltexts = svn_tristate__from_word(arg) == svn_tristate_true; break; case SVNSERVE_OPT_CACHE_REVPROPS: cache_revprops = svn_tristate__from_word(arg) == svn_tristate_true; break; case SVNSERVE_OPT_BLOCK_READ: use_block_read = svn_tristate__from_word(arg) == svn_tristate_true; break; case SVNSERVE_OPT_CLIENT_SPEED: { apr_size_t bandwidth = (apr_size_t)apr_strtoi64(arg, NULL, 0); /* for slower clients, don't try anything fancy */ if (bandwidth >= 1000) { /* block other clients for at most 1 ms (at full bandwidth). Note that the send buffer is 16kB anyways. */ params.zero_copy_limit = bandwidth * 120; /* check for aborted connections at the same rate */ params.error_check_interval = bandwidth * 120; } } break; case SVNSERVE_OPT_MIN_THREADS: min_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0); break; case SVNSERVE_OPT_MAX_THREADS: max_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0); break; #ifdef WIN32 case SVNSERVE_OPT_SERVICE: if (run_mode != run_mode_service) { run_mode = run_mode_service; mode_opt_count++; } break; #endif case SVNSERVE_OPT_CONFIG_FILE: SVN_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool)); config_filename = svn_dirent_internal_style(config_filename, pool); SVN_ERR(svn_dirent_get_absolute(&config_filename, config_filename, pool)); break; case SVNSERVE_OPT_PID_FILE: SVN_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool)); pid_filename = svn_dirent_internal_style(pid_filename, pool); SVN_ERR(svn_dirent_get_absolute(&pid_filename, pid_filename, pool)); break; case SVNSERVE_OPT_VIRTUAL_HOST: params.vhost = TRUE; break; case SVNSERVE_OPT_LOG_FILE: SVN_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool)); log_filename = svn_dirent_internal_style(log_filename, pool); SVN_ERR(svn_dirent_get_absolute(&log_filename, log_filename, pool)); break; } } if (is_version) { SVN_ERR(version(quiet, pool)); return SVN_NO_ERROR; } if (os->ind != argc) { usage(argv[0], pool); *exit_code = EXIT_FAILURE; return SVN_NO_ERROR; } if (mode_opt_count != 1) { svn_error_clear(svn_cmdline_fputs( #ifdef WIN32 _("You must specify exactly one of -d, -i, -t, " "--service or -X.\n"), #else _("You must specify exactly one of -d, -i, -t or -X.\n"), #endif stderr, pool)); usage(argv[0], pool); *exit_code = EXIT_FAILURE; return SVN_NO_ERROR; } if (handling_opt_count > 1) { svn_error_clear(svn_cmdline_fputs( _("You may only specify one of -T or --single-thread\n"), stderr, pool)); usage(argv[0], pool); *exit_code = EXIT_FAILURE; return SVN_NO_ERROR; } /* construct object pools */ is_multi_threaded = handling_mode == connection_mode_thread; params.fs_config = apr_hash_make(pool); svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, cache_txdeltas ? "1" :"0"); svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, cache_fulltexts ? "1" :"0"); svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, cache_revprops ? "2" :"0"); svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ, use_block_read ? "1" :"0"); SVN_ERR(svn_repos__config_pool_create(¶ms.config_pool, is_multi_threaded, pool)); SVN_ERR(svn_repos__authz_pool_create(¶ms.authz_pool, params.config_pool, is_multi_threaded, pool)); /* If a configuration file is specified, load it and any referenced * password and authorization files. */ if (config_filename) { params.base = svn_dirent_dirname(config_filename, pool); SVN_ERR(svn_repos__config_pool_get(¶ms.cfg, NULL, params.config_pool, config_filename, TRUE, /* must_exist */ FALSE, /* names_case_sensitive */ NULL, pool)); } if (log_filename) SVN_ERR(logger__create(¶ms.logger, log_filename, pool)); else if (run_mode == run_mode_listen_once) SVN_ERR(logger__create_for_stderr(¶ms.logger, pool)); if (params.tunnel_user && run_mode != run_mode_tunnel) { return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Option --tunnel-user is only valid in tunnel mode")); } if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel) { apr_pool_t *connection_pool; svn_ra_svn_conn_t *conn; svn_stream_t *stdin_stream; svn_stream_t *stdout_stream; params.tunnel = (run_mode == run_mode_tunnel); apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null, redirect_stdout); SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool)); SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); /* Use a subpool for the connection to ensure that if SASL is used * the pool cleanup handlers that call sasl_dispose() (connection_pool) * and sasl_done() (pool) are run in the right order. See issue #3664. */ connection_pool = svn_pool_create(pool); conn = svn_ra_svn_create_conn4(NULL, stdin_stream, stdout_stream, params.compression_level, params.zero_copy_limit, params.error_check_interval, connection_pool); err = serve(conn, ¶ms, connection_pool); svn_pool_destroy(connection_pool); return err; } #ifdef WIN32 /* If svnserve needs to run as a Win32 service, then we need to coordinate with the Service Control Manager (SCM) before continuing. This function call registers the svnserve.exe process with the SCM, waits for the "start" command from the SCM (which will come very quickly), and confirms that those steps succeeded. After this call succeeds, the service is free to run. At some point in the future, the SCM will send a message to the service, requesting that it stop. This is translated into a call to winservice_notify_stop(). The service is then responsible for cleanly terminating. We need to do this before actually starting the service logic (opening files, sockets, etc.) because the SCM wants you to connect *first*, then do your service-specific logic. If the service process takes too long to connect to the SCM, then the SCM will decide that the service is busted, and will give up on it. */ if (run_mode == run_mode_service) { err = winservice_start(); if (err) { svn_handle_error2(err, stderr, FALSE, "svnserve: "); /* This is the most common error. It means the user started svnserve from a shell, and specified the --service argument. svnserve cannot be started, as a service, in this way. The --service argument is valid only valid if svnserve is started by the SCM. */ if (err->apr_err == APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)) { svn_error_clear(svn_cmdline_fprintf(stderr, pool, _("svnserve: The --service flag is only valid if the" " process is started by the Service Control Manager.\n"))); } svn_error_clear(err); *exit_code = EXIT_FAILURE; return SVN_NO_ERROR; } /* The service is now in the "starting" state. Before the SCM will consider the service "started", this thread must call the winservice_running() function. */ } #endif /* WIN32 */ /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get APR_UNSPEC, because it may give us back an IPV6 address even if we can't create IPV6 sockets. */ #if APR_HAVE_IPV6 #ifdef MAX_SECS_TO_LINGER /* ### old APR interface */ status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool); #else status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP, pool); #endif if (status == 0) { apr_socket_close(sock); family = APR_UNSPEC; if (prefer_v6) { if (host == NULL) host = "::"; sockaddr_info_flags = APR_IPV6_ADDR_OK; } else { if (host == NULL) host = "0.0.0.0"; sockaddr_info_flags = APR_IPV4_ADDR_OK; } } #endif status = apr_sockaddr_info_get(&sa, host, family, port, sockaddr_info_flags, pool); if (status) { return svn_error_wrap_apr(status, _("Can't get address info")); } #ifdef MAX_SECS_TO_LINGER /* ### old APR interface */ status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool); #else status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP, pool); #endif if (status) { return svn_error_wrap_apr(status, _("Can't create server socket")); } /* Prevents "socket in use" errors when server is killed and quickly * restarted. */ status = apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1); if (status) { return svn_error_wrap_apr(status, _("Can't set options on server socket")); } status = apr_socket_bind(sock, sa); if (status) { return svn_error_wrap_apr(status, _("Can't bind server socket")); } status = apr_socket_listen(sock, ACCEPT_BACKLOG); if (status) { return svn_error_wrap_apr(status, _("Can't listen on server socket")); } #if APR_HAS_FORK if (run_mode != run_mode_listen_once && !foreground) /* ### ignoring errors... */ apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); apr_signal(SIGCHLD, sigchld_handler); #endif #ifdef SIGPIPE /* Disable SIGPIPE generation for the platforms that have it. */ apr_signal(SIGPIPE, SIG_IGN); #endif #ifdef SIGXFSZ /* Disable SIGXFSZ generation for the platforms that have it, otherwise * working with large files when compiled against an APR that doesn't have * large file support will crash the program, which is uncool. */ apr_signal(SIGXFSZ, SIG_IGN); #endif if (pid_filename) SVN_ERR(write_pid_file(pid_filename, pool)); #ifdef WIN32 status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock); if (status) winservice_svnserve_accept_socket = INVALID_SOCKET; /* At this point, the service is "running". Notify the SCM. */ if (run_mode == run_mode_service) winservice_running(); #endif /* Configure FS caches for maximum efficiency with svnserve. * For pre-forked (i.e. multi-processed) mode of operation, * keep the per-process caches smaller than the default. * Also, apply the respective command line parameters, if given. */ { svn_cache_config_t settings = *svn_cache_config_get(); if (params.memory_cache_size != -1) settings.cache_size = params.memory_cache_size; settings.single_threaded = TRUE; if (handling_mode == connection_mode_thread) { #if APR_HAS_THREADS settings.single_threaded = FALSE; #else /* No requests will be processed at all * (see "switch (handling_mode)" code further down). * But if they were, some other synchronization code * would need to take care of securing integrity of * APR-based structures. That would include our caches. */ #endif } svn_cache_config_set(&settings); } #if APR_HAS_THREADS SVN_ERR(svn_root_pools__create(&connection_pools)); if (handling_mode == connection_mode_thread) { /* create the thread pool with a valid range of threads */ if (max_thread_count < 1) max_thread_count = 1; if (min_thread_count > max_thread_count) min_thread_count = max_thread_count; status = apr_thread_pool_create(&threads, min_thread_count, max_thread_count, pool); if (status) { return svn_error_wrap_apr(status, _("Can't create thread pool")); } /* let idle threads linger for a while in case more requests are coming in */ apr_thread_pool_idle_wait_set(threads, THREADPOOL_THREAD_IDLE_LIMIT); /* don't queue requests unless we reached the worker thread limit */ apr_thread_pool_threshold_set(threads, 0); } else { threads = NULL; } #endif while (1) { connection_t *connection = NULL; SVN_ERR(accept_connection(&connection, sock, ¶ms, handling_mode, pool)); if (run_mode == run_mode_listen_once) { err = serve_socket(connection, connection->pool); close_connection(connection); return err; } switch (handling_mode) { case connection_mode_fork: #if APR_HAS_FORK status = apr_proc_fork(&proc, connection->pool); if (status == APR_INCHILD) { /* the child would't listen to the main server's socket */ apr_socket_close(sock); /* serve_socket() logs any error it returns, so ignore it. */ svn_error_clear(serve_socket(connection, connection->pool)); close_connection(connection); return SVN_NO_ERROR; } else if (status != APR_INPARENT) { err = svn_error_wrap_apr(status, "apr_proc_fork"); logger__log_error(params.logger, err, NULL, NULL); svn_error_clear(err); } #endif break; case connection_mode_thread: /* Create a detached thread for each connection. That's not a particularly sophisticated strategy for a threaded server, it's little different from forking one process per connection. */ #if APR_HAS_THREADS attach_connection(connection); status = apr_thread_pool_push(threads, serve_thread, connection, 0, NULL); if (status) { return svn_error_wrap_apr(status, _("Can't push task")); } #endif break; case connection_mode_single: /* Serve one connection at a time. */ /* serve_socket() logs any error it returns, so ignore it. */ svn_error_clear(serve_socket(connection, connection->pool)); } close_connection(connection); } /* NOTREACHED */ }
/* Public Interface */ svn_error_t * svn_wc_diff6(svn_wc_context_t *wc_ctx, const char *local_abspath, const svn_wc_diff_callbacks4_t *callbacks, void *callback_baton, svn_depth_t depth, svn_boolean_t ignore_ancestry, svn_boolean_t show_copies_as_adds, svn_boolean_t use_git_diff_format, const apr_array_header_t *changelist_filter, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool) { struct diff_baton eb = { 0 }; svn_node_kind_t kind; svn_boolean_t get_all; const svn_diff_tree_processor_t *processor; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, FALSE /* allow_missing */, TRUE /* show_deleted */, FALSE /* show_hidden */, scratch_pool)); if (kind == svn_node_dir) eb.anchor_abspath = local_abspath; else eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); SVN_ERR(svn_wc__wrap_diff_callbacks(&processor, callbacks, callback_baton, TRUE, scratch_pool, scratch_pool)); if (use_git_diff_format) show_copies_as_adds = TRUE; if (show_copies_as_adds) ignore_ancestry = FALSE; /* if (reverse_order) processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool); */ if (! show_copies_as_adds && !use_git_diff_format) processor = svn_diff__tree_processor_copy_as_changed_create(processor, scratch_pool); eb.db = wc_ctx->db; eb.processor = processor; eb.ignore_ancestry = ignore_ancestry; eb.show_copies_as_adds = show_copies_as_adds; eb.pool = scratch_pool; if (changelist_filter && changelist_filter->nelts) SVN_ERR(svn_hash_from_cstring_keys(&eb.changelist_hash, changelist_filter, scratch_pool)); if (show_copies_as_adds || use_git_diff_format || !ignore_ancestry) get_all = TRUE; /* We need unmodified descendants of copies */ else get_all = FALSE; /* Walk status handles files and directories */ SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth, get_all, TRUE /* no_ignore */, FALSE /* ignore_text_mods */, NULL /* ignore_patterns */, diff_status_callback, &eb, cancel_func, cancel_baton, scratch_pool)); /* Close the remaining open directories */ while (eb.cur) { struct node_state_t *ns = eb.cur; if (!ns->skip) { if (ns->propchanges) SVN_ERR(processor->dir_changed(ns->relpath, ns->left_src, ns->right_src, ns->left_props, ns->right_props, ns->propchanges, ns->baton, processor, ns->pool)); else SVN_ERR(processor->dir_closed(ns->relpath, ns->left_src, ns->right_src, ns->baton, processor, ns->pool)); } eb.cur = ns->parent; svn_pool_clear(ns->pool); } return SVN_NO_ERROR; }
/** * Initialize the environment for all requests. * @param env the JNI environment for this request */ bool JNIUtil::JNIGlobalInit(JNIEnv *env) { // This method has to be run only once during the run a program. static bool run = false; svn_error_t *err; if (run) // already run return true; run = true; // Do not run this part more than one time. This leaves a small // time window when two threads create their first SVNClient and // SVNAdmin at the same time, but I do not see a better option // without APR already initialized if (g_inInit) return false; g_inInit = true; g_initEnv = env; apr_status_t status; /* Initialize the APR subsystem, and register an atexit() function * to Uninitialize that subsystem at program exit. */ status = apr_initialize(); if (status) { if (stderr) { char buf[1024]; apr_strerror(status, buf, sizeof(buf) - 1); fprintf(stderr, "%s: error: cannot initialize APR: %s\n", "svnjavahl", buf); } return FALSE; } /* This has to happen before any pools are created. */ if ((err = svn_dso_initialize2())) { if (stderr && err->message) fprintf(stderr, "%s", err->message); svn_error_clear(err); return FALSE; } if (0 > atexit(apr_terminate)) { if (stderr) fprintf(stderr, "%s: error: atexit registration failed\n", "svnjavahl"); return FALSE; } /* Create our top-level pool. */ g_pool = svn_pool_create(NULL); apr_allocator_t* allocator = apr_pool_allocator_get(g_pool); if (allocator) { /* Keep a maximum of 1 free block, to release memory back to the JVM (and other modules). */ apr_allocator_max_free_set(allocator, 1); } svn_utf_initialize2(FALSE, g_pool); /* Optimize character conversions */ svn_fs_initialize(g_pool); /* Avoid some theoretical issues */ svn_ra_initialize(g_pool); /* We shouldn't fill the JVMs memory with FS cache data unless explictly requested. */ { svn_cache_config_t settings = *svn_cache_config_get(); settings.cache_size = 0; settings.file_handle_count = 0; settings.single_threaded = FALSE; svn_cache_config_set(&settings); } #ifdef ENABLE_NLS #ifdef WIN32 { WCHAR ucs2_path[MAX_PATH]; char *utf8_path; const char *internal_path; apr_pool_t *pool; apr_status_t apr_err; apr_size_t inwords, outbytes; unsigned int outlength; pool = svn_pool_create(g_pool); /* get dll name - our locale info will be in '../share/locale' */ inwords = sizeof(ucs2_path) / sizeof(ucs2_path[0]); HINSTANCE moduleHandle = GetModuleHandle("libsvnjavahl-1"); GetModuleFileNameW(moduleHandle, ucs2_path, inwords); inwords = lstrlenW(ucs2_path); outbytes = outlength = 3 * (inwords + 1); utf8_path = reinterpret_cast<char *>(apr_palloc(pool, outlength)); apr_err = apr_conv_ucs2_to_utf8((const apr_wchar_t *) ucs2_path, &inwords, utf8_path, &outbytes); if (!apr_err && (inwords > 0 || outbytes == 0)) apr_err = APR_INCOMPLETE; if (apr_err) { if (stderr) fprintf(stderr, "Can't convert module path to UTF-8"); return FALSE; } utf8_path[outlength - outbytes] = '\0'; internal_path = svn_dirent_internal_style(utf8_path, pool); /* get base path name */ internal_path = svn_dirent_dirname(internal_path, pool); internal_path = svn_dirent_join(internal_path, SVN_LOCALE_RELATIVE_PATH, pool); bindtextdomain(PACKAGE_NAME, internal_path); svn_pool_destroy(pool); } #else bindtextdomain(PACKAGE_NAME, SVN_LOCALE_DIR); #endif #endif #if defined(WIN32) || defined(__CYGWIN__) /* See http://svn.apache.org/repos/asf/subversion/trunk/notes/asp-dot-net-hack.txt */ /* ### This code really only needs to be invoked by consumers of ### the libsvn_wc library, which basically means SVNClient. */ if (getenv("SVN_ASP_DOT_NET_HACK")) { err = svn_wc_set_adm_dir("_svn", g_pool); if (err) { if (stderr) { fprintf(stderr, "%s: error: SVN_ASP_DOT_NET_HACK failed: %s\n", "svnjavahl", err->message); } svn_error_clear(err); return FALSE; } } #endif svn_error_set_malfunction_handler(svn_error_raise_on_malfunction); // Build all mutexes. g_finalizedObjectsMutex = new JNIMutex(g_pool); if (isExceptionThrown()) return false; g_logMutex = new JNIMutex(g_pool); if (isExceptionThrown()) return false; // initialized the thread local storage if (!JNIThreadData::initThreadData()) return false; setEnv(env); if (isExceptionThrown()) return false; g_initEnv = NULL; g_inInit = false; return true; }
/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself, but create it marked with skip+skip_children. */ static svn_error_t * ensure_state(struct diff_baton *eb, const char *local_abspath, svn_boolean_t recursive_skip, apr_pool_t *scratch_pool) { struct node_state_t *ns; apr_pool_t *ns_pool; if (!eb->cur) { if (!svn_dirent_is_ancestor(eb->anchor_abspath, local_abspath)) return SVN_NO_ERROR; SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool), FALSE, scratch_pool)); } else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL)) SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool), FALSE, scratch_pool)); else return SVN_NO_ERROR; if (eb->cur && eb->cur->skip_children) return SVN_NO_ERROR; ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool); ns = apr_pcalloc(ns_pool, sizeof(*ns)); ns->pool = ns_pool; ns->local_abspath = apr_pstrdup(ns_pool, local_abspath); ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath); ns->parent = eb->cur; eb->cur = ns; if (recursive_skip) { ns->skip = TRUE; ns->skip_children = TRUE; return SVN_NO_ERROR; } { svn_revnum_t revision; svn_error_t *err; err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool); if (err) { if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) return svn_error_trace(err); svn_error_clear(err); revision = 0; /* Use original revision? */ } ns->left_src = svn_diff__source_create(revision, ns->pool); ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool); SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip, &ns->skip_children, ns->relpath, ns->left_src, ns->right_src, NULL /* copyfrom_source */, ns->parent ? ns->parent->baton : NULL, eb->processor, ns->pool, scratch_pool)); } return SVN_NO_ERROR; }
/* Implements svn_wc_status_func3_t */ static svn_error_t * diff_status_callback(void *baton, const char *local_abspath, const svn_wc_status3_t *status, apr_pool_t *scratch_pool) { struct diff_baton *eb = baton; svn_wc__db_t *db = eb->db; if (! status->versioned) return SVN_NO_ERROR; /* unversioned (includes dir externals) */ if (status->node_status == svn_wc_status_conflicted && status->text_status == svn_wc_status_none && status->prop_status == svn_wc_status_none) { /* Node is an actual only node describing a tree conflict */ return SVN_NO_ERROR; } /* Not text/prop modified, not copied. Easy out */ if (status->node_status == svn_wc_status_normal && !status->copied) return SVN_NO_ERROR; /* Mark all directories where we are no longer inside as closed */ while (eb->cur && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath)) { struct node_state_t *ns = eb->cur; if (!ns->skip) { if (ns->propchanges) SVN_ERR(eb->processor->dir_changed(ns->relpath, ns->left_src, ns->right_src, ns->left_props, ns->right_props, ns->propchanges, ns->baton, eb->processor, ns->pool)); else SVN_ERR(eb->processor->dir_closed(ns->relpath, ns->left_src, ns->right_src, ns->baton, eb->processor, ns->pool)); } eb->cur = ns->parent; svn_pool_clear(ns->pool); } SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), FALSE, scratch_pool)); if (eb->cur && eb->cur->skip_children) return SVN_NO_ERROR; if (eb->changelist_hash != NULL && (!status->changelist || ! svn_hash_gets(eb->changelist_hash, status->changelist))) return SVN_NO_ERROR; /* Filtered via changelist */ /* This code does about the same thing as the inner body of walk_local_nodes_diff() in diff_editor.c, except that it is already filtered by the status walker, doesn't have to account for remote changes (and many tiny other details) */ { svn_boolean_t repos_only; svn_boolean_t local_only; svn_wc__db_status_t db_status; svn_boolean_t have_base; svn_node_kind_t base_kind; svn_node_kind_t db_kind = status->kind; svn_depth_t depth_below_here = svn_depth_unknown; const char *child_abspath = local_abspath; const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath); repos_only = FALSE; local_only = FALSE; /* ### optimize away this call using status info. Should be possible in almost every case (except conflict, missing, obst.)*/ SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &have_base, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool)); if (!have_base) { local_only = TRUE; /* Only report additions */ } else if (db_status == svn_wc__db_status_normal) { /* Simple diff */ base_kind = db_kind; } else if (db_status == svn_wc__db_status_deleted) { svn_wc__db_status_t base_status; repos_only = TRUE; SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool)); if (base_status != svn_wc__db_status_normal) return SVN_NO_ERROR; } else { /* working status is either added or deleted */ svn_wc__db_status_t base_status; SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool)); if (base_status != svn_wc__db_status_normal) local_only = TRUE; else if (base_kind != db_kind || !eb->ignore_ancestry) { repos_only = TRUE; local_only = TRUE; } } if (repos_only) { /* Report repository form deleted */ if (base_kind == svn_node_file) SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, child_relpath, SVN_INVALID_REVNUM, eb->processor, eb->cur ? eb->cur->baton : NULL, scratch_pool)); else if (base_kind == svn_node_dir) SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, child_relpath, SVN_INVALID_REVNUM, depth_below_here, eb->processor, eb->cur ? eb->cur->baton : NULL, eb->cancel_func, eb->cancel_baton, scratch_pool)); } else if (!local_only) { /* Diff base against actual */ if (db_kind == svn_node_file) { SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath, child_relpath, SVN_INVALID_REVNUM, eb->changelist_hash, eb->processor, eb->cur ? eb->cur->baton : NULL, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); } else if (db_kind == svn_node_dir) { SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool)); if (status->prop_status != svn_wc_status_none && status->prop_status != svn_wc_status_normal) { apr_array_header_t *propchanges; SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props, eb->db, local_abspath, eb->cur->pool, scratch_pool)); SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props, eb->db, local_abspath, eb->cur->pool, scratch_pool)); SVN_ERR(svn_prop_diffs(&propchanges, eb->cur->right_props, eb->cur->left_props, eb->cur->pool)); eb->cur->propchanges = propchanges; } } } if (local_only && (db_status != svn_wc__db_status_deleted)) { if (db_kind == svn_node_file) SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, child_relpath, eb->processor, eb->cur ? eb->cur->baton : NULL, eb->changelist_hash, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); else if (db_kind == svn_node_dir) SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, child_relpath, depth_below_here, eb->processor, eb->cur ? eb->cur->baton : NULL, eb->changelist_hash, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); } if (db_kind == svn_node_dir && (local_only || repos_only)) SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool)); } return SVN_NO_ERROR; }
static svn_error_t * handle_external_item_change(svn_client_ctx_t *ctx, const char *repos_root_url, const char *parent_dir_abspath, const char *parent_dir_url, const char *local_abspath, const char *old_defining_abspath, const svn_wc_external_item2_t *new_item, svn_boolean_t *timestamp_sleep, apr_pool_t *scratch_pool) { svn_ra_session_t *ra_session; svn_client__pathrev_t *new_loc; const char *new_url; svn_node_kind_t ext_kind; SVN_ERR_ASSERT(repos_root_url && parent_dir_url); SVN_ERR_ASSERT(new_item != NULL); /* Don't bother to check status, since we'll get that for free by attempting to retrieve the hash values anyway. */ /* When creating the absolute URL, use the pool and not the iterpool, since the hash table values outlive the iterpool and any pointers they have should also outlive the iterpool. */ SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, new_item, repos_root_url, parent_dir_url, scratch_pool, scratch_pool)); /* Determine if the external is a file or directory. */ /* Get the RA connection. */ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, new_url, NULL, &(new_item->peg_revision), &(new_item->revision), ctx, scratch_pool)); SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind, scratch_pool)); if (svn_node_none == ext_kind) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' at revision %ld doesn't exist"), new_loc->url, new_loc->rev); if (svn_node_dir != ext_kind && svn_node_file != ext_kind) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' at revision %ld is not a file " "or a directory"), new_loc->url, new_loc->rev); /* Not protecting against recursive externals. Detecting them in the global case is hard, and it should be pretty obvious to a user when it happens. Worst case: your disk fills up :-). */ /* First notify that we're about to handle an external. */ if (ctx->notify_func2) { (*ctx->notify_func2)( ctx->notify_baton2, svn_wc_create_notify(local_abspath, svn_wc_notify_update_external, scratch_pool), scratch_pool); } if (! old_defining_abspath) { /* The target dir might have multiple components. Guarantee the path leading down to the last component. */ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath, scratch_pool), scratch_pool)); } switch (ext_kind) { case svn_node_dir: SVN_ERR(switch_dir_external(local_abspath, new_loc->url, new_item->url, &(new_item->peg_revision), &(new_item->revision), parent_dir_abspath, timestamp_sleep, ctx, scratch_pool)); break; case svn_node_file: if (strcmp(repos_root_url, new_loc->repos_root_url)) { const char *local_repos_root_url; const char *local_repos_uuid; const char *ext_repos_relpath; svn_error_t *err; /* * The working copy library currently requires that all files * in the working copy have the same repository root URL. * The URL from the file external's definition differs from the * one used by the working copy. As a workaround, replace the * root URL portion of the file external's URL, after making * sure both URLs point to the same repository. See issue #4087. */ err = svn_wc__node_get_repos_info(NULL, NULL, &local_repos_root_url, &local_repos_uuid, ctx->wc_ctx, parent_dir_abspath, scratch_pool, scratch_pool); if (err) { if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) return svn_error_trace(err); svn_error_clear(err); local_repos_root_url = NULL; local_repos_uuid = NULL; } ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url, new_url, scratch_pool); if (local_repos_uuid == NULL || local_repos_root_url == NULL || ext_repos_relpath == NULL || strcmp(local_repos_uuid, new_loc->repos_uuid) != 0) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Unsupported external: URL of file external '%s' " "is not in repository '%s'"), new_url, repos_root_url); new_url = svn_path_url_add_component2(local_repos_root_url, ext_repos_relpath, scratch_pool); SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, new_url, NULL, &(new_item->peg_revision), &(new_item->revision), ctx, scratch_pool)); } SVN_ERR(switch_file_external(local_abspath, new_url, &new_item->peg_revision, &new_item->revision, parent_dir_abspath, ra_session, ctx, scratch_pool)); break; default: SVN_ERR_MALFUNCTION(); break; } return SVN_NO_ERROR; }
svn_error_t * svn_client__handle_externals(apr_hash_t *externals_new, apr_hash_t *ambient_depths, const char *repos_root_url, const char *target_abspath, svn_depth_t requested_depth, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { apr_hash_t *old_external_defs; apr_hash_index_t *hi; apr_pool_t *iterpool; SVN_ERR_ASSERT(repos_root_url); iterpool = svn_pool_create(scratch_pool); SVN_ERR(svn_wc__externals_defined_below(&old_external_defs, ctx->wc_ctx, target_abspath, scratch_pool, iterpool)); for (hi = apr_hash_first(scratch_pool, externals_new); hi; hi = apr_hash_next(hi)) { const char *local_abspath = svn__apr_hash_index_key(hi); const char *desc_text = svn__apr_hash_index_val(hi); svn_depth_t ambient_depth = svn_depth_infinity; svn_pool_clear(iterpool); if (ambient_depths) { const char *ambient_depth_w; ambient_depth_w = apr_hash_get(ambient_depths, local_abspath, svn__apr_hash_index_klen(hi)); if (ambient_depth_w == NULL) { return svn_error_createf( SVN_ERR_WC_CORRUPT, NULL, _("Traversal of '%s' found no ambient depth"), svn_dirent_local_style(local_abspath, scratch_pool)); } else { ambient_depth = svn_depth_from_word(ambient_depth_w); } } SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep, local_abspath, desc_text, old_external_defs, ambient_depth, requested_depth, iterpool)); } /* Remove the remaining externals */ for (hi = apr_hash_first(scratch_pool, old_external_defs); hi; hi = apr_hash_next(hi)) { const char *item_abspath = svn__apr_hash_index_key(hi); const char *defining_abspath = svn__apr_hash_index_val(hi); const char *parent_abspath; svn_pool_clear(iterpool); SVN_ERR(wrap_external_error( ctx, item_abspath, handle_external_item_removal(ctx, defining_abspath, item_abspath, iterpool), iterpool)); /* Are there any unversioned directories between the removed * external and the DEFINING_ABSPATH which we can remove? */ parent_abspath = item_abspath; do { svn_node_kind_t kind; parent_abspath = svn_dirent_dirname(parent_abspath, iterpool); SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath, FALSE /* show_deleted*/, FALSE /* show_hidden */, iterpool)); if (kind == svn_node_none) { svn_error_t *err; err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool); if (err) { if (APR_STATUS_IS_ENOTEMPTY(err->apr_err)) { svn_error_clear(err); break; /* No parents to delete */ } else if (APR_STATUS_IS_ENOENT(err->apr_err) || APR_STATUS_IS_ENOTDIR(err->apr_err)) { svn_error_clear(err); /* Fall through; parent dir might be unversioned */ } else return svn_error_trace(err); } } } while (strcmp(parent_abspath, defining_abspath) != 0); } svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
svn_error_t * svn_client__export_externals(apr_hash_t *externals, const char *from_url, const char *to_abspath, const char *repos_root_url, svn_depth_t requested_depth, const char *native_eol, svn_boolean_t ignore_keywords, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { apr_pool_t *iterpool = svn_pool_create(scratch_pool); apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool); apr_hash_index_t *hi; SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); for (hi = apr_hash_first(scratch_pool, externals); hi; hi = apr_hash_next(hi)) { const char *local_abspath = svn__apr_hash_index_key(hi); const char *desc_text = svn__apr_hash_index_val(hi); const char *local_relpath; const char *dir_url; apr_array_header_t *items; int i; svn_pool_clear(iterpool); SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath, desc_text, FALSE, iterpool)); if (! items->nelts) continue; local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath); dir_url = svn_path_url_add_component2(from_url, local_relpath, scratch_pool); for (i = 0; i < items->nelts; i++) { const char *item_abspath; const char *new_url; svn_boolean_t under_root; svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i, svn_wc_external_item2_t *); svn_pool_clear(sub_iterpool); SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath, local_abspath, item->target_dir, sub_iterpool)); if (! under_root) { return svn_error_createf( SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, _("Path '%s' is not in the working copy"), svn_dirent_local_style( svn_dirent_join(local_abspath, item->target_dir, sub_iterpool), sub_iterpool)); } SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item, repos_root_url, dir_url, sub_iterpool, sub_iterpool)); /* The target dir might have multiple components. Guarantee the path leading down to the last component. */ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath, sub_iterpool), sub_iterpool)); SVN_ERR(wrap_external_error( ctx, item_abspath, svn_client_export5(NULL, new_url, item_abspath, &item->peg_revision, &item->revision, TRUE, FALSE, ignore_keywords, svn_depth_infinity, native_eol, ctx, sub_iterpool), sub_iterpool)); } } svn_pool_destroy(sub_iterpool); svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
/* Try to update a directory external at PATH to URL at REVISION. Use POOL for temporary allocations, and use the client context CTX. */ static svn_error_t * switch_dir_external(const char *local_abspath, const char *url, const char *url_from_externals_definition, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, const char *defining_abspath, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_node_kind_t kind; svn_error_t *err; svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM; svn_revnum_t external_rev = SVN_INVALID_REVNUM; apr_pool_t *subpool = svn_pool_create(pool); const char *repos_root_url; const char *repos_uuid; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); if (peg_revision->kind == svn_opt_revision_number) external_peg_rev = peg_revision->value.number; if (revision->kind == svn_opt_revision_number) external_rev = revision->value.number; /* * The code below assumes existing versioned paths are *not* part of * the external's defining working copy. * The working copy library does not support registering externals * on top of existing BASE nodes and will error out if we try. * So if the external target is part of the defining working copy's * BASE tree, don't attempt to create the external. Doing so would * leave behind a switched path instead of an external (since the * switch succeeds but registration of the external in the DB fails). * The working copy then cannot be updated until the path is switched back. * See issue #4085. */ SVN_ERR(svn_wc__node_get_base(&kind, NULL, NULL, &repos_root_url, &repos_uuid, NULL, ctx->wc_ctx, local_abspath, TRUE, /* ignore_enoent */ TRUE, /* show hidden */ pool, pool)); if (kind != svn_node_unknown) { const char *wcroot_abspath; const char *defining_wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath, pool, pool)); SVN_ERR(svn_wc__get_wcroot(&defining_wcroot_abspath, ctx->wc_ctx, defining_abspath, pool, pool)); if (strcmp(wcroot_abspath, defining_wcroot_abspath) == 0) return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, _("The external '%s' defined in %s at '%s' " "cannot be checked out because '%s' is " "already a versioned path."), url_from_externals_definition, SVN_PROP_EXTERNALS, svn_dirent_local_style(defining_abspath, pool), svn_dirent_local_style(local_abspath, pool)); } /* If path is a directory, try to update/switch to the correct URL and revision. */ SVN_ERR(svn_io_check_path(local_abspath, &kind, pool)); if (kind == svn_node_dir) { const char *node_url; /* Doubles as an "is versioned" check. */ err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath, pool, subpool); if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { svn_error_clear(err); err = SVN_NO_ERROR; goto relegate; } else if (err) return svn_error_trace(err); if (node_url) { /* If we have what appears to be a version controlled subdir, and its top-level URL matches that of our externals definition, perform an update. */ if (strcmp(node_url, url) == 0) { SVN_ERR(svn_client__update_internal(NULL, local_abspath, revision, svn_depth_unknown, FALSE, FALSE, FALSE, TRUE, FALSE, TRUE, timestamp_sleep, ctx, subpool)); /* We just decided that this existing directory is an external, so update the external registry with this information, like when checking out an external */ SVN_ERR(svn_wc__external_register(ctx->wc_ctx, defining_abspath, local_abspath, svn_node_dir, repos_root_url, repos_uuid, svn_uri_skip_ancestor(repos_root_url, url, pool), external_peg_rev, external_rev, pool)); svn_pool_destroy(subpool); goto cleanup; } /* We'd really prefer not to have to do a brute-force relegation -- blowing away the current external working copy and checking it out anew -- so we'll first see if we can get away with a generally cheaper relocation (if required) and switch-style update. To do so, we need to know the repository root URL of the external working copy as it currently sits. */ err = svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, &repos_uuid, ctx->wc_ctx, local_abspath, pool, subpool); if (err) { if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) return svn_error_trace(err); svn_error_clear(err); repos_root_url = NULL; repos_uuid = NULL; } if (repos_root_url) { /* If the new external target URL is not obviously a child of the external working copy's current repository root URL... */ if (! svn_uri__is_ancestor(repos_root_url, url)) { const char *repos_root; /* ... then figure out precisely which repository root URL that target URL *is* a child of ... */ SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url, ctx, subpool, subpool)); /* ... and use that to try to relocate the external working copy to the target location. */ err = svn_client_relocate2(local_abspath, repos_root_url, repos_root, FALSE, ctx, subpool); /* If the relocation failed because the new URL points to a totally different repository, we've no choice but to relegate and check out a new WC. */ if (err && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION || (err->apr_err == SVN_ERR_CLIENT_INVALID_RELOCATION))) { svn_error_clear(err); goto relegate; } else if (err) return svn_error_trace(err); /* If the relocation went without a hitch, we should have a new repository root URL. */ repos_root_url = repos_root; } SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url, peg_revision, revision, svn_depth_infinity, TRUE, FALSE, FALSE, TRUE /* ignore_ancestry */, timestamp_sleep, ctx, subpool)); SVN_ERR(svn_wc__external_register(ctx->wc_ctx, defining_abspath, local_abspath, svn_node_dir, repos_root_url, repos_uuid, svn_uri_skip_ancestor( repos_root_url, url, subpool), external_peg_rev, external_rev, subpool)); svn_pool_destroy(subpool); goto cleanup; } } } relegate: /* Fall back on removing the WC and checking out a new one. */ /* Ensure that we don't have any RA sessions or WC locks from failed operations above. */ svn_pool_destroy(subpool); if (kind == svn_node_dir) { /* Buh-bye, old and busted ... */ SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath, local_abspath, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } else { /* The target dir might have multiple components. Guarantee the path leading down to the last component. */ const char *parent = svn_dirent_dirname(local_abspath, pool); SVN_ERR(svn_io_make_dir_recursively(parent, pool)); } /* ... Hello, new hotness. */ SVN_ERR(svn_client__checkout_internal(NULL, url, local_abspath, peg_revision, revision, svn_depth_infinity, FALSE, FALSE, timestamp_sleep, ctx, pool)); SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, &repos_uuid, ctx->wc_ctx, local_abspath, pool, pool)); SVN_ERR(svn_wc__external_register(ctx->wc_ctx, defining_abspath, local_abspath, svn_node_dir, repos_root_url, repos_uuid, svn_uri_skip_ancestor(repos_root_url, url, pool), external_peg_rev, external_rev, pool)); cleanup: /* Issues #4123 and #4130: We don't need to keep the newly checked out external's DB open. */ SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool)); return SVN_NO_ERROR; }
/* Given a list of committables described by their common base abspath BASE_ABSPATH and a list of relative dirents TARGET_RELPATHS determine which absolute paths must be locked to commit all these targets and return this as a const char * array in LOCK_TARGETS Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary storage */ static svn_error_t * determine_lock_targets(apr_array_header_t **lock_targets, svn_wc_context_t *wc_ctx, const char *base_abspath, const apr_array_header_t *target_relpaths, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_pool_t *iterpool = svn_pool_create(scratch_pool); apr_hash_t *wc_items; /* const char *wcroot -> apr_array_header_t */ apr_hash_index_t *hi; int i; wc_items = apr_hash_make(scratch_pool); /* Create an array of targets for each working copy used */ for (i = 0; i < target_relpaths->nelts; i++) { const char *target_abspath; const char *wcroot_abspath; apr_array_header_t *wc_targets; svn_error_t *err; const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i, const char *); svn_pool_clear(iterpool); target_abspath = svn_dirent_join(base_abspath, target_relpath, scratch_pool); err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath, iterpool, iterpool); if (err) { if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { svn_error_clear(err); continue; } return svn_error_trace(err); } wc_targets = svn_hash_gets(wc_items, wcroot_abspath); if (! wc_targets) { wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *)); svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath), wc_targets); } APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath; } *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items), sizeof(const char *)); /* For each working copy determine where to lock */ for (hi = apr_hash_first(scratch_pool, wc_items); hi; hi = apr_hash_next(hi)) { const char *common; const char *wcroot_abspath = svn__apr_hash_index_key(hi); apr_array_header_t *wc_targets = svn__apr_hash_index_val(hi); svn_pool_clear(iterpool); if (wc_targets->nelts == 1) { const char *target_abspath; target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *); if (! strcmp(wcroot_abspath, target_abspath)) { APR_ARRAY_PUSH(*lock_targets, const char *) = apr_pstrdup(result_pool, target_abspath); } else { /* Lock the parent to allow deleting the target */ APR_ARRAY_PUSH(*lock_targets, const char *) = svn_dirent_dirname(target_abspath, result_pool); } }
svn_error_t * svn_client__update_internal(svn_revnum_t *result_rev, const char *local_abspath, const svn_opt_revision_t *revision, svn_depth_t depth, svn_boolean_t depth_is_sticky, svn_boolean_t ignore_externals, svn_boolean_t allow_unver_obstructions, svn_boolean_t adds_as_modification, svn_boolean_t make_parents, svn_boolean_t innerupdate, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) { const char *anchor_abspath, *lockroot_abspath; svn_error_t *err; svn_opt_revision_t peg_revision = *revision; apr_hash_t *conflicted_paths = ctx->conflict_func2 ? apr_hash_make(pool) : NULL; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(! (innerupdate && make_parents)); if (make_parents) { int i; const char *parent_abspath = local_abspath; apr_array_header_t *missing_parents = apr_array_make(pool, 4, sizeof(const char *)); while (1) { /* Try to lock. If we can't lock because our target (or its parent) isn't a working copy, we'll try to walk up the tree to find a working copy, remembering this path's parent as one we need to flesh out. */ err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx, parent_abspath, !innerupdate, pool, pool); if (!err) break; if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) || svn_dirent_is_root(parent_abspath, strlen(parent_abspath))) return err; svn_error_clear(err); /* Remember the parent of our update target as a missing parent. */ parent_abspath = svn_dirent_dirname(parent_abspath, pool); APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath; } /* Run 'svn up --depth=empty' (effectively) on the missing parents, if any. */ anchor_abspath = lockroot_abspath; for (i = missing_parents->nelts - 1; i >= 0; i--) { const char *missing_parent = APR_ARRAY_IDX(missing_parents, i, const char *); err = update_internal(result_rev, conflicted_paths, missing_parent, anchor_abspath, &peg_revision, svn_depth_empty, FALSE, ignore_externals, allow_unver_obstructions, adds_as_modification, timestamp_sleep, FALSE, ctx, pool); if (err) goto cleanup; anchor_abspath = missing_parent; /* If we successfully updated a missing parent, let's re-use the returned revision number for future updates for the sake of consistency. */ peg_revision.kind = svn_opt_revision_number; peg_revision.value.number = *result_rev; } } else {
svn_error_t * svn_client_status6(svn_revnum_t *result_rev, svn_client_ctx_t *ctx, const char *path, const svn_opt_revision_t *revision, svn_depth_t depth, svn_boolean_t get_all, svn_boolean_t check_out_of_date, svn_boolean_t check_working_copy, svn_boolean_t no_ignore, svn_boolean_t ignore_externals, svn_boolean_t depth_as_sticky, const apr_array_header_t *changelists, svn_client_status_func_t status_func, void *status_baton, apr_pool_t *pool) /* ### aka scratch_pool */ { struct status_baton sb; const char *dir, *dir_abspath; const char *target_abspath; const char *target_basename; apr_array_header_t *ignores; svn_error_t *err; apr_hash_t *changelist_hash = NULL; /* Override invalid combinations of the check_out_of_date and check_working_copy flags. */ if (!check_out_of_date) check_working_copy = TRUE; if (svn_path_is_url(path)) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("'%s' is not a local path"), path); if (changelists && changelists->nelts) SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool)); if (result_rev) *result_rev = SVN_INVALID_REVNUM; sb.real_status_func = status_func; sb.real_status_baton = status_baton; sb.deleted_in_repos = FALSE; sb.changelist_hash = changelist_hash; sb.wc_ctx = ctx->wc_ctx; SVN_ERR(svn_dirent_get_absolute(&target_abspath, path, pool)); if (check_out_of_date) { /* The status editor only works on directories, so get the ancestor if necessary */ svn_node_kind_t kind; SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath, TRUE, FALSE, pool)); /* Dir must be a working copy directory or the status editor fails */ if (kind == svn_node_dir) { dir_abspath = target_abspath; target_basename = ""; dir = path; } else { dir_abspath = svn_dirent_dirname(target_abspath, pool); target_basename = svn_dirent_basename(target_abspath, NULL); dir = svn_dirent_dirname(path, pool); if (kind == svn_node_file) { if (depth == svn_depth_empty) depth = svn_depth_files; } else { err = svn_wc_read_kind2(&kind, ctx->wc_ctx, dir_abspath, FALSE, FALSE, pool); svn_error_clear(err); if (err || kind != svn_node_dir) { return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, _("'%s' is not a working copy"), svn_dirent_local_style(path, pool)); } } } } else { dir = path; dir_abspath = target_abspath; } if (svn_dirent_is_absolute(dir)) { sb.anchor_abspath = NULL; sb.anchor_relpath = NULL; } else { sb.anchor_abspath = dir_abspath; sb.anchor_relpath = dir; } /* Get the status edit, and use our wrapping status function/baton as the callback pair. */ SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool)); /* If we want to know about out-of-dateness, we crawl the working copy and let the RA layer drive the editor for real. Otherwise, we just close the edit. :-) */ if (check_out_of_date) { svn_ra_session_t *ra_session; const char *URL; svn_node_kind_t kind; svn_boolean_t server_supports_depth; const svn_delta_editor_t *editor; void *edit_baton, *set_locks_baton; svn_revnum_t edit_revision = SVN_INVALID_REVNUM; /* Get full URL from the ANCHOR. */ SVN_ERR(svn_client_url_from_path2(&URL, dir_abspath, ctx, pool, pool)); if (!URL) return svn_error_createf (SVN_ERR_ENTRY_MISSING_URL, NULL, _("Entry '%s' has no URL"), svn_dirent_local_style(dir, pool)); /* Open a repository session to the URL. */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, dir_abspath, NULL, FALSE, TRUE, ctx, pool, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); SVN_ERR(svn_wc__get_status_editor(&editor, &edit_baton, &set_locks_baton, &edit_revision, ctx->wc_ctx, dir_abspath, target_basename, depth, get_all, check_working_copy, no_ignore, depth_as_sticky, server_supports_depth, ignores, tweak_status, &sb, ctx->cancel_func, ctx->cancel_baton, pool, pool)); /* Verify that URL exists in HEAD. If it doesn't, this can save us a whole lot of hassle; if it does, the cost of this request should be minimal compared to the size of getting back the average amount of "out-of-date" information. */ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, pool)); if (kind == svn_node_none) { svn_boolean_t added; /* Our status target does not exist in HEAD. If we've got it locally added, that's okay. But if it was previously versioned, then it must have since been deleted from the repository. (Note that "locally replaced" doesn't count as "added" in this case.) */ SVN_ERR(svn_wc__node_is_added(&added, ctx->wc_ctx, dir_abspath, pool)); if (! added) sb.deleted_in_repos = TRUE; /* And now close the edit. */ SVN_ERR(editor->close_edit(edit_baton, pool)); } else { svn_revnum_t revnum; report_baton_t rb; svn_depth_t status_depth; if (revision->kind == svn_opt_revision_head) { /* Cause the revision number to be omitted from the request, which implies HEAD. */ revnum = SVN_INVALID_REVNUM; } else { /* Get a revision number for our status operation. */ SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx, target_abspath, ra_session, revision, pool)); } if (depth_as_sticky || !server_supports_depth) status_depth = depth; else status_depth = svn_depth_unknown; /* Use depth from WC */ /* Do the deed. Let the RA layer drive the status editor. */ SVN_ERR(svn_ra_do_status2(ra_session, &rb.wrapped_reporter, &rb.wrapped_report_baton, target_basename, revnum, status_depth, editor, edit_baton, pool)); /* Init the report baton. */ rb.ancestor = apr_pstrdup(pool, URL); /* Edited later */ rb.set_locks_baton = set_locks_baton; rb.ctx = ctx; rb.pool = pool; if (depth == svn_depth_unknown) rb.depth = svn_depth_infinity; else rb.depth = depth; /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, EDITOR will be driven to describe differences between our working copy and HEAD. */ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, target_abspath, &lock_fetch_reporter, &rb, FALSE /* restore_files */, depth, (! depth_as_sticky), (! server_supports_depth), FALSE /* use_commit_times */, ctx->cancel_func, ctx->cancel_baton, NULL, NULL, pool)); } if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(target_abspath, svn_wc_notify_status_completed, pool); notify->revision = edit_revision; ctx->notify_func2(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = edit_revision; } else { err = svn_wc_walk_status(ctx->wc_ctx, target_abspath, depth, get_all, no_ignore, FALSE, ignores, tweak_status, &sb, ctx->cancel_func, ctx->cancel_baton, pool); if (err && err->apr_err == SVN_ERR_WC_MISSING) { /* This error code is checked for in svn to continue after this error */ svn_error_clear(err); return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, _("'%s' is not a working copy"), svn_dirent_local_style(path, pool)); } SVN_ERR(err); } /* We only descend into an external if depth is svn_depth_infinity or svn_depth_unknown. However, there are conceivable behaviors that would involve descending under other circumstances; thus, we pass depth anyway, so the code will DTRT if we change the conditional in the future. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) { apr_hash_t *external_map; SVN_ERR(svn_wc__externals_defined_below(&external_map, ctx->wc_ctx, target_abspath, pool, pool)); SVN_ERR(do_external_status(ctx, external_map, depth, get_all, check_out_of_date, check_working_copy, no_ignore, changelists, sb.anchor_abspath, sb.anchor_relpath, status_func, status_baton, pool)); } return SVN_NO_ERROR; }
/* Make an unversioned copy of the versioned file at FROM_ABSPATH. Copy it * to the destination path TO_ABSPATH. * * If REVISION is svn_opt_revision_working, copy the working version, * otherwise copy the base version. * * Expand the file's keywords according to the source file's 'svn:keywords' * property, if present. If copying a locally modified working version, * append 'M' to the revision number and use '(local)' for the author. * * Translate the file's line endings according to the source file's * 'svn:eol-style' property, if present. If NATIVE_EOL is not NULL, use it * in place of the native EOL style. Throw an error if the source file has * inconsistent line endings and EOL translation is attempted. * * Set the destination file's modification time to the source file's * modification time if copying the working version and the working version * is locally modified; otherwise set it to the versioned file's last * changed time. * * Set the destination file's 'executable' flag according to the source * file's 'svn:executable' property. */ static svn_error_t * copy_one_versioned_file(const char *from_abspath, const char *to_abspath, svn_client_ctx_t *ctx, const svn_opt_revision_t *revision, const char *native_eol, svn_boolean_t ignore_keywords, apr_pool_t *scratch_pool) { apr_hash_t *kw = NULL; svn_subst_eol_style_t style; apr_hash_t *props; svn_string_t *eol_style, *keywords, *executable, *special; const char *eol = NULL; svn_boolean_t local_mod = FALSE; apr_time_t tm; svn_stream_t *source; svn_stream_t *dst_stream; const char *dst_tmp; svn_error_t *err; svn_boolean_t is_deleted; svn_wc_context_t *wc_ctx = ctx->wc_ctx; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, from_abspath, scratch_pool)); /* Don't export 'deleted' files and directories unless it's a revision other than WORKING. These files and directories don't really exist in WORKING. */ if (revision->kind == svn_opt_revision_working && is_deleted) return SVN_NO_ERROR; if (revision->kind != svn_opt_revision_working) { /* Only export 'added' files when the revision is WORKING. This is not WORKING, so skip the 'added' files, since they didn't exist in the BASE revision and don't have an associated text-base. 'replaced' files are technically the same as 'added' files. ### TODO: Handle replaced nodes properly. ### svn_opt_revision_base refers to the "new" ### base of the node. That means, if a node is locally ### replaced, export skips this node, as if it was locally ### added, because svn_opt_revision_base refers to the base ### of the added node, not to the node that was deleted. ### In contrast, when the node is copied-here or moved-here, ### the copy/move source's content will be exported. ### It is currently not possible to export the revert-base ### when a node is locally replaced. We need a new ### svn_opt_revision_ enum value for proper distinction ### between revert-base and commit-base. Copied-/moved-here nodes have a base, so export both added and replaced files when they involve a copy-/move-here. We get all this for free from evaluating SOURCE == NULL: */ SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (source == NULL) return SVN_NO_ERROR; SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, from_abspath, scratch_pool, scratch_pool)); } else { svn_wc_status3_t *status; /* ### hmm. this isn't always a specialfile. this will simply open ### the file readonly if it is a regular file. */ SVN_ERR(svn_subst_read_specialfile(&source, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_status3(&status, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (status->text_status != svn_wc_status_normal) local_mod = TRUE; } /* We can early-exit if we're creating a special file. */ special = apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING); if (special != NULL) { /* Create the destination as a special file, and copy the source details into the destination stream. */ SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath, scratch_pool, scratch_pool)); return svn_error_trace( svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool)); } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); executable = apr_hash_get(props, SVN_PROP_EXECUTABLE, APR_HASH_KEY_STRING); if (eol_style) SVN_ERR(get_eol_style(&style, &eol, eol_style->data, native_eol)); if (local_mod) { /* Use the modified time from the working copy of the file */ SVN_ERR(svn_io_file_affected_time(&tm, from_abspath, scratch_pool)); } else { SVN_ERR(svn_wc__node_get_changed_info(NULL, &tm, NULL, wc_ctx, from_abspath, scratch_pool, scratch_pool)); } if (keywords) { svn_revnum_t changed_rev; const char *suffix; const char *url; const char *author; SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, NULL, &author, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (local_mod) { /* For locally modified files, we'll append an 'M' to the revision number, and set the author to "(local)" since we can't always determine the current user's username */ suffix = "M"; author = _("(local)"); } else { suffix = ""; } SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_subst_build_keywords2 (&kw, keywords->data, apr_psprintf(scratch_pool, "%ld%s", changed_rev, suffix), url, tm, author, scratch_pool)); } /* For atomicity, we translate to a tmp file and then rename the tmp file over the real destination. */ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, svn_dirent_dirname(to_abspath, scratch_pool), svn_io_file_del_none, scratch_pool, scratch_pool)); /* If some translation is needed, then wrap the output stream (this is more efficient than wrapping the input). */ if (eol || (kw && (apr_hash_count(kw) > 0))) dst_stream = svn_subst_stream_translated(dst_stream, eol, FALSE /* repair */, kw, ! ignore_keywords /* expand */, scratch_pool); /* ###: use cancel func/baton in place of NULL/NULL below. */ err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool); if (!err && executable) err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool); if (!err) err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool); if (err) return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE, scratch_pool)); /* Now that dst_tmp contains the translated data, do the atomic rename. */ SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool)); if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath, svn_wc_notify_update_add, scratch_pool); notify->kind = svn_node_file; (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); } return SVN_NO_ERROR; }
/* Write to DIGEST_PATH a representation of CHILDREN (which may be empty, if the versioned path in FS represented by DIGEST_PATH has no children) and LOCK (which may be NULL if that versioned path is lock itself locked). Set the permissions of DIGEST_PATH to those of PERMS_REFERENCE. Use POOL for all allocations. */ static svn_error_t * write_digest_file(apr_hash_t *children, svn_lock_t *lock, const char *fs_path, const char *digest_path, const char *perms_reference, apr_pool_t *pool) { svn_error_t *err = SVN_NO_ERROR; svn_stream_t *stream; apr_hash_index_t *hi; apr_hash_t *hash = apr_hash_make(pool); const char *tmp_path; SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR, pool), fs_path, pool)); SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool), fs_path, pool)); if (lock) { const char *creation_date = NULL, *expiration_date = NULL; if (lock->creation_date) creation_date = svn_time_to_cstring(lock->creation_date, pool); if (lock->expiration_date) expiration_date = svn_time_to_cstring(lock->expiration_date, pool); hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1, lock->path, APR_HASH_KEY_STRING, pool); hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1, lock->token, APR_HASH_KEY_STRING, pool); hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1, lock->owner, APR_HASH_KEY_STRING, pool); hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1, lock->comment, APR_HASH_KEY_STRING, pool); hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1, lock->is_dav_comment ? "1" : "0", 1, pool); hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1, creation_date, APR_HASH_KEY_STRING, pool); hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1, expiration_date, APR_HASH_KEY_STRING, pool); } if (apr_hash_count(children)) { svn_stringbuf_t *children_list = svn_stringbuf_create("", pool); for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi)) { svn_stringbuf_appendbytes(children_list, svn__apr_hash_index_key(hi), svn__apr_hash_index_klen(hi)); svn_stringbuf_appendbyte(children_list, '\n'); } hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1, children_list->data, children_list->len, pool); } SVN_ERR(svn_stream_open_unique(&stream, &tmp_path, svn_dirent_dirname(digest_path, pool), svn_io_file_del_none, pool, pool)); if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool))) { svn_error_clear(svn_stream_close(stream)); return svn_error_createf(err->apr_err, err, _("Cannot write lock/entries hashfile '%s'"), svn_dirent_local_style(tmp_path, pool)); } SVN_ERR(svn_stream_close(stream)); SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool)); SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool)); return SVN_NO_ERROR; }
/* Make an unversioned copy of the versioned file or directory tree at the * source path FROM_ABSPATH. Copy it to the destination path TO_ABSPATH. * * If REVISION is svn_opt_revision_working, copy the working version, * otherwise copy the base version. * * See copy_one_versioned_file() for details of file copying behaviour, * including IGNORE_KEYWORDS and NATIVE_EOL. * * Include externals unless IGNORE_EXTERNALS is true. * * Recurse according to DEPTH. * */ static svn_error_t * copy_versioned_files(const char *from_abspath, const char *to_abspath, const svn_opt_revision_t *revision, svn_boolean_t force, svn_boolean_t ignore_externals, svn_boolean_t ignore_keywords, svn_depth_t depth, const char *native_eol, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_error_t *err; apr_pool_t *iterpool; const apr_array_header_t *children; svn_node_kind_t from_kind; svn_depth_t node_depth; SVN_ERR_ASSERT(svn_dirent_is_absolute(from_abspath)); SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); /* Only export 'added' and 'replaced' files when the revision is WORKING; when the revision is BASE (i.e. != WORKING), only export 'added' and 'replaced' files when they are part of a copy-/move-here. Otherwise, skip them, since they don't have an associated text-base. This condition for added/replaced simply is an optimization. Added and replaced files would be handled similarly by svn_wc_get_pristine_contents2(), which would return NULL if they have no base associated. TODO: We may prefer not to duplicate this condition and rather use svn_wc_get_pristine_contents2() or a dedicated new function instead. Don't export 'deleted' files and directories unless it's a revision other than WORKING. These files and directories don't really exist in WORKING. */ if (revision->kind != svn_opt_revision_working) { svn_boolean_t is_added; const char *repos_relpath; SVN_ERR(svn_wc__node_get_origin(&is_added, NULL, &repos_relpath, NULL, NULL, NULL, ctx->wc_ctx, from_abspath, FALSE, pool, pool)); if (is_added && !repos_relpath) return SVN_NO_ERROR; /* Local addition */ } else { svn_boolean_t is_deleted; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx, from_abspath, pool)); if (is_deleted) return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind(&from_kind, ctx->wc_ctx, from_abspath, FALSE, pool)); if (from_kind == svn_node_dir) { apr_fileperms_t perm = APR_OS_DEFAULT; int j; /* Try to make the new directory. If this fails because the directory already exists, check our FORCE flag to see if we care. */ /* Keep the source directory's permissions if applicable. Skip retrieving the umask on windows. Apr does not implement setting filesystem privileges on Windows. Retrieving the file permissions with APR_FINFO_PROT | APR_FINFO_OWNER is documented to be 'incredibly expensive' */ #ifndef WIN32 if (revision->kind == svn_opt_revision_working) { apr_finfo_t finfo; SVN_ERR(svn_io_stat(&finfo, from_abspath, APR_FINFO_PROT, pool)); perm = finfo.protection; } #endif err = svn_io_dir_make(to_abspath, perm, pool); if (err) { if (! APR_STATUS_IS_EEXIST(err->apr_err)) return svn_error_trace(err); if (! force) SVN_ERR_W(err, _("Destination directory exists, and will not be " "overwritten unless forced")); else svn_error_clear(err); } SVN_ERR(svn_wc__node_get_children(&children, ctx->wc_ctx, from_abspath, FALSE, pool, pool)); iterpool = svn_pool_create(pool); for (j = 0; j < children->nelts; j++) { const char *child_abspath = APR_ARRAY_IDX(children, j, const char *); const char *child_name = svn_dirent_basename(child_abspath, NULL); const char *target_abspath; svn_node_kind_t child_kind; svn_pool_clear(iterpool); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); target_abspath = svn_dirent_join(to_abspath, child_name, iterpool); SVN_ERR(svn_wc_read_kind(&child_kind, ctx->wc_ctx, child_abspath, FALSE, iterpool)); if (child_kind == svn_node_dir) { if (depth == svn_depth_infinity || depth == svn_depth_immediates) { if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(target_abspath, svn_wc_notify_update_add, pool); notify->kind = svn_node_dir; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } if (depth == svn_depth_infinity) SVN_ERR(copy_versioned_files(child_abspath, target_abspath, revision, force, ignore_externals, ignore_keywords, depth, native_eol, ctx, iterpool)); else SVN_ERR(svn_io_make_dir_recursively(target_abspath, iterpool)); } } else if (child_kind == svn_node_file && depth >= svn_depth_files) { svn_node_kind_t external_kind; SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, ctx->wc_ctx, child_abspath, child_abspath, TRUE, pool, pool)); if (external_kind != svn_node_file) SVN_ERR(copy_one_versioned_file(child_abspath, target_abspath, ctx, revision, native_eol, ignore_keywords, iterpool)); } } SVN_ERR(svn_wc__node_get_depth(&node_depth, ctx->wc_ctx, from_abspath, pool)); /* Handle externals. */ if (! ignore_externals && depth == svn_depth_infinity && node_depth == svn_depth_infinity) { apr_array_header_t *ext_items; const svn_string_t *prop_val; SVN_ERR(svn_wc_prop_get2(&prop_val, ctx->wc_ctx, from_abspath, SVN_PROP_EXTERNALS, pool, pool)); if (prop_val != NULL) { int i; SVN_ERR(svn_wc_parse_externals_description3(&ext_items, from_abspath, prop_val->data, FALSE, pool)); for (i = 0; i < ext_items->nelts; ++i) { svn_wc_external_item2_t *ext_item; const char *new_from, *new_to; svn_pool_clear(iterpool); ext_item = APR_ARRAY_IDX(ext_items, i, svn_wc_external_item2_t *); new_from = svn_dirent_join(from_abspath, ext_item->target_dir, iterpool); new_to = svn_dirent_join(to_abspath, ext_item->target_dir, iterpool); /* The target dir might have parents that don't exist. Guarantee the path upto the last component. */ if (!svn_dirent_is_root(ext_item->target_dir, strlen(ext_item->target_dir))) { const char *parent = svn_dirent_dirname(new_to, iterpool); SVN_ERR(svn_io_make_dir_recursively(parent, iterpool)); } SVN_ERR(copy_versioned_files(new_from, new_to, revision, force, FALSE, ignore_keywords, svn_depth_infinity, native_eol, ctx, iterpool)); } }
/* ... Add the paths of any conflict victims to CONFLICTED_PATHS, if that is not null. */ static svn_error_t * switch_internal(svn_revnum_t *result_rev, apr_hash_t *conflicted_paths, const char *local_abspath, const char *anchor_abspath, const char *switch_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_depth_t depth, svn_boolean_t depth_is_sticky, svn_boolean_t ignore_externals, svn_boolean_t allow_unver_obstructions, svn_boolean_t ignore_ancestry, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_ra_reporter3_t *reporter; void *report_baton; const char *anchor_url, *target; svn_client__pathrev_t *switch_loc; svn_ra_session_t *ra_session; svn_revnum_t revnum; const char *diff3_cmd; apr_hash_t *wcroot_iprops; apr_array_header_t *inherited_props; svn_boolean_t use_commit_times; const svn_delta_editor_t *switch_editor; void *switch_edit_baton; const char *preserved_exts_str; apr_array_header_t *preserved_exts; svn_boolean_t server_supports_depth; struct svn_client__dirent_fetcher_baton_t dfb; svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; /* Do not support the situation of both exclude and switch a target. */ if (depth == svn_depth_exclude) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot both exclude and switch a path")); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); { svn_boolean_t has_working; SVN_ERR(svn_wc__node_has_working(&has_working, ctx->wc_ctx, local_abspath, pool)); if (has_working) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot switch '%s' because it is not in the " "repository yet"), svn_dirent_local_style(local_abspath, pool)); } /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) : NULL; /* Sanity check. Without these, the switch is meaningless. */ SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0')); if (strcmp(local_abspath, anchor_abspath)) target = svn_dirent_basename(local_abspath, pool); else target = ""; SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath, pool, pool)); if (! anchor_url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("Directory '%s' has no URL"), svn_dirent_local_style(anchor_abspath, pool)); /* We may need to crop the tree if the depth is sticky */ if (depth_is_sticky && depth < svn_depth_infinity) { svn_node_kind_t target_kind; if (depth == svn_depth_exclude) { SVN_ERR(svn_wc_exclude(ctx->wc_ctx, local_abspath, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* Target excluded, we are done now */ return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath, TRUE, TRUE, pool)); if (target_kind == svn_node_dir) SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } /* Open an RA session to 'source' URL */ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc, switch_url, anchor_abspath, peg_revision, revision, ctx, pool)); /* Disallow a switch operation to change the repository root of the target. */ if (! svn_uri__is_ancestor(switch_loc->repos_root_url, anchor_url)) return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL, _("'%s'\nis not the same repository as\n'%s'"), anchor_url, switch_loc->repos_root_url); /* If we're not ignoring ancestry, then error out if the switch source and target don't have a common ancestory. ### We're acting on the anchor here, not the target. Is that ### okay? */ if (! ignore_ancestry) { svn_client__pathrev_t *target_base_loc, *yca; SVN_ERR(svn_client__wc_node_get_base(&target_base_loc, local_abspath, ctx->wc_ctx, pool, pool)); if (!target_base_loc) yca = NULL; /* Not versioned */ else { SVN_ERR(svn_client__get_youngest_common_ancestor( &yca, switch_loc, target_base_loc, ra_session, ctx, pool, pool)); } if (! yca) return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, _("'%s' shares no common ancestry with '%s'"), switch_url, svn_dirent_local_style(local_abspath, pool)); } wcroot_iprops = apr_hash_make(pool); /* Will the base of LOCAL_ABSPATH require an iprop cache post-switch? If we are switching LOCAL_ABSPATH to the root of the repository then we don't need to cache inherited properties. In all other cases we *might* need to cache iprops. */ if (strcmp(switch_loc->repos_root_url, switch_loc->url) != 0) { svn_boolean_t wc_root; svn_boolean_t needs_iprop_cache = TRUE; SVN_ERR(svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath, pool)); /* Switching the WC root to anything but the repos root means we need an iprop cache. */ if (!wc_root) { /* We know we are switching a subtree to something other than the repos root, but if we are unswitching that subtree we don't need an iprops cache. */ const char *target_parent_url; const char *unswitched_url; /* Calculate the URL LOCAL_ABSPATH would have if it was unswitched relative to its parent. */ SVN_ERR(svn_wc__node_get_url(&target_parent_url, ctx->wc_ctx, svn_dirent_dirname(local_abspath, pool), pool, pool)); unswitched_url = svn_path_url_add_component2( target_parent_url, svn_dirent_basename(local_abspath, pool), pool); /* If LOCAL_ABSPATH will be unswitched relative to its parent, then it doesn't need an iprop cache. Note: It doesn't matter if LOCAL_ABSPATH is withing a switched subtree, only if it's the *root* of a switched subtree.*/ if (strcmp(unswitched_url, switch_loc->url) == 0) needs_iprop_cache = FALSE; } if (needs_iprop_cache) { SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", switch_loc->rev, pool, pool)); svn_hash_sets(wcroot_iprops, local_abspath, inherited_props); } } SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool)); /* Fetch the switch (update) editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision() later on. */ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); dfb.ra_session = ra_session; dfb.anchor_url = anchor_url; dfb.target_revision = switch_loc->rev; SVN_ERR(svn_wc__get_switch_editor(&switch_editor, &switch_edit_baton, &revnum, ctx->wc_ctx, anchor_abspath, target, switch_loc->url, wcroot_iprops, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, server_supports_depth, diff3_cmd, preserved_exts, svn_client__dirent_fetcher, &dfb, conflicted_paths ? record_conflict : NULL, conflicted_paths, NULL, NULL, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool, pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton, switch_loc->rev, target, depth_is_sticky ? depth : svn_depth_unknown, switch_loc->url, FALSE /* send_copyfrom_args */, ignore_ancestry, switch_editor, switch_edit_baton, pool, pool)); /* Past this point, we assume the WC is going to be modified so we will * need to sleep for timestamps. */ *timestamp_sleep = TRUE; /* Drive the reporter structure, describing the revisions within LOCAL_ABSPATH. When this calls reporter->finish_report, the reporter will drive the switch_editor. */ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* We handle externals after the switch is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) { apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, &new_depths, ctx->wc_ctx, local_abspath, depth, pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, switch_loc->repos_root_url, local_abspath, depth, timestamp_sleep, ctx, pool)); } /* Let everyone know we're finished here. */ if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(anchor_abspath, svn_wc_notify_update_completed, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }