int main (int argc, const char **argv) { apr_pool_t *pool; svn_error_t *err; apr_hash_t *dirents; const char *upload_file, *URL; const char *parent_URL, *basename; svn_ra_plugin_t *ra_lib; void *session, *ra_baton; svn_revnum_t rev; const svn_delta_editor_t *editor; void *edit_baton; svn_dirent_t *dirent; svn_ra_callbacks_t *cbtable; apr_hash_t *cfg_hash; svn_auth_baton_t *auth_baton; if (argc <= 2) { printf ("Usage: %s PATH URL\n", argv[0]); printf (" Uploads file at PATH to Subversion repository URL.\n"); return EXIT_FAILURE; } upload_file = argv[1]; URL = argv[2]; /* Initialize the app. Send all error messages to 'stderr'. */ if (svn_cmdline_init ("minimal_client", stderr) != EXIT_SUCCESS) return EXIT_FAILURE; /* Create top-level memory pool. Be sure to read the HACKING file to understand how to properly use/free subpools. */ pool = svn_pool_create (NULL); /* Initialize the FS library. */ err = svn_fs_initialize (pool); if (err) goto hit_error; /* Make sure the ~/.subversion run-time config files exist, and load. */ err = svn_config_ensure (NULL, pool); if (err) goto hit_error; err = svn_config_get_config (&cfg_hash, NULL, pool); if (err) goto hit_error; /* Build an authentication baton. */ { /* There are many different kinds of authentication back-end "providers". See svn_auth.h for a full overview. */ svn_auth_provider_object_t *provider; apr_array_header_t *providers = apr_array_make (pool, 4, sizeof (svn_auth_provider_object_t *)); svn_client_get_simple_prompt_provider (&provider, my_simple_prompt_callback, NULL, /* baton */ 2, /* retry limit */ pool); APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; svn_client_get_username_prompt_provider (&provider, my_username_prompt_callback, NULL, /* baton */ 2, /* retry limit */ pool); APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; /* Register the auth-providers into the context's auth_baton. */ svn_auth_open (&auth_baton, providers, pool); } /* Create a table of callbacks for the RA session, mostly nonexistent. */ cbtable = apr_pcalloc (pool, sizeof(*cbtable)); cbtable->auth_baton = auth_baton; cbtable->open_tmp_file = open_tmp_file; /* Now do the real work. */ /* Open an RA session to the parent URL, fetch current HEAD rev and "lock" onto that revnum for the remainder of the session. */ svn_path_split (URL, &parent_URL, &basename, pool); err = svn_ra_init_ra_libs (&ra_baton, pool); if (err) goto hit_error; err = svn_ra_get_ra_library (&ra_lib, ra_baton, parent_URL, pool); if (err) goto hit_error; err = ra_lib->open (&session, parent_URL, cbtable, NULL, cfg_hash, pool); if (err) goto hit_error; err = ra_lib->get_latest_revnum (session, &rev, pool); if (err) goto hit_error; /* Examine contents of parent dir in the rev. */ err = ra_lib->get_dir (session, "", rev, &dirents, NULL, NULL, pool); if (err) goto hit_error; /* Sanity checks. Don't let the user shoot himself *too* much. */ dirent = apr_hash_get (dirents, basename, APR_HASH_KEY_STRING); if (dirent && dirent->kind == svn_node_dir) { printf ("Sorry, a directory already exists at that URL.\n"); return EXIT_FAILURE; } if (dirent && dirent->kind == svn_node_file) { char answer[5]; printf ("\n*** WARNING ***\n\n"); printf ("You're about to overwrite r%ld of this file.\n", rev); printf ("It was last changed by user '%s',\n", dirent->last_author ? dirent->last_author : "?"); printf ("on %s.\n", svn_time_to_human_cstring (dirent->time, pool)); printf ("\nSomebody *might* have just changed the file seconds ago,\n" "and your upload would be overwriting their changes!\n\n"); err = prompt_and_read_line("Are you SURE you want to upload? [y/n]", answer, sizeof(answer)); if (err) goto hit_error; if (apr_strnatcasecmp (answer, "y")) { printf ("Operation aborted.\n"); return EXIT_SUCCESS; } } /* Fetch a commit editor (it's anchored on the parent URL, because the session is too.) */ /* ### someday add an option for a user-written commit message? */ err = ra_lib->get_commit_editor (session, &editor, &edit_baton, "File upload from 'svnput' program.", my_commit_callback, NULL, pool); if (err) goto hit_error; /* Drive the editor */ { void *root_baton, *file_baton, *handler_baton; svn_txdelta_window_handler_t handler; svn_stream_t *contents; apr_file_t *f = NULL; err = editor->open_root (edit_baton, rev, pool, &root_baton); if (err) goto hit_error; if (! dirent) { err = editor->add_file (basename, root_baton, NULL, SVN_INVALID_REVNUM, pool, &file_baton); } else { err = editor->open_file (basename, root_baton, rev, pool, &file_baton); } if (err) goto hit_error; err = editor->apply_textdelta (file_baton, NULL, pool, &handler, &handler_baton); if (err) goto hit_error; err = svn_io_file_open (&f, upload_file, APR_READ, APR_OS_DEFAULT, pool); if (err) goto hit_error; contents = svn_stream_from_aprfile (f, pool); err = svn_txdelta_send_stream (contents, handler, handler_baton, NULL, pool); if (err) goto hit_error; err = svn_io_file_close (f, pool); if (err) goto hit_error; err = editor->close_file (file_baton, NULL, pool); if (err) goto hit_error; err = editor->close_edit (edit_baton, pool); if (err) goto hit_error; } return EXIT_SUCCESS; hit_error: svn_handle_error2 (err, stderr, FALSE, "svnput: "); return EXIT_FAILURE; }
/* Drive EDITOR to affect the change represented by OPERATION. HEAD is the last-known youngest revision in the repository. */ static svn_error_t * drive(struct operation *operation, svn_revnum_t head, const svn_delta_editor_t *editor, apr_pool_t *pool) { apr_pool_t *subpool = svn_pool_create(pool); apr_hash_index_t *hi; struct driver_state state; for (hi = apr_hash_first(pool, operation->children); hi; hi = apr_hash_next(hi)) { const void *key; void *val; struct operation *child; void *file_baton = NULL; svn_pool_clear(subpool); apr_hash_this(hi, &key, NULL, &val); child = val; /* Deletes and replacements are simple -- delete something. */ if (child->operation == OP_DELETE || child->operation == OP_REPLACE) { SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool)); } /* Opens could be for directories or files. */ if (child->operation == OP_OPEN) { if (child->kind == svn_node_dir) { SVN_ERR(editor->open_directory(key, operation->baton, head, subpool, &child->baton)); } else { SVN_ERR(editor->open_file(key, operation->baton, head, subpool, &file_baton)); } } /* Adds and replacements could also be for directories or files. */ if (child->operation == OP_ADD || child->operation == OP_REPLACE || child->operation == OP_PROPSET) { if (child->kind == svn_node_dir) { SVN_ERR(editor->add_directory(key, operation->baton, child->url, child->rev, subpool, &child->baton)); } else { SVN_ERR(editor->add_file(key, operation->baton, child->url, child->rev, subpool, &file_baton)); } } /* If there's a source file and an open file baton, we get to change textual contents. */ if ((child->src_file) && (file_baton)) { svn_txdelta_window_handler_t handler; void *handler_baton; svn_stream_t *contents; apr_file_t *f = NULL; SVN_ERR(editor->apply_textdelta(file_baton, NULL, subpool, &handler, &handler_baton)); if (strcmp(child->src_file, "-")) { SVN_ERR(svn_io_file_open(&f, child->src_file, APR_READ, APR_OS_DEFAULT, pool)); } else { apr_status_t apr_err = apr_file_open_stdin(&f, pool); if (apr_err) return svn_error_wrap_apr(apr_err, "Can't open stdin"); } contents = svn_stream_from_aprfile(f, pool); SVN_ERR(svn_txdelta_send_stream(contents, handler, handler_baton, NULL, pool)); SVN_ERR(svn_io_file_close(f, pool)); } /* If we opened a file, we need to apply outstanding propmods, then close it. */ if (file_baton) { if ((child->kind == svn_node_file) && (! apr_is_empty_table(child->props))) { state.baton = file_baton; state.pool = subpool; state.editor = editor; state.kind = child->kind; if (! apr_table_do(set_props, &state, child->props, NULL)) SVN_ERR(state.err); } SVN_ERR(editor->close_file(file_baton, NULL, subpool)); } /* If we opened, added, or replaced a directory, we need to recurse, apply outstanding propmods, and then close it. */ if ((child->kind == svn_node_dir) && (child->operation == OP_OPEN || child->operation == OP_ADD || child->operation == OP_REPLACE)) { SVN_ERR(drive(child, head, editor, subpool)); if ((child->kind == svn_node_dir) && (! apr_is_empty_table(child->props))) { state.baton = child->baton; state.pool = subpool; state.editor = editor; state.kind = child->kind; if (! apr_table_do(set_props, &state, child->props, NULL)) SVN_ERR(state.err); } SVN_ERR(editor->close_directory(child->baton, subpool)); } } svn_pool_destroy(subpool); return SVN_NO_ERROR; }