blob: 87a7bc8d2f556561fe423acc26047a1e9dab147f [file] [log] [blame]
/*
* commit.c: wrappers around wc commit functionality.
*
* ====================================================================
* Copyright (c) 2000-2002 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 <string.h>
#include <assert.h>
#include <apr_strings.h>
#include "svn_wc.h"
#include "svn_ra.h"
#include "svn_delta.h"
#include "svn_client.h"
#include "svn_string.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_path.h"
#include "svn_test.h"
#include "svn_io.h"
#include "client.h"
/* Hash value for FILES hash in the import routines. */
struct imported_file
{
apr_pool_t *subpool;
void *file_baton;
};
/* Apply PATH's contents (as a delta against the empty string) to
FILE_BATON in EDITOR. Use POOL for any temporary allocation. */
static svn_error_t *
send_file_contents (const char *path,
void *file_baton,
const svn_delta_editor_t *editor,
apr_pool_t *pool)
{
svn_stream_t *contents;
svn_txdelta_window_handler_t handler;
void *handler_baton;
apr_file_t *f = NULL;
apr_status_t apr_err;
/* Get an apr file for PATH. */
SVN_ERR (svn_io_file_open (&f, path, APR_READ, APR_OS_DEFAULT, pool));
/* Get a readable stream of the file's contents. */
contents = svn_stream_from_aprfile (f, pool);
/* Get an editor func that wants to consume the delta stream. */
SVN_ERR (editor->apply_textdelta (file_baton, &handler, &handler_baton));
/* Send the file's contents to the delta-window handler. */
SVN_ERR (svn_txdelta_send_stream (contents, handler, handler_baton, pool));
/* Close the file. */
apr_err = apr_file_close (f);
if (apr_err)
return svn_error_createf
(apr_err, 0, NULL, pool, "error closing `%s'", path);
return SVN_NO_ERROR;
}
/* Import file PATH as EDIT_PATH in the repository directory indicated
* by DIR_BATON in EDITOR.
*
* Accumulate file paths and their batons in FILES, which must be
* non-null. (These are used to send postfix textdeltas later).
*
* If NOTIFY_FUNC is non-null, invoke it with NOTIFY_BATON for each
* file. ### add mime-type (or at least binary) indicator to
* notify_func ###
*
* Use POOL for any temporary allocation.
*/
static svn_error_t *
import_file (apr_hash_t *files,
svn_wc_notify_func_t notify_func,
void *notify_baton,
const svn_delta_editor_t *editor,
void *dir_baton,
const char *path,
const char *edit_path,
apr_pool_t *pool)
{
void *file_baton;
const char *mimetype;
apr_pool_t *hash_pool = apr_hash_pool_get (files);
apr_pool_t *subpool = svn_pool_create (hash_pool);
const char *filepath = apr_pstrdup (hash_pool, path);
struct imported_file *value = apr_palloc (hash_pool, sizeof (*value));
/* Add the file, using the pool from the FILES hash. */
SVN_ERR (editor->add_file (edit_path, dir_baton, NULL, SVN_INVALID_REVNUM,
subpool, &file_baton));
/* If the file has a discernable mimetype, add that as a property to
the file. */
SVN_ERR (svn_io_detect_mimetype (&mimetype, path, pool));
if (mimetype)
SVN_ERR (editor->change_file_prop (file_baton, SVN_PROP_MIME_TYPE,
svn_string_create (mimetype, pool),
pool));
if (notify_func)
(*notify_func) (notify_baton,
path,
svn_wc_notify_commit_added,
svn_node_file,
mimetype,
svn_wc_notify_state_inapplicable,
svn_wc_notify_state_inapplicable,
SVN_INVALID_REVNUM);
/* Finally, add the file's path and baton to the FILES hash. */
value->subpool = subpool;
value->file_baton = file_baton;
apr_hash_set (files, filepath, APR_HASH_KEY_STRING, (void *)value);
return SVN_NO_ERROR;
}
/* Import directory PATH into the repository directory indicated by
* DIR_BATON in EDITOR. ROOT_PATH is the path imported as the root
* directory, so all edits are relative to that.
*
* Accumulate file paths and their batons in FILES, which must be
* non-null. (These are used to send postfix textdeltas later).
*
* If NOTIFY_FUNC is non-null, invoke it with NOTIFY_BATON for each
* directory.
*
* Use POOL for any temporary allocation. */
static svn_error_t *
import_dir (apr_hash_t *files,
svn_wc_notify_func_t notify_func,
void *notify_baton,
const svn_delta_editor_t *editor,
void *dir_baton,
const char *path,
const char *edit_path,
svn_boolean_t nonrecursive,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create (pool); /* iteration pool */
apr_dir_t *dir;
apr_finfo_t finfo;
apr_status_t apr_err;
apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
svn_error_t *err;
SVN_ERR (svn_io_dir_open (&dir, path, pool));
for (err = svn_io_dir_read (&finfo, flags, dir, subpool);
err == SVN_NO_ERROR;
svn_pool_clear (subpool),
err = svn_io_dir_read (&finfo, flags, dir, subpool))
{
const char *this_path, *this_edit_path;
if (finfo.filetype == APR_DIR)
{
/* Skip entries for this dir and its parent.
(APR promises that they'll come first, so technically
this guard could be moved outside the loop. But somehow
that feels iffy. */
if (finfo.name[0] == '.'
&& (finfo.name[1] == '\0'
|| (finfo.name[1] == '.' && finfo.name[2] == '\0')))
continue;
/* If someone's trying to import a directory named the same
as our administrative directories, that's probably not
what they wanted to do. Someday we can take an option to
make these subdirs be silently ignored, but for now,
seems safest to error. */
if (strcmp (finfo.name, SVN_WC_ADM_DIR_NAME) == 0)
return svn_error_createf
(SVN_ERR_CL_ADM_DIR_RESERVED, 0, NULL, subpool,
"cannot import directory named \"%s\" (in `%s')",
finfo.name, path);
}
/* Typically, we started importing from ".", in which case
edit_path is "". So below, this_path might become "./blah",
and this_edit_path might become "blah", for example. */
this_path = svn_path_join (path, finfo.name, subpool);
this_edit_path = svn_path_join (edit_path, finfo.name, subpool);
/* We only import subdirectories when we're doing a regular
recursive import. */
if ((finfo.filetype == APR_DIR) && (! nonrecursive))
{
void *this_dir_baton;
/* Add the new subdirectory, getting a descent baton from
the editor. */
SVN_ERR (editor->add_directory (this_edit_path, dir_baton,
NULL, SVN_INVALID_REVNUM, subpool,
&this_dir_baton));
/* By notifying before the recursive call below, we display
a directory add before displaying adds underneath the
directory. To do it the other way around, just move this
after the recursive call. */
if (notify_func)
(*notify_func) (notify_baton,
this_path,
svn_wc_notify_commit_added,
svn_node_dir,
NULL,
svn_wc_notify_state_inapplicable,
svn_wc_notify_state_inapplicable,
SVN_INVALID_REVNUM);
/* Recurse. */
SVN_ERR (import_dir (files,
notify_func, notify_baton,
editor, this_dir_baton,
this_path, this_edit_path,
FALSE, subpool));
/* Finally, close the sub-directory. */
SVN_ERR (editor->close_directory (this_dir_baton));
}
else if (finfo.filetype == APR_REG)
{
/* Import a file. */
SVN_ERR (import_file (files,
notify_func, notify_baton,
editor, dir_baton,
this_path, this_edit_path, subpool));
}
/* We're silently ignoring things that aren't files or
directories. If we stop doing that, here is the place to
change your world. */
}
/* Check that the loop exited cleanly. */
if (! (APR_STATUS_IS_ENOENT (err->apr_err)))
return svn_error_createf
(err->apr_err, err->src_err, err, subpool,
"error during import of `%s'", path);
/* Yes, it exited cleanly, so close the dir. */
else if ((apr_err = apr_dir_close (dir)))
return svn_error_createf
(apr_err, 0, NULL, subpool, "error closing dir `%s'", path);
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* Recursively import PATH to a repository using EDITOR and
* EDIT_BATON. PATH can be a file or directory.
*
* NEW_ENTRY is the name to use in the repository. If PATH is a
* directory, NEW_ENTRY may be null, which creates as many new entries
* in the top repository target directory as there are entries in the
* top of PATH; but if NEW_ENTRY is non-null, it is the name of a new
* subdirectory in the repository to hold the import. If PATH is a
* file, NEW_ENTRY may not be null.
*
* NEW_ENTRY can never be the empty string.
*
* If NOTIFY_FUNC is non-null, invoke it with NOTIFY_BATON for each
* imported path, passing the actions svn_wc_notify_commit_added or
* svn_wc_notify_commit_postfix_txdelta.
*
* Use POOL for any temporary allocation.
*
* Note: the repository directory receiving the import was specified
* when the editor was fetched. (I.e, when EDITOR->open_root() is
* called, it returns a directory baton for that directory, which is
* not necessarily the root.)
*/
static svn_error_t *
import (const char *path,
const char *new_entry,
svn_wc_notify_func_t notify_func,
void *notify_baton,
const svn_delta_editor_t *editor,
void *edit_baton,
svn_boolean_t nonrecursive,
apr_pool_t *pool)
{
void *root_baton;
enum svn_node_kind kind;
apr_hash_t *files = apr_hash_make (pool);
apr_hash_index_t *hi;
/* Get a root dir baton. We pass an invalid revnum to open_root
to mean "base this on the youngest revision". Should we have an
SVN_YOUNGEST_REVNUM defined for these purposes? */
SVN_ERR (editor->open_root (edit_baton, SVN_INVALID_REVNUM,
pool, &root_baton));
/* Import a file or a directory tree. */
SVN_ERR (svn_io_check_path (path, &kind, pool));
/* Note that there is no need to check whether PATH's basename is
the same name that we reserve for our admistritave
subdirectories. It would be strange, but not illegal to import
the contents of a directory of that name, because the directory's
own name is not part of those contents. Of course, if something
underneath it also has our reserved name, then we'll error. */
if (kind == svn_node_file)
{
if (! new_entry)
return svn_error_create
(SVN_ERR_UNKNOWN_NODE_KIND, 0, NULL, pool,
"new entry name required when importing a file");
SVN_ERR (import_file (files,
notify_func, notify_baton,
editor, root_baton,
path, new_entry, pool));
}
else if (kind == svn_node_dir)
{
void *new_dir_baton = NULL;
/* Grab a new baton, making two we'll have to close. */
if (new_entry)
SVN_ERR (editor->add_directory (new_entry, root_baton,
NULL, SVN_INVALID_REVNUM,
pool, &new_dir_baton));
#if 0 /* Temporarily blocked out for consideration, see below. */
/* If we activate this notification, then
*
* $ svn import url://blah/blah [PATH_TO_IMPORT] [NEW_ENTRY_IN_REPOS]
*
* will lead off with a notification for PATH_TO_IMPORT
* ("." by default). This is technically accurate -- after all,
* that dir is being imported -- but it's also kind of
* redundant. I'm not sure it really helps users to see it. In
* any case, the current test suite does not expect it. And see
*
* http://subversion.tigris.org/issues/show_bug.cgi?id=735
* http://subversion.tigris.org/issues/show_bug.cgi?id=736
*
* which are also about import notification paths.
*
* If we _are_ going to do this, perhaps the better way would be
* to have import_dir(foo) notify for foo, instead of only
* handling things underneath foo and requiring its caller
* (i.e., this code right here) to notify for foo itself.
*/
if (notify_func)
(*notify_func) (notify_baton,
path,
svn_wc_notify_commit_added,
svn_node_dir,
NULL,
svn_wc_notify_state_inapplicable,
svn_wc_notify_state_inapplicable,
SVN_INVALID_REVNUM);
#endif /* 0 */
SVN_ERR (import_dir
(files,
notify_func, notify_baton,
editor, new_dir_baton ? new_dir_baton : root_baton,
path, new_entry ? new_entry : "",
nonrecursive, pool));
/* Close one baton or two. */
if (new_dir_baton)
SVN_ERR (editor->close_directory (new_dir_baton));
}
else if (kind == svn_node_none)
{
return svn_error_createf
(SVN_ERR_UNKNOWN_NODE_KIND, 0, NULL, pool,
"'%s' does not exist.", path);
}
SVN_ERR (editor->close_directory (root_baton));
/* Do post-fix textdeltas here! */
for (hi = apr_hash_first (pool, files); hi; hi = apr_hash_next (hi))
{
const void *key;
void *val;
struct imported_file *value;
const char *full_path;
apr_hash_this (hi, &key, NULL, &val);
value = val;
full_path = key;
SVN_ERR (send_file_contents (full_path, value->file_baton,
editor, value->subpool));
/* ### full_path is wrong, should be remainder when path is
subtracted */
if (notify_func)
(*notify_func) (notify_baton,
full_path,
svn_wc_notify_commit_postfix_txdelta,
svn_node_file,
NULL,
svn_wc_notify_state_inapplicable,
svn_wc_notify_state_inapplicable,
SVN_INVALID_REVNUM);
SVN_ERR (editor->close_file (value->file_baton));
svn_pool_destroy (value->subpool);
}
SVN_ERR (editor->close_edit (edit_baton));
return SVN_NO_ERROR;
}
static svn_error_t *
get_xml_editor (apr_file_t **xml_hnd,
const svn_delta_editor_t **editor,
void **edit_baton,
const char *xml_file,
apr_pool_t *pool)
{
/* Open the xml file for writing. */
SVN_ERR (svn_io_file_open (xml_hnd, xml_file,
(APR_WRITE | APR_CREATE),
APR_OS_DEFAULT, pool));
/* ... we need an XML commit editor. */
return svn_delta_get_xml_editor (svn_stream_from_aprfile (*xml_hnd, pool),
editor, edit_baton, pool);
}
static svn_error_t *
get_ra_editor (void **ra_baton,
void **session,
svn_ra_plugin_t **ra_lib,
const svn_delta_editor_t **editor,
void **edit_baton,
svn_client_auth_baton_t *auth_baton,
const char *base_url,
const char *base_dir,
const char *log_msg,
apr_array_header_t *commit_items,
svn_revnum_t *committed_rev,
const char **committed_date,
const char **committed_author,
svn_boolean_t is_commit,
apr_pool_t *pool)
{
/* Get the RA vtable that matches URL. */
SVN_ERR (svn_ra_init_ra_libs (ra_baton, pool));
SVN_ERR (svn_ra_get_ra_library (ra_lib, *ra_baton,
base_url, pool));
/* Open an RA session to URL. */
SVN_ERR (svn_client__open_ra_session (session, *ra_lib,
base_url, base_dir,
commit_items, is_commit,
is_commit, !is_commit,
auth_baton, pool));
/* Fetch RA commit editor */
return (*ra_lib)->get_commit_editor (*session, editor, edit_baton,
committed_rev, committed_date,
committed_author, log_msg);
}
/*** Public Interfaces. ***/
svn_error_t *
svn_client_import (svn_client_commit_info_t **commit_info,
svn_wc_notify_func_t notify_func,
void *notify_baton,
svn_client_auth_baton_t *auth_baton,
const char *path,
const char *url,
const char *new_entry,
svn_client_get_commit_log_t log_msg_func,
void *log_msg_baton,
const char *xml_dst,
svn_revnum_t revision,
svn_boolean_t nonrecursive,
apr_pool_t *pool)
{
apr_status_t apr_err;
svn_error_t *err;
const char *log_msg;
const svn_delta_editor_t *editor;
void *edit_baton;
void *ra_baton, *session;
svn_ra_plugin_t *ra_lib;
svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
const char *committed_date = NULL;
const char *committed_author = NULL;
apr_file_t *xml_hnd;
/* Sanity check: NEW_ENTRY can be null or non-empty, but it can't be
empty. */
if (new_entry && (strcmp (new_entry, "") == 0))
return svn_error_create (SVN_ERR_FS_PATH_SYNTAX, 0, NULL, pool,
"empty string is an invalid entry name");
/* The repository doesn't know about the reserved. */
if (new_entry && strcmp (new_entry, SVN_WC_ADM_DIR_NAME) == 0)
return svn_error_createf
(SVN_ERR_CL_ADM_DIR_RESERVED, 0, NULL, pool,
"the name \"%s\" is reserved and cannot be imported",
SVN_WC_ADM_DIR_NAME);
/* Create a new commit item and add it to the array. */
if (log_msg_func)
{
svn_client_commit_item_t *item;
apr_array_header_t *commit_items
= apr_array_make (pool, 1, sizeof (item));
item = apr_pcalloc (pool, sizeof (*item));
item->path = apr_pstrdup (pool, path);
item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
(*((svn_client_commit_item_t **) apr_array_push (commit_items)))
= item;
SVN_ERR ((*log_msg_func) (&log_msg, commit_items, log_msg_baton, pool));
if (! log_msg)
return SVN_NO_ERROR;
}
else
log_msg = "";
/* If we're importing to XML ... */
if (xml_dst)
SVN_ERR (get_xml_editor (&xml_hnd, &editor, &edit_baton,
xml_dst, pool));
/* Else we're importing to an RA layer. */
else
SVN_ERR (get_ra_editor (&ra_baton, &session, &ra_lib,
&editor, &edit_baton, auth_baton, url, path,
log_msg, NULL, &committed_rev, &committed_date,
&committed_author, FALSE, pool));
/* If an error occured during the commit, abort the edit and return
the error. We don't even care if the abort itself fails. */
if ((err = import (path, new_entry,
notify_func, notify_baton,
editor, edit_baton, nonrecursive, pool)))
{
editor->abort_edit (edit_baton);
return err;
}
/* Finish the import. */
if (xml_dst)
{
/* If we were committing into XML, close the xml file. */
if ((apr_err = apr_file_close (xml_hnd)))
return svn_error_createf (apr_err, 0, NULL, pool,
"error closing %s", xml_dst);
/* Use REVISION for COMMITTED_REV. */
committed_rev = revision;
}
else
{
/* We were committing to RA, so close the session. */
SVN_ERR (ra_lib->close (session));
}
/* Finally, fill in the commit_info structure. */
*commit_info = svn_client__make_commit_info (committed_rev,
committed_author,
committed_date,
pool);
return SVN_NO_ERROR;
}
static svn_error_t *
unlock_dirs (apr_hash_t *locked_dirs,
apr_pool_t *pool)
{
apr_hash_index_t *hi;
/* Split if there's nothing to be done. */
if (! locked_dirs)
return SVN_NO_ERROR;
/* Clean up any locks. */
for (hi = apr_hash_first (pool, locked_dirs); hi; hi = apr_hash_next (hi))
{
const void *key;
void *val;
svn_wc_adm_access_t *adm_access;
apr_hash_this (hi, &key, NULL, &val);
adm_access = val;
SVN_ERR (svn_wc_adm_close (adm_access));
}
return SVN_NO_ERROR;
}
static svn_error_t *
remove_tmpfiles (apr_hash_t *tempfiles,
apr_pool_t *pool)
{
apr_hash_index_t *hi;
/* Split if there's nothing to be done. */
if (! tempfiles)
return SVN_NO_ERROR;
/* Clean up any tempfiles. */
for (hi = apr_hash_first (pool, tempfiles); hi; hi = apr_hash_next (hi))
{
const void *key;
void *val;
svn_node_kind_t kind;
apr_hash_this (hi, &key, NULL, &val);
SVN_ERR (svn_io_check_path ((const char *)key, &kind, pool));
if (kind == svn_node_file)
SVN_ERR (svn_io_remove_file ((const char *)key, pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
reconcile_errors (svn_error_t *commit_err,
svn_error_t *unlock_err,
svn_error_t *bump_err,
svn_error_t *cleanup_err,
apr_pool_t *pool)
{
svn_error_t *err;
/* Early release (for good behavior). */
if (! (commit_err || unlock_err || bump_err || cleanup_err))
return SVN_NO_ERROR;
/* If there was a commit error, start off our error chain with
that. */
if (commit_err)
{
commit_err = svn_error_quick_wrap
(commit_err, "Commit failed (details follow):");
err = commit_err;
}
/* Else, create a new "general" error that will head off the errors
that follow. */
else
err = svn_error_create (SVN_ERR_GENERAL, 0, NULL, pool,
"Commit succeeded, but other errors follow:");
/* If there was an unlock error... */
if (unlock_err)
{
/* Wrap the error with some headers. */
unlock_err = svn_error_quick_wrap
(unlock_err, "Error unlocking locked dirs (details follow):");
/* Append this error to the chain. */
svn_error_compose (err, unlock_err);
}
/* If there was a bumping error... */
if (bump_err)
{
/* Wrap the error with some headers. */
bump_err = svn_error_quick_wrap
(bump_err, "Error bumping revisions post-commit (details follow):");
/* Append this error to the chain. */
svn_error_compose (err, bump_err);
}
/* If there was a cleanup error... */
if (cleanup_err)
{
/* Wrap the error with some headers. */
cleanup_err = svn_error_quick_wrap
(cleanup_err, "Error in post-commit clean-up (details follow):");
/* Append this error to the chain. */
svn_error_compose (err, cleanup_err);
}
return err;
}
svn_error_t *
svn_client_commit (svn_client_commit_info_t **commit_info,
svn_wc_notify_func_t notify_func,
void *notify_baton,
svn_client_auth_baton_t *auth_baton,
const apr_array_header_t *targets,
svn_client_get_commit_log_t log_msg_func,
void *log_msg_baton,
const char *xml_dst,
svn_revnum_t revision,
svn_boolean_t nonrecursive,
apr_pool_t *pool)
{
const svn_delta_editor_t *editor;
void *edit_baton;
void *ra_baton, *session;
const char *log_msg;
svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
const char *committed_date = NULL;
const char *committed_author = NULL;
svn_ra_plugin_t *ra_lib;
const char *base_dir;
const char *base_url;
apr_array_header_t *rel_targets;
apr_hash_t *committables, *locked_dirs, *tempfiles = NULL;
apr_array_header_t *commit_items;
apr_status_t apr_err = 0;
apr_file_t *xml_hnd = NULL;
svn_error_t *cmt_err = NULL, *unlock_err = NULL;
svn_error_t *bump_err = NULL, *cleanup_err = NULL;
svn_boolean_t use_xml = (xml_dst && xml_dst[0]) ? TRUE : FALSE;
svn_boolean_t commit_in_progress = FALSE;
const char *display_dir = ".";
int notify_path_offset;
int i;
/* Condense the target list. */
SVN_ERR (svn_path_condense_targets (&base_dir, &rel_targets, targets, pool));
/* If we calculated only a base_dir and no relative targets, this
must mean that we are being asked to commit a single directory.
In order to do this properly, we need to anchor our commit up one
directory level, so long as our anchor is still a versioned
directory. */
if ((! rel_targets) || (! rel_targets->nelts))
{
const char *parent_dir, *name;
SVN_ERR (svn_wc_get_actual_target (base_dir, &parent_dir, &name, pool));
if (name)
{
/* Our new "grandfather directory" is the parent directory
of the former one. */
base_dir = apr_pstrdup (pool, parent_dir);
/* Make the array if it wasn't already created. */
if (! rel_targets)
rel_targets = apr_array_make (pool, targets->nelts, sizeof (name));
/* Now, push this name as a relative path to our new
base directory. */
(*((const char **)apr_array_push (rel_targets))) = name;
}
}
/* Crawl the working copy for commit items. */
if ((cmt_err = svn_client__harvest_committables (&committables,
&locked_dirs,
base_dir,
rel_targets,
nonrecursive,
pool)))
goto cleanup;
/* ### todo: Currently there should be only one hash entry, which
has a hacked name until we have the entries files storing
canonical repository URLs. Then, the hacked name can go away
and be replaced with a canonical repos URL, and from there we
are poised to started handling nested working copies. */
if (! ((commit_items = apr_hash_get (committables,
SVN_CLIENT__SINGLE_REPOS_NAME,
APR_HASH_KEY_STRING))))
goto cleanup;
/* Go get a log message. If an error occurs, or no log message is
specified, abort the operation. */
if (log_msg_func)
{
cmt_err = (*log_msg_func)(&log_msg, commit_items, log_msg_baton, pool);
if (cmt_err || (! log_msg))
goto cleanup;
}
else
log_msg = "";
/* Sort and condense our COMMIT_ITEMS. */
if ((cmt_err = svn_client__condense_commit_items (&base_url,
commit_items,
pool)))
goto cleanup;
/* If we're committing to XML ... */
if (use_xml)
{
if ((cmt_err = get_xml_editor (&xml_hnd, &editor, &edit_baton,
xml_dst, pool)))
goto cleanup;
/* Make a note that we have a commit-in-progress. */
commit_in_progress = TRUE;
}
/* Else we're commit to RA */
else
{
svn_revnum_t head = SVN_INVALID_REVNUM;
if ((cmt_err = get_ra_editor (&ra_baton, &session, &ra_lib,
&editor, &edit_baton, auth_baton,
base_url, base_dir, log_msg,
commit_items, &committed_rev,
&committed_date, &committed_author,
TRUE, pool)))
goto cleanup;
/* Make a note that we have a commit-in-progress. */
commit_in_progress = TRUE;
/* ### Temporary: If we have any non-added directories with
property mods, and we're not committing to an XML file, make
sure those directories are up-to-date. Someday this should
just be protected against by the server. */
for (i = 0; i < commit_items->nelts; i++)
{
svn_client_commit_item_t *item
= ((svn_client_commit_item_t **) commit_items->elts)[i];
if ((item->kind == svn_node_dir)
&& (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
&& (! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)))
{
if (! SVN_IS_VALID_REVNUM (head))
{
if ((cmt_err = ra_lib->get_latest_revnum (session, &head)))
goto cleanup;
}
if (item->revision != head)
{
cmt_err = svn_error_createf
(SVN_ERR_WC_NOT_UP_TO_DATE, 0, NULL, pool,
"Cannot commit propchanges for directory '%s'",
item->path);
goto cleanup;
}
}
}
}
/* Determine prefix to strip from the commit notify messages */
if ((cmt_err = svn_path_get_absolute (&display_dir,
display_dir, pool)))
goto cleanup;
display_dir = svn_path_get_longest_ancestor (display_dir, base_dir, pool);
notify_path_offset = display_dir ? (strlen (display_dir) + 1): 0;
/* Perform the commit. */
cmt_err = svn_client__do_commit (base_url, commit_items, editor, edit_baton,
notify_func, notify_baton,
notify_path_offset,
&tempfiles, pool);
/* Make a note that our commit is finished. */
commit_in_progress = FALSE;
/* Bump the revision if the commit went well. */
if (! cmt_err)
{
apr_pool_t *subpool = svn_pool_create (pool);
if (use_xml)
committed_rev = revision;
for (i = 0; i < commit_items->nelts; i++)
{
svn_client_commit_item_t *item
= ((svn_client_commit_item_t **) commit_items->elts)[i];
svn_boolean_t recurse = FALSE;
const char *adm_access_path;
svn_wc_adm_access_t *adm_access;
if (item->kind == svn_node_dir)
adm_access_path = item->path;
else
svn_path_split_nts (item->path, &adm_access_path, NULL, pool);
adm_access = apr_hash_get (locked_dirs, adm_access_path,
APR_HASH_KEY_STRING);
if (! adm_access)
/* This should *never* happen */
return svn_error_createf(SVN_ERR_GENERAL, 0, NULL, pool,
"BUG: no lock for %s",
item->path);
if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
&& (item->kind == svn_node_dir)
&& (item->copyfrom_url))
recurse = TRUE;
if ((bump_err = svn_wc_process_committed (item->path, adm_access,
recurse,
committed_rev,
committed_date,
committed_author,
subpool)))
break;
/* Clear the per-iteration subpool. */
svn_pool_clear (subpool);
}
/* Destroy the subpool (unless an error occurred, since we'll
need to keep the error around for a little while longer). */
if (! bump_err)
svn_pool_destroy (subpool);
}
/* Unlock the locked directories. */
if (! ((unlock_err = unlock_dirs (locked_dirs, pool))))
locked_dirs = NULL;
/* If we were committing into XML, close the xml file. */
if (use_xml)
{
if ((apr_err = apr_file_close (xml_hnd)))
{
cleanup_err = svn_error_createf (apr_err, 0, NULL, pool,
"error closing %s", xml_dst);
goto cleanup;
}
/* Use REVISION for COMMITTED_REV. */
committed_rev = revision;
}
else
{
/* We were committing to RA, so close the session. */
if ((cleanup_err = ra_lib->close (session)))
goto cleanup;
}
/* Sleep for one second to ensure timestamp integrity. */
apr_sleep (APR_USEC_PER_SEC * 1);
cleanup:
/* Abort the commit if it is still in progress. */
if (commit_in_progress)
editor->abort_edit (edit_baton); /* ignore return value */
/* Unlock any remaining locked dirs. */
if (locked_dirs)
unlock_err = unlock_dirs (locked_dirs, pool);
/* Remove any outstanding temporary text-base files. */
cleanup_err = remove_tmpfiles (tempfiles, pool);
/* Fill in the commit_info structure */
*commit_info = svn_client__make_commit_info (committed_rev,
committed_author,
committed_date, pool);
return reconcile_errors (cmt_err, unlock_err, bump_err, cleanup_err, pool);
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */