Index: Subversion/subversion/include/svn_error_codes.h =================================================================== --- Subversion/subversion/include/svn_error_codes.h (revision 6459) +++ Subversion/subversion/include/svn_error_codes.h (working copy) @@ -692,6 +692,14 @@ SVN_ERR_CLIENT_CATEGORY_START + 9, "Inter-repository relocation not allowed") + SVN_ERRDEF (SVN_ERR_CLIENT_INVALID_TARBALLS_DESCRIPTION, + SVN_ERR_CLIENT_CATEGORY_START + 10, + "Format of an " SVN_PROP_TARBALLS " property was invalid.") + + SVN_ERRDEF (SVN_ERR_CLIENT_CONNECTION_IO_ERROR, + SVN_ERR_CLIENT_CATEGORY_START + 11, + "Error getting tarball.") + /* misc errors */ SVN_ERRDEF (SVN_ERR_BASE, Index: Subversion/subversion/include/svn_props.h =================================================================== --- Subversion/subversion/include/svn_props.h (revision 6459) +++ Subversion/subversion/include/svn_props.h (working copy) @@ -198,6 +198,22 @@ */ #define SVN_PROP_EXTERNALS SVN_PROP_PREFIX "externals" +/** Describes tarball items to check out into this directory. + * + * The format is a series of lines, such as: + * + *
   localdir1           http://url.for.external.source/etc/
+ *   localdir1/foo       http://url.for.external.source/foo
+ *   localdir1/bar       http://blah.blah.blah/repositories/theirproj
+ *   localdir1/bar/baz   http://blorg.blorg.blorg/basement/code
+ *   localdir2           http://another.url/blah/blah/blah
+ *   localdir3           http://and.so.on/and/so/forth
+ * + * The subdir names on the left side are relative to the directory on + * which this property is set. + */ +#define SVN_PROP_TARBALLS SVN_PROP_PREFIX "tarballs" + /** @} */ /** WC props are props that are invisible to users: they're generated Index: Subversion/subversion/include/svn_config.h =================================================================== --- Subversion/subversion/include/svn_config.h (revision 6459) +++ Subversion/subversion/include/svn_config.h (working copy) @@ -78,6 +78,8 @@ #define SVN_CONFIG_OPTION_DIFF_CMD "diff-cmd" #define SVN_CONFIG_OPTION_DIFF3_CMD "diff3-cmd" #define SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG "diff3-has-program-arg" +#define SVN_CONFIG_OPTION_DECOMPRESS_CMD "decompress-cmd" +#define SVN_CONFIG_OPTION_DECOMPRESS_ARGS "decompress-args" #define SVN_CONFIG_SECTION_MISCELLANY "miscellany" #define SVN_CONFIG_OPTION_GLOBAL_IGNORES "global-ignores" #define SVN_CONFIG_OPTION_LOG_ENCODING "log-encoding" Index: Subversion/subversion/include/svn_wc.h =================================================================== --- Subversion/subversion/include/svn_wc.h (revision 6459) +++ Subversion/subversion/include/svn_wc.h (working copy) @@ -251,10 +251,15 @@ svn_wc_notify_update_completed, /** About to update an external module, use for checkouts and switches too, - * end with @c svn_wc_update_completed. + * end with @c svn_wc_notify_update_completed. */ svn_wc_notify_update_external, + /** About to update a tarball, use for checkouts and switches too, + * end with @c svn_wc_notify_update_completed. + */ + svn_wc_notify_update_tarball, + svn_wc_notify_commit_modified, svn_wc_notify_commit_added, svn_wc_notify_commit_deleted, @@ -1270,7 +1275,30 @@ apr_hash_t **externals_new, svn_wc_traversal_info_t *traversal_info); +/** Set @a *tarballs_old and @a *tarballs_new to hash tables representing + * changes to values of the svn:tarballs property on directories + * traversed by @a traversal_info. + * + * @a traversal_info is obtained from @c svn_wc_init_traversal_info, but is + * only useful after it has been passed through another function, such + * as @c svn_wc_crawl_revisions, @c svn_wc_get_update_editor, + * @c svn_wc_get_checkout_editor, @c svn_wc_get_switch_editor, etc. + * + * Each hash maps const char * directory names onto + * const char * values of the tarballs property for that directory. + * The dir names are full paths -- that is, anchor plus target, not target + * alone. The values are not parsed, they are simply copied raw, and are + * never null: directories that acquired or lost the property are + * simply omitted from the appropriate table. Directories whose value + * of the property did not change show the same value in each hash. + * + * The hashes, keys, and values have the same lifetime as @a traversal_info. + */ +void svn_wc_edited_tarballs (apr_hash_t **tarballs_old, + apr_hash_t **tarballs_new, + svn_wc_traversal_info_t *traversal_info); + /** Do a depth-first crawl in a working copy, beginning at @a path. Index: Subversion/subversion/libsvn_wc/wc.h =================================================================== --- Subversion/subversion/libsvn_wc/wc.h (revision 6459) +++ Subversion/subversion/libsvn_wc/wc.h (working copy) @@ -76,6 +76,16 @@ */ apr_hash_t *externals_old; apr_hash_t *externals_new; + + /* The before and after values of the SVN_PROP_TARBALLS property, + * for each directory on which that property changed. These have + * the same layout as those returned by svn_wc_edited_tarballs(). + * + * The hashes, their keys, and their values are allocated in the + * above pool. + */ + apr_hash_t *tarballs_old; + apr_hash_t *tarballs_new; }; Index: Subversion/subversion/libsvn_wc/adm_crawler.c =================================================================== --- Subversion/subversion/libsvn_wc/adm_crawler.c (revision 6459) +++ Subversion/subversion/libsvn_wc/adm_crawler.c (working copy) @@ -158,19 +158,33 @@ in traversal_info. */ if (traversal_info) { - const svn_string_t *val; - SVN_ERR (svn_wc_prop_get (&val, SVN_PROP_EXTERNALS, full_path, adm_access, + const svn_string_t *extval; + const svn_string_t *tarval; + SVN_ERR (svn_wc_prop_get (&extval, SVN_PROP_EXTERNALS, full_path, adm_access, subpool)); - if (val) + if (extval) { apr_pool_t *dup_pool = traversal_info->pool; const char *dup_path = apr_pstrdup (dup_pool, full_path); - const char *dup_val = apr_pstrmemdup (dup_pool, val->data, val->len); + const char *dup_val = apr_pstrmemdup (dup_pool, extval->data, extval->len); apr_hash_set (traversal_info->externals_old, dup_path, APR_HASH_KEY_STRING, dup_val); apr_hash_set (traversal_info->externals_new, dup_path, APR_HASH_KEY_STRING, dup_val); } + + SVN_ERR (svn_wc_prop_get (&tarval, SVN_PROP_TARBALLS, full_path, adm_access, + subpool)); + if (tarval) + { + apr_pool_t *dup_pool = traversal_info->pool; + const char *dup_path = apr_pstrdup (dup_pool, full_path); + const char *dup_val = apr_pstrmemdup (dup_pool, tarval->data, tarval->len); + apr_hash_set (traversal_info->tarballs_old, + dup_path, APR_HASH_KEY_STRING, dup_val); + apr_hash_set (traversal_info->tarballs_new, + dup_path, APR_HASH_KEY_STRING, dup_val); + } } /* Looping over current directory's SVN entries: */ Index: Subversion/subversion/libsvn_wc/update_editor.c =================================================================== --- Subversion/subversion/libsvn_wc/update_editor.c (revision 6459) +++ Subversion/subversion/libsvn_wc/update_editor.c (working copy) @@ -884,6 +884,25 @@ return NULL; } +/* If any of the svn_prop_t objects in PROPCHANGES represents a change + to the SVN_PROP_TARBALLS property, return that change, else return + null. If PROPCHANGES contains more than one such change, return + the first. */ +static const svn_prop_t * +tarballs_prop_changed (apr_array_header_t *propchanges) +{ + int i; + + for (i = 0; i < propchanges->nelts; i++) + { + const svn_prop_t *p = &(APR_ARRAY_IDX(propchanges, i, svn_prop_t)); + if (strcmp (p->name, SVN_PROP_TARBALLS) == 0) + return p; + } + + return NULL; +} + static svn_error_t * close_directory (void *dir_baton, apr_pool_t *pool) @@ -926,11 +945,12 @@ if (db->edit_baton->traversal_info) { svn_wc_traversal_info_t *ti = db->edit_baton->traversal_info; - const svn_prop_t *change = externals_prop_changed (regular_props); + const svn_prop_t *extchange = externals_prop_changed (regular_props); + const svn_prop_t *tarchange = tarballs_prop_changed (regular_props); - if (change) + if (extchange) { - const svn_string_t *new_val_s = change->value; + const svn_string_t *new_val_s = extchange->value; const svn_string_t *old_val_s; SVN_ERR (svn_wc_prop_get @@ -966,8 +986,46 @@ new_val_s->len)); } } - } + if (tarchange) + { + const svn_string_t *new_val_s = tarchange->value; + const svn_string_t *old_val_s; + SVN_ERR (svn_wc_prop_get + (&old_val_s, SVN_PROP_TARBALLS, + db->path, adm_access, db->pool)); + + if ((new_val_s == NULL) && (old_val_s == NULL)) + ; /* No value before, no value after... so do nothing. */ + else if (new_val_s && old_val_s + && (svn_string_compare (old_val_s, new_val_s))) + ; /* Value did not change... so do nothing. */ + else /* something changed, record the change */ + { + /* We can't assume that ti came pre-loaded with the + old values of the svn:externals property. Yes, + most callers will have already initialized ti by + sending it through svn_wc_crawl_revisions, but we + shouldn't count on that here -- so we set both the + old and new values again. */ + + if (old_val_s) + apr_hash_set (ti->tarballs_old, + apr_pstrdup (ti->pool, db->path), + APR_HASH_KEY_STRING, + apr_pstrmemdup (ti->pool, old_val_s->data, + old_val_s->len)); + + if (new_val_s) + apr_hash_set (ti->tarballs_new, + apr_pstrdup (ti->pool, db->path), + APR_HASH_KEY_STRING, + apr_pstrmemdup (ti->pool, new_val_s->data, + new_val_s->len)); + } + } + } + /* Merge pending properties into temporary files (ignoring conflicts). */ SVN_ERR_W (svn_wc__merge_prop_diffs (&prop_state, @@ -2112,7 +2170,8 @@ ti->pool = pool; ti->externals_old = apr_hash_make (pool); ti->externals_new = apr_hash_make (pool); - + ti->tarballs_old = apr_hash_make (pool); + ti->tarballs_new = apr_hash_make (pool); return ti; } @@ -2127,7 +2186,17 @@ } +void +svn_wc_edited_tarballs (apr_hash_t **tarballs_old, + apr_hash_t **tarballs_new, + svn_wc_traversal_info_t *traversal_info) +{ + *tarballs_old = traversal_info->tarballs_old; + *tarballs_new = traversal_info->tarballs_new; +} + + /* THE GOAL Note the following actions, where X is the thing we wish to update, Index: Subversion/subversion/libsvn_subr/config_file.c =================================================================== --- Subversion/subversion/libsvn_subr/config_file.c (revision 6459) +++ Subversion/subversion/libsvn_subr/config_file.c (working copy) @@ -923,11 +923,18 @@ "### Subversion's internal diff3 implementation.\n" "### Set diff3-has-program-arg to `true' or `yes' if your `diff3'\n" "### program accepts the `--diff-program' option.\n" + "### Set decompress-cmd to the absolute path of your untar/unzip program.\n" + "### This will be used for the svn:tarballs property to extract the tarball.\n" + "### Set decompress-args to the arguments your untar/unzip program needs.\n" + "### Read the docs of your favourite untar/unzip program to find out\n" + "### which arguments you need to pass.\n" "# [helpers]\n" "# editor-cmd = editor (vi, emacs, notepad, etc.)\n" "# diff-cmd = diff_program (diff, gdiff, etc.)\n" "# diff3-cmd = diff3_program (diff3, gdiff3, etc.)\n" "# diff3-has-program-arg = [true | false]\n" + "# decompress-cmd = extract-program\n" + "# decompress-args = command_line_arguments_for_extract_program\n" "\n" "### Section for configuring tunnel agents.\n" "# [tunnels]\n" Index: Subversion/subversion/libsvn_client/switch.c =================================================================== --- Subversion/subversion/libsvn_client/switch.c (revision 6459) +++ Subversion/subversion/libsvn_client/switch.c (working copy) @@ -209,6 +209,9 @@ ×tamp_sleep, ctx, pool)); + SVN_ERR (svn_client__handle_tarballs (traversal_info, + ctx, + pool)); } else if (entry->kind == svn_node_file) Index: Subversion/subversion/libsvn_client/checkout.c =================================================================== --- Subversion/subversion/libsvn_client/checkout.c (revision 6459) +++ Subversion/subversion/libsvn_client/checkout.c (working copy) @@ -172,9 +172,13 @@ */ SVN_ERR (svn_client__handle_externals (traversal_info, FALSE, use_sleep, ctx, pool)); + if (sleep_here) svn_sleep_for_timestamps (); + SVN_ERR (svn_client__handle_tarballs (traversal_info, + ctx, pool)); + return SVN_NO_ERROR; } Index: Subversion/subversion/libsvn_client/tarball.c =================================================================== --- Subversion/subversion/libsvn_client/tarball.c (working copy) +++ Subversion/subversion/libsvn_client/tarball.c (working copy) @@ -0,0 +1,645 @@ +/* + * tarball.c: handle the svn:tarball property + * + * ==================================================================== + * Copyright (c) 2000-2003 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include +#include "svn_wc.h" +#include "svn_pools.h" +#include "svn_delta.h" +#include "svn_client.h" +#include "svn_config.h" +#include "svn_string.h" +#include "svn_hash.h" +#include "svn_types.h" +#include "svn_error.h" +#include "svn_path.h" +#include "client.h" + +#include +#include +#include + + + +/* Set *PROXY_HOST, *PROXY_PORT, *PROXY_USERNAME, *PROXY_PASSWORD, + * *TIMEOUT_SECONDS to the information for REQUESTED_HOST, + * allocated in POOL, if there is any applicable information. If there is + * no applicable information or if there is an error, then set *PROXY_PORT + * to (unsigned int) -1, *TIMEOUT_SECONDS to zero, and the + * rest to NULL. This function can return an error, so before checking any + * values, check the error return value. + */ +static svn_error_t * +get_proxy_settings (const char **proxy_host, + unsigned long *proxy_port, + const char **proxy_username, + const char **proxy_password, + int *timeout_seconds, + apr_hash_t *config, + const char *requested_host, + apr_pool_t *pool) +{ + const char *exceptions, *port_str, *timeout_str, *server_group; + svn_boolean_t is_exception = FALSE; + svn_config_t *cfg; + + /* If we find nothing, default to nulls. */ + *proxy_host = NULL; + *proxy_port = (unsigned int) -1; + *proxy_username = NULL; + *proxy_password = NULL; + port_str = NULL; + timeout_str = NULL; + + cfg = config ? apr_hash_get (config, + SVN_CONFIG_CATEGORY_SERVERS, + APR_HASH_KEY_STRING) : NULL; + + /* If there are defaults, use them, but only if the requested host + is not one of the exceptions to the defaults. */ + svn_config_get(cfg, &exceptions, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, NULL); + if (exceptions) + { + apr_array_header_t *l = svn_cstring_split (exceptions, ",", TRUE, pool); + is_exception = svn_cstring_match_glob_list (requested_host, l); + } + if (! is_exception) + { + svn_config_get (cfg, proxy_host, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL); + svn_config_get (cfg, &port_str, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL); + svn_config_get (cfg, proxy_username, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL); + svn_config_get (cfg, proxy_password, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL); + svn_config_get (cfg, &timeout_str, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL); + } + + if (cfg) + server_group = svn_config_find_group (cfg, requested_host, + SVN_CONFIG_SECTION_GROUPS, pool); + else + server_group = NULL; + + if (server_group) + { + svn_config_get (cfg, proxy_host, server_group, + SVN_CONFIG_OPTION_HTTP_PROXY_HOST, *proxy_host); + svn_config_get (cfg, &port_str, server_group, + SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str); + svn_config_get (cfg, proxy_username, server_group, + SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, *proxy_username); + svn_config_get (cfg, proxy_password, server_group, + SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, *proxy_password); + svn_config_get (cfg, &timeout_str, server_group, + SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str); + } + + /* Special case: convert the port value, if any. */ + if (port_str) + { + char *endstr; + const long int port = strtol (port_str, &endstr, 10); + + if (*endstr) + return svn_error_create (SVN_ERR_RA_ILLEGAL_URL, NULL, + "illegal character in proxy port number"); + if (port < 0) + return svn_error_create (SVN_ERR_RA_ILLEGAL_URL, NULL, + "negative proxy port number"); + if (port > 65535) + return svn_error_create (SVN_ERR_RA_ILLEGAL_URL, NULL, + "proxy port number greater than maximum TCP " + "port number 65535"); + *proxy_port = port; + } + else + *proxy_port = 80; + + if (timeout_str) + { + char *endstr; + const long int timeout = strtol (timeout_str, &endstr, 10); + + if (*endstr) + return svn_error_create (SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL, + "illegal character in timeout value"); + if (timeout < 0) + return svn_error_create (SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL, + "negative timeout value"); + *timeout_seconds = timeout; + } + else + *timeout_seconds = 0; + + return SVN_NO_ERROR; +} + +/* callback function, used by libcurl during the download. If the + return value is nonzero then libcurl aborts the download. */ +static int +url_progress_func (svn_client_ctx_t *ctx, + double t, /* dltotal */ + double d, /* dlnow */ + double ultotal, + double ulnow) +{ + svn_error_t * err = NULL; + err = ctx->cancel_func (ctx->cancel_baton); + return (err != NULL); +} + +/* callback function, used by libcurl to read data. */ +static size_t +url_read_func (void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + return fread(ptr, size, nmemb, stream); +} + +/* downloads an URL and saves it in a file. */ +static svn_error_t * +fetch_url_to_file (const char * url, + const char * file, + svn_client_ctx_t * ctx, + apr_pool_t * pool) +{ + svn_error_t * err = NULL; + CURL *curl; + FILE *outfile; + char errbuf[CURL_ERROR_SIZE]; + const char * proxy_host = NULL; + unsigned long proxy_port; + const char * proxy_username = NULL; + const char * proxy_password = NULL; + const char * proxy_userpwd = NULL; + int timeout_seconds; + + SVN_ERR (get_proxy_settings (&proxy_host, + &proxy_port, + &proxy_username, + &proxy_password, + &timeout_seconds, + ctx->config, + url, + pool)); + curl = curl_easy_init (); + if(curl) + { + outfile = fopen (file, "wb+"); + + curl_easy_setopt (curl, CURLOPT_URL, url); + curl_easy_setopt (curl, CURLOPT_FILE, outfile); + curl_easy_setopt (curl, CURLOPT_READFUNCTION, url_read_func); + curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt (curl, CURLOPT_NOPROGRESS, FALSE); + curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, url_progress_func); + curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, ctx); + curl_easy_setopt (curl, CURLOPT_FAILONERROR, TRUE); + curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, TRUE); + if (proxy_host) + { + curl_easy_setopt (curl, CURLOPT_PROXY, proxy_host); + curl_easy_setopt (curl, CURLOPT_PROXYPORT, proxy_port); + } + if ((proxy_username)&&(proxy_password)) + { + apr_pstrcat (pool, proxy_userpwd, proxy_username, ":", proxy_password); + curl_easy_setopt (curl, CURLOPT_PROXYUSERPWD, proxy_userpwd); + } + + if (curl_easy_perform (curl)) + { + err = svn_error_create (SVN_ERR_CLIENT_CONNECTION_IO_ERROR, NULL, errbuf); + } + + fclose (outfile); + /* always cleanup */ + curl_easy_cleanup (curl); + } + return err; +} + +/* Checks if a untar/unzip program is set in the config + file. If yes then the compressed file \c tarballfile + is extracted using the given program. + If no program is set then this function returns without + an error and leaves the tarball untouched. */ +static svn_error_t * +decompress_tarball (const char * tarballfile, + const char * targetdir, + svn_client_ctx_t * ctx, + apr_pool_t * pool) +{ + const char *args[5]; + const char * decompress_cmd = NULL; + const char * decompress_args = NULL; + const char * folder; + apr_finfo_t finfo; + apr_dir_t * new_dir; + int filecount = 0; + int dircount = 0; + int i = 0; + + svn_config_t *cfg = apr_hash_get (ctx->config, + SVN_CONFIG_CATEGORY_CONFIG, + APR_HASH_KEY_STRING); + + svn_config_get (cfg, &decompress_cmd, + SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DECOMPRESS_CMD, + NULL); + if (! decompress_cmd) + { + /* if no decompressing program is specified, we + just return without error. That way the user + can decompress the tarball later manually */ + return SVN_NO_ERROR; + } + args[i++] = decompress_cmd; + svn_config_get (cfg, &decompress_args, + SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DECOMPRESS_ARGS, + NULL); + if (decompress_args) + args[i++] = decompress_args; + args[i++] = tarballfile; + args[i++] = targetdir; + args[i] = 0; + + /* Run decompress program, output to the targetdir */ + SVN_ERR (svn_io_run_cmd (NULL, decompress_cmd, args, + NULL, NULL, + FALSE, /* clean environment */ + NULL, NULL, NULL, + pool)); + /* depending on how the tarball was compressed, it already may + have placed all the files into an own folder. If we would do + nothing about that, we could end up with a structure like this: + /subversion/neon/neon-0.23.9/ + instead of + /subversion/neon/ + + So we first check if in the destination folder is only one + folder and no other files. If that is the case then we + move that single folder over the destination folder */ + if (apr_dir_open (&new_dir, targetdir, pool) == APR_SUCCESS) + { + while (apr_dir_read (&finfo, APR_FINFO_TYPE, new_dir) == APR_SUCCESS) + { + if (finfo.filetype == APR_DIR) + { + dircount++; + folder = finfo.name; + } + if (finfo.filetype == APR_REG) + filecount++; + } + apr_dir_close (new_dir); + } + + if ((dircount == 3)&&(filecount == 0)) /* the first two folders are always . and .. */ + { + /* yes, the tarball got it's content extracted in a subfolder */ + const char * folderpath; + const char * folderpathtemp; + const char * parentdir; + const char * dir; + svn_path_split (targetdir, &parentdir, &dir, pool); + folderpath = svn_path_join (targetdir, folder, pool); + /* This is not the best way to do that. A function which returns a + temporary filename should be used for that. */ + folderpathtemp = svn_path_join (parentdir, "___svntempdir___123456789", pool); + apr_file_rename (folderpath, folderpathtemp, pool); + apr_dir_remove (targetdir, pool); + apr_file_rename (folderpathtemp, targetdir, pool); + } + /* at last delete the downloaded tarball since it's no longer needed */ + apr_file_remove(tarballfile, pool); + return SVN_NO_ERROR; +} + +/* One tarball item. This usually represents one line from an + svn:tarball description but with the path and URL canonicalized. */ +struct tarball_item +{ + /* The name of the subdirectory into which this tarball should be + extracted. This is relative to the parent directory that holds + this tarball item. (Note that these structs are often stored in + hash tables with the target dirs as keys, so this field will + often be redundant.) */ + const char *target_dir; + + /* Where to download from. */ + const char *url; + +}; + +/* downloads and extracts a tarball */ +static svn_error_t * +fetch_and_decompress (struct tarball_item * item, + const char * path, + svn_client_ctx_t * ctx, + apr_pool_t * pool) +{ + const char * downloadpath; + const char * dir; + const char * file; + const char * dummyfile; + /* First notify that we're about to handle a tarball. */ + if (ctx->notify_func) + { + (*ctx->notify_func) (ctx->notify_baton, + item->url, + svn_wc_notify_update_tarball, + svn_node_unknown, + NULL, + svn_wc_notify_state_unknown, + svn_wc_notify_state_unknown, + SVN_INVALID_REVNUM); + } + /* create a path to where the tarball should be downloaded + before decompressing it to the target directory */ + svn_path_split (item->url, &dir, &file, pool); + svn_path_split (path, &dir, &dummyfile, pool); + downloadpath = svn_path_join (dir, file, pool); + + SVN_ERR (fetch_url_to_file (item->url, downloadpath, ctx, pool)); + SVN_ERR (decompress_tarball (downloadpath, path, ctx, pool)); + return SVN_NO_ERROR; +} + +/* Set *TARBALLS_P to a hash table whose keys are target subdir + * names, and values are `struct external_item *' objects, + * based on DESC. + * + * The format of TARBALLS is the same as for values of the directory + * property SVN_PROP_TARBALLS, which see. + * + * Allocate the table, keys, and values in POOL. + * + * If the format of DESC is invalid, don't touch *TARBALLS_P and + * return SVN_ERR_CLIENT_INVALID_TARBALLS_DESCRIPTION. + * + * Use PARENT_DIRECTORY only in constructing error strings. + */ +static svn_error_t * +parse_tarballs_description (apr_hash_t **tarballs_p, + const char *parent_directory, + const char *desc, + apr_pool_t *pool) +{ + apr_hash_t *tarballs = apr_hash_make (pool); + apr_array_header_t *lines = svn_cstring_split (desc, "\n\r", TRUE, pool); + int i; + + for (i = 0; i < lines->nelts; i++) + { + const char *line = APR_ARRAY_IDX (lines, i, const char *); + apr_array_header_t *line_parts; + struct tarball_item *item; + + if ((! line) || (line[0] == '#')) + continue; + + /* else proceed */ + + line_parts = svn_cstring_split (line, " \t", TRUE, pool); + + item = apr_palloc (pool, sizeof (*item)); + + if (line_parts->nelts < 2) + goto parse_error; + + else if (line_parts->nelts == 2) + { + item->target_dir = APR_ARRAY_IDX (line_parts, 0, const char *); + item->url = APR_ARRAY_IDX (line_parts, 1, const char *); + } + else /* too many items on line */ + goto parse_error; + + if (0) + { + parse_error: + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_TARBALLS_DESCRIPTION, NULL, + "error parsing " SVN_PROP_TARBALLS " property on '%s':\n" + "Invalid line: '%s'", parent_directory, line); + } + + item->target_dir = svn_path_canonicalize (item->target_dir, pool); + item->url = svn_path_canonicalize (item->url, pool); + + apr_hash_set (tarballs, item->target_dir, APR_HASH_KEY_STRING, item); + } + + *tarballs_p = tarballs; + + return SVN_NO_ERROR; +} + +/* Closure for handle_tarballs_item_change. */ +struct handle_tarballs_item_change_baton +{ + /* As returned by parse_tarballs_description(). */ + apr_hash_t *new_desc; + apr_hash_t *old_desc; + + /* The directory that has this tarballs property. */ + const char *parent_dir; + + svn_client_ctx_t *ctx; + + apr_pool_t *pool; +}; + +/* Return true if NEW_ITEM and OLD_ITEM represent the same tarball + item extracted into the same target subdir, else return false. */ +static svn_boolean_t +compare_tarball_items (struct tarball_item *new_item, + struct tarball_item *old_item) +{ + if ((strcmp (new_item->target_dir, old_item->target_dir) != 0) + || (strcmp (new_item->url, old_item->url) != 0)) + return FALSE; + + /* Else. */ + return TRUE; +} + + +/* Remove PATH from disk, and do the same to any underneath PATH + * Use POOL for all temporary allocation. + */ +static svn_error_t * +relegate_tarball (const char *path, + apr_pool_t *pool) +{ + SVN_ERR (svn_io_remove_dir (path, pool)); + return SVN_NO_ERROR; +} + +/* This implements the `svn_hash_diff_func_t' interface. + BATON is of type `struct handle_tarball_item_change_baton *'. */ +static svn_error_t * +handle_tarball_item_change (const void *key, apr_ssize_t klen, + enum svn_hash_diff_key_status status, + void *baton) +{ + struct handle_tarballs_item_change_baton *ib = baton; + struct tarball_item *old_item, *new_item; + const char *path = svn_path_join (ib->parent_dir, + (const char *) key, ib->pool); + + if (ib->old_desc) + old_item = apr_hash_get (ib->old_desc, key, klen); + else + old_item = NULL; + + if (ib->new_desc) + new_item = apr_hash_get (ib->new_desc, key, klen); + else + new_item = NULL; + + /* We couldn't possibly be here if both values were null, right? */ + assert (old_item || new_item); + + if (! old_item) + { + /* The target dir might have multiple components. Guarantee + the path leading down to the last component. */ + { + const char *checkout_parent; + svn_path_split (path, &checkout_parent, NULL, ib->pool); + SVN_ERR (svn_io_make_dir_recursively (checkout_parent, ib->pool)); + } + + fetch_and_decompress(new_item, path, ib->ctx, ib->pool); + } + else if (! new_item) + { + SVN_ERR (relegate_tarball (old_item->target_dir, ib->pool)); + } + else if (! compare_tarball_items (new_item, old_item)) + { + + SVN_ERR (relegate_tarball (old_item->target_dir, + ib->pool)); + fetch_and_decompress (new_item, path, ib->ctx, ib->pool); + } + else if (compare_tarball_items (new_item, old_item)) + { + /* the property hasn't changed, but maybe the extracted folder + is missing and needs to be downloaded again */ + svn_node_kind_t kind; + SVN_ERR (svn_io_check_path (new_item->target_dir, &kind, ib->pool)); + if (kind != svn_node_dir) + { + /* the extracted directory doesn't exist, but maybe the tarball? */ + const char * dir; + const char * file; + const char * dummyfile; + svn_path_split (new_item->url, &dir, &file, ib->pool); + svn_path_split (path, &dir, &dummyfile, ib->pool); + dummyfile = svn_path_join (dir, file, ib->pool); + SVN_ERR (svn_io_check_path (dummyfile, &kind, ib->pool)); + if (kind != svn_node_file) + fetch_and_decompress(new_item, path, ib->ctx, ib->pool); + } + } + return SVN_NO_ERROR; +} + +/* Closure for handle_tarballs_change. */ +struct handle_tarballs_desc_change_baton +{ + /* As returned by svn_wc_edited_tarballs(). */ + apr_hash_t *tarballs_new; + apr_hash_t *tarballs_old; + + /* Passed through to handle_tarballs_item_change_baton. */ + svn_client_ctx_t *ctx; + + apr_pool_t *pool; +}; + +/* This implements the `svn_hash_diff_func_t' interface. + BATON is of type `struct handle_tarballs_desc_change_baton *'. +*/ +static svn_error_t * +handle_tarballs_desc_change (const void *key, apr_ssize_t klen, + enum svn_hash_diff_key_status status, + void *baton) +{ + struct handle_tarballs_desc_change_baton *cb = baton; + struct handle_tarballs_item_change_baton ib; + const char *old_desc_text, *new_desc_text; + apr_hash_t *old_desc, *new_desc; + + if ((old_desc_text = apr_hash_get (cb->tarballs_old, key, klen))) + SVN_ERR (parse_tarballs_description (&old_desc, (const char *) key, + old_desc_text, cb->pool)); + else + old_desc = NULL; + + if ((new_desc_text = apr_hash_get (cb->tarballs_new, key, klen))) + SVN_ERR (parse_tarballs_description (&new_desc, (const char *) key, + new_desc_text, cb->pool)); + else + new_desc = NULL; + + ib.old_desc = old_desc; + ib.new_desc = new_desc; + ib.parent_dir = (const char *) key; + ib.ctx = cb->ctx; + ib.pool = cb->pool; + + SVN_ERR (svn_hash_diff (old_desc, new_desc, + handle_tarball_item_change, &ib, cb->pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__handle_tarballs (svn_wc_traversal_info_t *traversal_info, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + apr_hash_t *tarballs_old, *tarballs_new; + struct handle_tarballs_desc_change_baton cb; + + svn_wc_edited_tarballs (&tarballs_old, &tarballs_new, traversal_info); + + cb.tarballs_new = tarballs_new; + cb.tarballs_old = tarballs_old; + cb.ctx = ctx; + cb.pool = pool; + + SVN_ERR (svn_hash_diff (tarballs_old, tarballs_new, + handle_tarballs_desc_change, &cb, pool)); + + return SVN_NO_ERROR; +} Index: Subversion/subversion/libsvn_client/update.c =================================================================== --- Subversion/subversion/libsvn_client/update.c (revision 6459) +++ Subversion/subversion/libsvn_client/update.c (working copy) @@ -169,12 +169,17 @@ handling external items (and any errors therefrom) doesn't delay the primary operation. */ if (recurse) + { SVN_ERR (svn_client__handle_externals (traversal_info, TRUE, /* update unchanged ones */ use_sleep, ctx, pool)); + SVN_ERR (svn_client__handle_tarballs (traversal_info, + ctx, + pool)); + } if (sleep_here) svn_sleep_for_timestamps (); Index: Subversion/subversion/libsvn_client/client.h =================================================================== --- Subversion/subversion/libsvn_client/client.h (revision 6459) +++ Subversion/subversion/libsvn_client/client.h (working copy) @@ -536,7 +536,24 @@ svn_client_ctx_t *ctx, apr_pool_t *pool); +/* Handle changes to the svn:tarballs property in the tree traversed + by TRAVERSAL_INFO (obtained from svn_wc_get_checkout_editor, + svn_wc_get_update_editor, svn_wc_get_switch_editor, for example). + For each changed value of the property, discover the nature of the + change and behave appropriately -- either download the tarball + again, or delete the tarball and/or the directory where the tarball + was extracted to. + + Pass NOTIFY_FUNC with NOTIFY_BATON along to svn_client_checkout(). + + Use POOL for temporary allocation. */ +svn_error_t *svn_client__handle_tarballs + (svn_wc_traversal_info_t *traversal_info, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + + #ifdef __cplusplus } Index: Subversion/subversion/clients/cmdline/main.c =================================================================== --- Subversion/subversion/clients/cmdline/main.c (revision 6459) +++ Subversion/subversion/clients/cmdline/main.c (working copy) @@ -407,7 +407,12 @@ " each of which consists of a relative directory path, optional\n" " revision flags, and an URL. For example\n" " foo http://example.com/repos/zig\n" - " foo/bar -r 1234 http://example.com/repos/zag\n", + " foo/bar -r 1234 http://example.com/repos/zag\n" + " svn:tarballs - A newline separated list of tarballs,\n" + " each of which consists of a relative directory path and an URL.\n" + " for example\n" + " foo http://example.com/tarball.zip\n" + " foo/bar http://example.com/dir/project.tar.gz\n", {'F', 'q', 'r', svn_cl__targets_opt, 'R', svn_cl__revprop_opt, SVN_CL__AUTH_OPTIONS, svn_cl__encoding_opt} }, Index: Subversion/subversion/clients/cmdline/feedback.c =================================================================== --- Subversion/subversion/clients/cmdline/feedback.c (revision 6459) +++ Subversion/subversion/clients/cmdline/feedback.c (working copy) @@ -165,7 +165,9 @@ want different output, we'll have to add new actions. */ printf ("\nFetching external item into %s\n", path_native); break; - + case svn_wc_notify_update_tarball: + printf ("\nDownloading tarball from %s\n", path_native); + break; case svn_wc_notify_update_completed: { if (! nb->suppress_final_line)