blob: 0223cceeacc891c9d9724fc28f0b211a7db04d30 [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"
/* Shared internals of import and commit. */
/* 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 (svn_stringbuf_t *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. */
apr_err = apr_file_open (&f, path->data, APR_READ, APR_OS_DEFAULT, pool);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf (apr_err, 0, NULL, pool,
"error opening `%s' for reading", path->data);
/* 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_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool, "error closing `%s'", path->data);
return SVN_NO_ERROR;
}
/* Import file PATH as EDIT_PATH in the repository directory indicated
* by DIR_BATON in EDITOR.
*
* Use POOL for any temporary allocation. */
static svn_error_t *
import_file (apr_hash_t *files,
const svn_delta_editor_t *editor,
void *dir_baton,
const svn_stringbuf_t *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);
svn_stringbuf_t *filepath = svn_stringbuf_dup (path, hash_pool);
/* Add the file, using the pool from the FILES hash. */
SVN_ERR (editor->add_file (edit_path, dir_baton, NULL, SVN_INVALID_REVNUM,
hash_pool, &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->data, pool));
if (mimetype)
SVN_ERR (editor->change_file_prop (file_baton, SVN_PROP_MIME_TYPE,
svn_string_create (mimetype, pool),
pool));
/* Finally, add the file's path and baton to the FILES hash. */
apr_hash_set (files, filepath->data, filepath->len, (void *)file_baton);
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.
*
* Use POOL for any temporary allocation. */
static svn_error_t *
import_dir (apr_hash_t *files,
const svn_delta_editor_t *editor,
void *dir_baton,
const svn_stringbuf_t *path,
const svn_stringbuf_t *edit_path,
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_stringbuf_t *this_path, *this_edit_path;
if ((apr_err = apr_dir_open (&dir, path->data, pool)))
return svn_error_createf (apr_err, 0, NULL, pool,
"unable to open directory %s", path->data);
this_path = svn_stringbuf_dup (path, pool);
this_edit_path = svn_stringbuf_dup (edit_path, pool);
for (apr_err = apr_dir_read (&finfo, flags, dir);
APR_STATUS_IS_SUCCESS (apr_err);
svn_pool_clear (subpool), apr_err = apr_dir_read (&finfo, flags, dir))
{
svn_stringbuf_t *name;
if (finfo.filetype == APR_DIR)
{
/* Skip entries for this dir and its parent.
### kff todo: APR actually promises that they'll come first,
so this guard could be moved outside the loop. */
if (! (strcmp (finfo.name, ".") && strcmp (finfo.name, "..")))
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->data);
}
/* Make a stringbuf version of the entry name, and append it as
a path component to THIS_PATH and THIS_EDIT_PATH. */
name = svn_stringbuf_create (finfo.name, subpool);
svn_path_add_component (this_path, name);
svn_path_add_component (this_edit_path, name);
if (finfo.filetype == APR_DIR)
{
void *this_dir_baton;
/* Add the new subdirectory, getting a descent baton from
the editor. */
SVN_ERR (editor->add_directory (this_edit_path->data, dir_baton,
NULL, SVN_INVALID_REVNUM, subpool,
&this_dir_baton));
/* Recurse. */
SVN_ERR (import_dir (files, editor, this_dir_baton,
this_path, this_edit_path, 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, editor, dir_baton,
this_path, this_edit_path->data, subpool));
}
else
{
/* It's not a file or dir, so we can't import it (yet).
No need to error, just ignore the thing. */
}
/* Hack THIS_PATH and THIS_EDIT_PATH back to their original sizes. */
svn_stringbuf_chop (this_path,
(path->len ? name->len + 1 : name->len));
svn_stringbuf_chop (this_edit_path,
(edit_path->len ? name->len + 1 : name->len));
}
/* Check that the loop exited cleanly. */
if (! (APR_STATUS_IS_ENOENT (apr_err)))
return svn_error_createf
(apr_err, 0, NULL, subpool, "error during import of `%s'", path->data);
/* 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->data);
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/*** Public interfaces. ***/
/* 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.
*
* 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 svn_stringbuf_t *path,
const svn_stringbuf_t *new_entry,
const svn_delta_editor_t *editor,
void *edit_baton,
apr_pool_t *pool)
{
void *root_baton;
enum svn_node_kind kind;
apr_hash_t *files = apr_hash_make (pool);
apr_pool_t *subpool = svn_pool_create (pool); /* for post-fix textdeltas */
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->data, &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, editor, root_baton,
path, new_entry->data, 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->data, root_baton,
NULL, SVN_INVALID_REVNUM,
pool, &new_dir_baton));
SVN_ERR (import_dir
(files, editor, new_dir_baton ? new_dir_baton : root_baton,
path, new_entry ? new_entry : svn_stringbuf_create ("", pool),
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->data);
}
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;
apr_ssize_t keylen;
void *file_baton;
svn_stringbuf_t *full_path;
apr_hash_this (hi, &key, &keylen, &file_baton);
full_path = svn_stringbuf_create ((char *) key, subpool);
SVN_ERR (send_file_contents (full_path, file_baton, editor, subpool));
SVN_ERR (editor->close_file (file_baton));
/* Clear our per-iteration subpool. */
svn_pool_clear (subpool);
}
/* Destroy the per-iteration subpool. */
svn_pool_destroy (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)
{
apr_status_t apr_err;
/* Open the xml file for writing. */
if ((apr_err = apr_file_open (xml_hnd, xml_file, (APR_WRITE | APR_CREATE),
APR_OS_DEFAULT, pool)))
return svn_error_createf (apr_err, 0, NULL, pool,
"error opening %s", xml_file);
/* ... 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,
svn_stringbuf_t *base_url,
svn_stringbuf_t *base_dir,
svn_stringbuf_t *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->data, 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, giving it svn_wc_process_committed(). */
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,
const svn_delta_editor_t *before_editor,
void *before_edit_baton,
const svn_delta_editor_t *after_editor,
void *after_edit_baton,
svn_client_auth_baton_t *auth_baton,
svn_stringbuf_t *path,
svn_stringbuf_t *url,
svn_stringbuf_t *new_entry,
svn_client_get_commit_log_t log_msg_func,
void *log_msg_baton,
svn_stringbuf_t *xml_dst,
svn_revnum_t revision,
apr_pool_t *pool)
{
apr_status_t apr_err;
svn_error_t *err;
svn_stringbuf_t *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;
svn_boolean_t use_xml = (xml_dst && xml_dst->data) ? TRUE : FALSE;
/* Sanity check: NEW_ENTRY can be null or non-empty, but it can't be
empty. */
if (new_entry && (strcmp (new_entry->data, "") == 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->data, 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 = svn_stringbuf_dup (path, pool);
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 = svn_stringbuf_create ("", pool);
/* If we're importing to XML ... */
if (use_xml)
SVN_ERR (get_xml_editor (&xml_hnd, &editor, &edit_baton,
xml_dst->data, 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));
/* Wrap the resulting editor with BEFORE and AFTER editors. */
svn_delta_wrap_editor (&editor, &edit_baton,
before_editor, before_edit_baton,
editor, edit_baton,
after_editor, after_edit_baton, 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, editor, edit_baton, pool)))
{
editor->abort_edit (edit_baton);
return err;
}
/* Finish the import. */
if (use_xml)
{
/* 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->data);
/* 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;
apr_ssize_t keylen;
void *val;
svn_stringbuf_t *strkey;
apr_hash_this (hi, &key, &keylen, &val);
strkey = svn_stringbuf_ncreate ((const char *)key, keylen, pool);
SVN_ERR (svn_wc_unlock (strkey, pool));
}
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;
apr_ssize_t keylen;
void *val;
svn_node_kind_t kind;
apr_hash_this (hi, &key, &keylen, &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,
const svn_delta_editor_t *before_editor,
void *before_edit_baton,
const svn_delta_editor_t *after_editor,
void *after_edit_baton,
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,
svn_stringbuf_t *xml_dst,
svn_revnum_t revision,
apr_pool_t *pool)
{
const svn_delta_editor_t *editor;
void *edit_baton;
void *ra_baton, *session;
svn_stringbuf_t *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;
svn_stringbuf_t *base_dir, *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->data) ? TRUE : FALSE;
svn_boolean_t commit_in_progress = FALSE;
svn_stringbuf_t *display_dir = svn_stringbuf_create (".", pool);
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))
{
svn_stringbuf_t *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. */
svn_stringbuf_set (base_dir, parent_dir->data);
/* 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. */
(*((svn_stringbuf_t **)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,
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 = svn_stringbuf_create ("", pool);
/* 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->data, 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->data);
goto cleanup;
}
}
}
}
/* Wrap the resulting editor with BEFORE and AFTER editors. */
svn_delta_wrap_editor (&editor, &edit_baton,
before_editor, before_edit_baton,
editor, edit_baton,
after_editor, after_edit_baton, pool);
/* Calculate a display_dir. */
if ((cmt_err = svn_path_get_absolute (&display_dir, display_dir, pool)))
goto cleanup;
/* Perform the commit. */
cmt_err = svn_client__do_commit (base_url, commit_items, editor, edit_baton,
notify_func, notify_baton, display_dir,
&tempfiles, pool);
/* Make a note that our commit is finished. */
commit_in_progress = FALSE;
/* Unlock the locked directories. */
if (! ((unlock_err = unlock_dirs (locked_dirs, pool))))
locked_dirs = NULL;
/* Bump the revision if the commit went well. */
if (! cmt_err)
{
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;
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, recurse,
committed_rev,
committed_date,
committed_author, pool)))
break;
}
}
/* 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->data);
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: */