You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2010/09/06 22:02:24 UTC
svn commit: r993141 [8/25] - in /subversion/branches/performance: ./
build/ac-macros/ build/generator/ contrib/server-side/ notes/ notes/wc-ng/
subversion/bindings/javahl/native/
subversion/bindings/javahl/src/org/apache/subversion/javahl/ subversion/b...
Modified: subversion/branches/performance/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_client/merge.c?rev=993141&r1=993140&r2=993141&view=diff
==============================================================================
--- subversion/branches/performance/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/performance/subversion/libsvn_client/merge.c Mon Sep 6 20:02:15 2010
@@ -378,8 +378,9 @@ is_path_conflicted_by_merge(merge_cmd_ba
* different kind is expected, or if the disk node cannot be read.
* - Return 'missing' if there is no node on disk but one is expected.
* Also return 'missing' for absent nodes (not here due to authz).*/
-static svn_wc_notify_state_t
-obstructed_or_missing(const char *local_abspath,
+static svn_error_t *
+obstructed_or_missing(svn_wc_notify_state_t *obstr_state,
+ const char *local_abspath,
const char *local_dir_abspath,
const merge_cmd_baton_t *merge_b,
apr_pool_t *pool)
@@ -391,20 +392,14 @@ obstructed_or_missing(const char *local_
/* In a dry run, make as if nodes "deleted" by the dry run appear so. */
if (merge_b->dry_run && dry_run_deleted_p(merge_b, local_abspath))
- return svn_wc_notify_state_inapplicable;
-
- /* Since this function returns no svn_error_t, we make all errors look like
- * no node found in the wc. */
-
- err = svn_wc_read_kind(&kind_expected, merge_b->ctx->wc_ctx,
- local_abspath, FALSE, pool);
-
- if (err)
{
- svn_error_clear(err);
- kind_expected = svn_node_none;
+ *obstr_state = svn_wc_notify_state_inapplicable;
+ return SVN_NO_ERROR;
}
+ SVN_ERR(svn_wc_read_kind(&kind_expected, merge_b->ctx->wc_ctx,
+ local_abspath, FALSE, pool));
+
/* Report absent nodes as 'missing'. First of all, they count as 'hidden',
* and since we pass show_hidden == FALSE above, they will show up as
* 'none' here. */
@@ -413,44 +408,68 @@ obstructed_or_missing(const char *local_
svn_boolean_t is_absent;
err = svn_wc__node_is_status_absent(&is_absent, merge_b->ctx->wc_ctx,
local_abspath, pool);
- if (err)
- svn_error_clear(err);
- else if (is_absent)
- return svn_wc_notify_state_missing;
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_return(err);
+ else if (!err && is_absent)
+ {
+ *obstr_state = svn_wc_notify_state_missing;
+ return SVN_NO_ERROR;
+ }
+
+ svn_error_clear(err);
}
- /* If a node is deleted, we will still get its kind from the working copy.
+ SVN_ERR(svn_io_check_path(local_abspath, &kind_on_disk, pool));
+
+ /* If a node is deleted, the node might be gone in the working copy (and
+ * it probably is gone after we switch to single-db
+ *
* But to compare with disk state, we want to consider deleted nodes as
- * svn_node_none instead of their original kind.
- * ### Not necessary with central DB:
- * Because deleted directories are expected to remain on disk until commit
- * to keep the metadata available, we only do this for files, not dirs. */
- if (kind_expected == svn_node_file)
+ * svn_node_none instead of their original kind. */
+
+ if (kind_expected == svn_node_file
+ || kind_expected == svn_node_dir)
{
svn_boolean_t is_deleted;
- err = svn_wc__node_is_status_deleted(&is_deleted,
- merge_b->ctx->wc_ctx,
- local_abspath,
- pool);
- if (err)
- svn_error_clear(err);
- else if (is_deleted)
- kind_expected = svn_node_none;
- }
- err = svn_io_check_path(local_abspath, &kind_on_disk, pool);
- if (err)
- {
- svn_error_clear(err);
- kind_on_disk = svn_node_unknown;
+ SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted,
+ merge_b->ctx->wc_ctx,
+ local_abspath,
+ pool));
+ if (is_deleted)
+ {
+#ifndef SVN_WC__SINGLE_DB
+ /* ### While we are not at single-db: detect missing .svn dirs.
+ ### Once we switch to single db expected kind should be always
+ ### none, just like for files */
+ if (kind_expected == svn_node_dir)
+ {
+ if (kind_on_disk == svn_node_none)
+ {
+ svn_boolean_t is_obstructed;
+
+ SVN_ERR(svn_wc__node_is_status_obstructed(&is_obstructed,
+ merge_b->ctx->wc_ctx,
+ local_abspath,
+ pool));
+ if (!is_obstructed)
+ kind_expected = svn_node_none;
+ }
+ }
+ else
+#endif
+ kind_expected = svn_node_none;
+ }
}
if (kind_expected == kind_on_disk)
- return svn_wc_notify_state_inapplicable;
+ *obstr_state = svn_wc_notify_state_inapplicable;
else if (kind_on_disk == svn_node_none)
- return svn_wc_notify_state_missing;
+ *obstr_state = svn_wc_notify_state_missing;
else
- return svn_wc_notify_state_obstructed;
+ *obstr_state = svn_wc_notify_state_obstructed;
+
+ return SVN_NO_ERROR;
}
/* Create *LEFT and *RIGHT conflict versions for conflict victim
@@ -1083,8 +1102,9 @@ merge_props_changed(const char *local_di
{
svn_wc_notify_state_t obstr_state;
- obstr_state = obstructed_or_missing(local_abspath, local_dir_abspath,
- merge_b, subpool);
+ SVN_ERR(obstructed_or_missing(&obstr_state,
+ local_abspath, local_dir_abspath,
+ merge_b, subpool));
if (obstr_state != svn_wc_notify_state_inapplicable)
{
if (state)
@@ -1319,8 +1339,9 @@ merge_file_changed(const char *local_dir
{
svn_wc_notify_state_t obstr_state;
- obstr_state = obstructed_or_missing(mine_abspath, local_dir_abspath,
- merge_b, subpool);
+ SVN_ERR(obstructed_or_missing(&obstr_state,
+ mine_abspath, local_dir_abspath,
+ merge_b, subpool));
if (obstr_state != svn_wc_notify_state_inapplicable)
{
if (content_state)
@@ -1349,7 +1370,9 @@ merge_file_changed(const char *local_dir
SVN_ERR(svn_wc__node_get_depth(&parent_depth, merge_b->ctx->wc_ctx,
svn_dirent_dirname(mine_abspath, subpool),
subpool));
- if (wc_kind == svn_node_none && parent_depth < svn_depth_files)
+ if (wc_kind == svn_node_none
+ && parent_depth < svn_depth_files
+ && parent_depth != svn_depth_unknown)
{
if (content_state)
*content_state = svn_wc_notify_state_missing;
@@ -1635,8 +1658,9 @@ merge_file_added(const char *local_dir_a
{
svn_wc_notify_state_t obstr_state;
- obstr_state = obstructed_or_missing(mine_abspath, local_dir_abspath,
- merge_b, subpool);
+ SVN_ERR(obstructed_or_missing(&obstr_state,
+ mine_abspath, local_dir_abspath,
+ merge_b, subpool));
if (obstr_state != svn_wc_notify_state_inapplicable)
{
if (content_state)
@@ -1921,8 +1945,9 @@ merge_file_deleted(const char *local_dir
{
svn_wc_notify_state_t obstr_state;
- obstr_state = obstructed_or_missing(mine_abspath, local_dir_abspath,
- merge_b, subpool);
+ SVN_ERR(obstructed_or_missing(&obstr_state,
+ mine_abspath, local_dir_abspath,
+ merge_b, subpool));
if (obstr_state != svn_wc_notify_state_inapplicable)
{
if (state)
@@ -2109,8 +2134,9 @@ merge_dir_added(const char *local_dir_ab
{
svn_wc_notify_state_t obstr_state;
- obstr_state = obstructed_or_missing(local_abspath, local_dir_abspath,
- merge_b, subpool);
+ SVN_ERR(obstructed_or_missing(&obstr_state,
+ local_abspath, local_dir_abspath,
+ merge_b, subpool));
/* In this case of adding a directory, we have an exception to the usual
* "skip if it's inconsistent" rule. If the directory exists on disk
@@ -2138,7 +2164,7 @@ merge_dir_added(const char *local_dir_ab
merge_b->added_path = apr_pstrdup(merge_b->pool, local_abspath);
else
{
- SVN_ERR(svn_io_make_dir_recursively(local_abspath, subpool));
+ SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, subpool));
SVN_ERR(svn_wc_add4(merge_b->ctx->wc_ctx, local_abspath,
svn_depth_infinity,
copyfrom_url, copyfrom_rev,
@@ -2298,8 +2324,9 @@ merge_dir_deleted(const char *local_dir_
{
svn_wc_notify_state_t obstr_state;
- obstr_state = obstructed_or_missing(local_abspath, local_dir_abspath,
- merge_b, subpool);
+ SVN_ERR(obstructed_or_missing(&obstr_state,
+ local_abspath, local_dir_abspath,
+ merge_b, subpool));
if (obstr_state != svn_wc_notify_state_inapplicable)
{
if (state)
@@ -2430,8 +2457,9 @@ merge_dir_opened(const char *local_dir_a
}
/* Check for an obstructed or missing node on disk. */
- obstr_state = obstructed_or_missing(path, local_dir_abspath, merge_b,
- subpool);
+ SVN_ERR(obstructed_or_missing(&obstr_state,
+ path, local_dir_abspath,
+ merge_b, subpool));
if (obstr_state != svn_wc_notify_state_inapplicable)
{
if (skip_children)
@@ -3341,6 +3369,7 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
svn_revnum_t target_rev;
svn_opt_revision_t peg_revision;
apr_pool_t *sesspool = NULL;
+ svn_error_t *err;
/* Assert that we have sane input. */
SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start)
@@ -3348,9 +3377,26 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
&& (start > end));
peg_revision.kind = svn_opt_revision_working;
- SVN_ERR(svn_client__derive_location(&url, &target_rev, target_abspath,
- &peg_revision, ra_session,
- ctx, result_pool, scratch_pool));
+ err = svn_client__derive_location(&url, &target_rev, target_abspath,
+ &peg_revision, ra_session,
+ ctx, result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED)
+ {
+ /* We've been asked to operate on a target which has no location
+ * in the repository. Either it's unversioned (but attempts to
+ * merge into unversioned targets should not get as far as here),
+ * or it is locally added, in which case the target's implicit
+ * mergeinfo is empty. */
+ svn_error_clear(err);
+ *implicit_mergeinfo = apr_hash_make(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_return(err);
+ }
if (target_rev <= end)
{
@@ -3369,7 +3415,7 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
}
else
{
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, url,
NULL, NULL,
FALSE, TRUE,
ctx, scratch_pool));
@@ -5258,6 +5304,17 @@ struct get_mergeinfo_walk_baton
{
/* Array of paths that have explicit mergeinfo and/or are switched. */
apr_array_header_t *children_with_mergeinfo;
+
+ /* A hash of MERGE_TARGET_ABSPATH's subdirectories' dirents. Maps
+ const char * absolute working copy paths to dirent hashes as obtained
+ by svn_io_get_dirents3(). Contents are allocated in CB_POOL. */
+ apr_hash_t *subtree_dirents;
+
+ /* A hash to keep track of any subtrees in the merge target which are
+ unexpectedly missing from disk. Maps const char * absolute working
+ copy paths to the same. Contents are allocated in CB_POOL. */
+ apr_hash_t *missing_subtrees;
+
/* Merge source canonical path. */
const char* merge_src_canon_path;
@@ -5278,8 +5335,111 @@ struct get_mergeinfo_walk_baton
/* Pool from which to allocate new elements of CHILDREN_WITH_MERGEINFO. */
apr_pool_t *pool;
+
+ /* Pool with a lifetime guaranteed over all the get_mergeinfo_walk_cb
+ callbacks. */
+ apr_pool_t *cb_pool;
};
+/* Helper for the svn_wc__node_found_func_t callback get_mergeinfo_walk_cb().
+
+ Checks for issue #2915 subtrees, i.e. those that the WC thinks are on disk
+ but have been removed due to an OS-level deletion.
+
+ If the supposed working path LOCAL_ABSPATH, of kind KIND, is the root
+ of a missing subtree, then add a (const char *) WC absolute path to
+ (const char *) WC absolute path mapping to MISSING_SUBTREES, where the
+ paths are both a copy of LOCAL_ABSPATH, allocated in RESULT_POOL.
+
+ If LOCAL_ABSPATH is a directory and is not missing from disk, then add
+ a (const char *) WC absolute path to (svn_io_dirent2_t *) dirent mapping
+ to SUBTREE_DIRENTS, again allocated in RESULT_POOL (see
+ svn_io_get_dirents3).
+
+ SCRATCH_POOL is used for temporary allocations.
+
+ Note: Since this is effetively a svn_wc__node_found_func_t callback, it
+ must be called in depth-first order. */
+static svn_error_t *
+record_missing_subtree_roots(const char *local_abspath,
+ svn_node_kind_t kind,
+ apr_hash_t *subtree_dirents,
+ apr_hash_t *missing_subtrees,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t missing_subtree_root = FALSE;
+
+ /* Store the dirents for each directory in SUBTREE_DIRENTS. */
+ if (kind == svn_node_dir)
+ {
+ /* If SUBTREE_DIRENTS is empty LOCAL_ABSPATH is merge target. */
+ if (apr_hash_count(subtree_dirents) == 0
+ || apr_hash_get(subtree_dirents,
+ svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ APR_HASH_KEY_STRING))
+ {
+ apr_hash_t *dirents;
+ svn_error_t *err = svn_io_get_dirents3(&dirents, local_abspath,
+ TRUE, result_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ {
+ /* We can't get this directory's dirents, it's missing
+ from disk. */
+ svn_error_clear(err);
+ missing_subtree_root = TRUE;
+ }
+ else
+ {
+ return err;
+ }
+ }
+ else
+ {
+ apr_hash_set(subtree_dirents,
+ apr_pstrdup(result_pool, local_abspath),
+ APR_HASH_KEY_STRING, dirents);
+ }
+ }
+ }
+ else /* kind != svn_node_dir */
+ {
+ /* Is this non-directory missing from disk? Check LOCAL_ABSPATH's
+ parent's dirents. */
+ apr_hash_t *parent_dirents = apr_hash_get(subtree_dirents,
+ svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ APR_HASH_KEY_STRING);
+
+ /* If the parent_dirents is NULL, then LOCAL_ABSPATH is the
+ subtree of a missing subtree. Since we only report the roots
+ of missing subtrees there is nothing more to do in that case. */
+ if (parent_dirents)
+ {
+ svn_io_dirent2_t *dirent =
+ apr_hash_get(parent_dirents,
+ svn_dirent_basename(local_abspath, scratch_pool),
+ APR_HASH_KEY_STRING);
+ if (!dirent)
+ missing_subtree_root = TRUE;
+ }
+ }
+
+ if (missing_subtree_root)
+ {
+ const char *path = apr_pstrdup(result_pool, local_abspath);
+
+ apr_hash_set(missing_subtrees, path,
+ APR_HASH_KEY_STRING, path);
+ }
+
+ return SVN_NO_ERROR;
+}
/* svn_wc__node_found_func_t callback for get_mergeinfo_paths().
@@ -5307,7 +5467,9 @@ get_mergeinfo_walk_cb(const char *local_
svn_boolean_t is_present;
svn_boolean_t deleted;
svn_boolean_t absent;
+#ifndef SVN_WC__SINGLE_DB
svn_boolean_t obstructed;
+#endif
svn_boolean_t immediate_child_dir;
/* TODO(#2843) How to deal with a excluded item on merge? */
@@ -5319,14 +5481,20 @@ get_mergeinfo_walk_cb(const char *local_
if (!is_present)
return SVN_NO_ERROR;
+#ifndef SVN_WC__SINGLE_DB
SVN_ERR(svn_wc__node_is_status_obstructed(&obstructed, wb->ctx->wc_ctx,
local_abspath, scratch_pool));
+#endif
SVN_ERR(svn_wc__node_is_status_deleted(&deleted, wb->ctx->wc_ctx,
local_abspath, scratch_pool));
SVN_ERR(svn_wc__node_is_status_absent(&absent, wb->ctx->wc_ctx,
local_abspath, scratch_pool));
+#ifndef SVN_WC__SINGLE_DB
if (obstructed || deleted || absent)
+#else
+ if (deleted || absent)
+#endif
{
propval = NULL;
switched = FALSE;
@@ -5356,6 +5524,17 @@ get_mergeinfo_walk_cb(const char *local_
&&(kind == svn_node_dir)
&& (strcmp(abs_parent_path,
wb->merge_target_abspath) == 0));
+ /* Make sure what the WC thinks is present on disk really is. */
+#ifndef SVN_WC__SINGLE_DB
+ if (!absent && !deleted && !obstructed)
+#else
+ if (!absent && !deleted)
+#endif
+ SVN_ERR(record_missing_subtree_roots(local_abspath, kind,
+ wb->subtree_dirents,
+ wb->missing_subtrees,
+ wb->cb_pool,
+ scratch_pool));
/* Store PATHs with explict mergeinfo, which are switched, are missing
children due to a sparse checkout, are scheduled for deletion are absent
@@ -5574,7 +5753,7 @@ insert_parent_and_sibs_of_sw_absent_del_
/* Helper for do_directory_merge()
If HONOR_MERGEINFO is TRUE, then perform a depth first walk of the working
- copy tree rooted at MERGE_CMD_BATON->TARGET_ABSPATH.
+ copy tree rooted at MERGE_CMD_BATON->TARGET_ABSPATH to depth DEPTH.
Create an svn_client__merge_path_t * for any path which meets one or more
of the following criteria:
@@ -5602,6 +5781,9 @@ insert_parent_and_sibs_of_sw_absent_del_
If HONOR_MERGEINFO is FALSE, then create an svn_client__merge_path_t * only
for MERGE_CMD_BATON->TARGET_ABSPATH (i.e. only criteria 7 is applied).
+ If subtrees within the requested DEPTH are unexpectedly missing disk,
+ then raise SVN_ERR_CLIENT_NOT_READY_TO_MERGE.
+
Store the svn_client__merge_path_t *'s in *CHILDREN_WITH_MERGEINFO in
depth-first order based on the svn_client__merge_path_t *s path member as
sorted by svn_path_compare_paths(). Set the remaining_ranges field of each
@@ -5641,6 +5823,9 @@ get_mergeinfo_paths(apr_array_header_t *
struct get_mergeinfo_walk_baton wb = { 0 };
wb.children_with_mergeinfo = children_with_mergeinfo;
+ wb.cb_pool = svn_pool_create(scratch_pool);
+ wb.subtree_dirents = apr_hash_make(wb.cb_pool);
+ wb.missing_subtrees = apr_hash_make(wb.cb_pool);
wb.merge_src_canon_path = merge_src_canon_path;
wb.merge_target_abspath = merge_cmd_baton->target_abspath;
wb.source_root_url = source_root_url;
@@ -5664,6 +5849,35 @@ get_mergeinfo_paths(apr_array_header_t *
merge_cmd_baton->ctx->cancel_baton,
scratch_pool));
+ if (apr_hash_count(wb.missing_subtrees))
+ {
+ apr_hash_index_t *hi;
+ svn_stringbuf_t *missing_subtree_err_buf =
+ svn_stringbuf_create(_("Merge tracking not allowed with missing "
+ "subtrees; try restoring these items "
+ "first:\n"), scratch_pool);
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, wb.missing_subtrees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_pool_clear(iterpool);
+ svn_stringbuf_appendcstr(missing_subtree_err_buf,
+ svn_dirent_local_style(
+ svn__apr_hash_index_key(hi), iterpool));
+ svn_stringbuf_appendcstr(missing_subtree_err_buf, "\n");
+ }
+
+ return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE,
+ NULL, missing_subtree_err_buf->data);
+ }
+
+ /* This pool is only needed across all the callbacks to detect
+ missing subtrees. */
+ svn_pool_destroy(wb.cb_pool);
+
/* CHILDREN_WITH_MERGEINFO must be in depth first order, but the node
walk code returns nodes in a non particular order. Also, we may need
to add elements to the array to cover case 3) through 5) from the
@@ -7166,7 +7380,8 @@ record_mergeinfo_for_dir_merge(svn_merge
merge target, with no pre-existing explicit mergeinfo, during a --depth
immediates merge). Stash those that are inoperative at any depth in
INOPERATIVE_IMMEDIATE_CHILDREN. */
- if (!merge_b->record_only && range.start <= range.end)
+ if (!merge_b->record_only && range.start <= range.end
+ && depth == svn_depth_immediates)
SVN_ERR(get_inoperative_immediate_children(
&inoperative_immediate_children,
notify_b->children_with_mergeinfo,
@@ -8173,9 +8388,8 @@ ensure_ra_session_url(svn_ra_session_t *
if (! *ra_session || (err && err->apr_err == SVN_ERR_RA_ILLEGAL_URL))
{
svn_error_clear(err);
- err = svn_client__open_ra_session_internal(ra_session, url,
- NULL, NULL,
- FALSE, TRUE, ctx, pool);
+ err = svn_client__open_ra_session_internal(ra_session, NULL, url, NULL,
+ NULL, FALSE, TRUE, ctx, pool);
}
SVN_ERR(err);
@@ -8291,6 +8505,11 @@ do_merge(apr_hash_t **modified_subtrees,
SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, target_abspath, FALSE,
pool));
+ if (target_kind != svn_node_dir && target_kind != svn_node_file)
+ return svn_error_return(svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Merge target '%s' does not exist in the "
+ "working copy"), target_abspath));
/* Ensure a known depth. */
if (depth == svn_depth_unknown)
@@ -8668,12 +8887,26 @@ merge_locked(const char *source1,
apr_pool_t *sesspool;
svn_boolean_t same_repos;
const char *source_repos_uuid1, *source_repos_uuid2;
+ svn_node_kind_t target_kind;
+
+ /* Make sure the target is really there. */
+ SVN_ERR(svn_io_check_path(target_abspath, &target_kind, scratch_pool));
+ if (target_kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Path '%s' does not exist"),
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
- /* Sanity check our input -- we require specified revisions. */
+ /* Sanity check our input -- we require specified revisions,
+ * and either 2 paths or 2 URLs. */
if ((revision1->kind == svn_opt_revision_unspecified)
|| (revision2->kind == svn_opt_revision_unspecified))
return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
_("Not all required revisions are specified"));
+ if (svn_path_is_url(source1) != svn_path_is_url(source2))
+ return svn_error_return(svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Merge sources must both be "
+ "either paths or URLs")));
/* ### FIXME: This function really ought to do a history check on
the left and right sides of the merge source, and -- if one is an
@@ -8701,6 +8934,14 @@ merge_locked(const char *source1,
_("'%s' has no URL"),
svn_dirent_local_style(source2, scratch_pool));
+ SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, target_abspath, FALSE,
+ scratch_pool));
+ if (target_kind != svn_node_dir && target_kind != svn_node_file)
+ return svn_error_return(svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Merge target '%s' does not exist in the "
+ "working copy"), target_abspath));
+
/* Determine the working copy target's repository root URL. */
working_rev.kind = svn_opt_revision_working;
SVN_ERR(svn_client__get_repos_root(&wc_repos_root, target_abspath,
@@ -8709,12 +8950,12 @@ merge_locked(const char *source1,
/* Open some RA sessions to our merge source sides. */
sesspool = svn_pool_create(scratch_pool);
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session1,
- URL1, NULL, NULL,
- FALSE, TRUE, ctx, sesspool));
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session2,
- URL2, NULL, NULL,
- FALSE, TRUE, ctx, sesspool));
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session1, NULL, URL1,
+ NULL, NULL, FALSE, TRUE,
+ ctx, sesspool));
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session2, NULL, URL2,
+ NULL, NULL, FALSE, TRUE,
+ ctx, sesspool));
/* Resolve revisions to real numbers. */
SVN_ERR(svn_client__get_revision_number(&rev1, &youngest_rev, ctx->wc_ctx,
@@ -9931,6 +10172,15 @@ merge_reintegrate_locked(const char *sou
struct get_subtree_mergeinfo_walk_baton wb;
const char *target_url;
svn_revnum_t target_base_rev;
+ svn_node_kind_t kind;
+
+ /* Make sure the target is really there. */
+ SVN_ERR(svn_io_check_path(target_abspath, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Path '%s' does not exist"),
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
/* Make sure we're dealing with a real URL. */
SVN_ERR(svn_client_url_from_path2(&url2, source, ctx,
@@ -10001,13 +10251,13 @@ merge_reintegrate_locked(const char *sou
/* Open two RA sessions, one to our source and one to our target. */
SVN_ERR(svn_wc__node_get_url(&target_url, ctx->wc_ctx, target_abspath,
scratch_pool, scratch_pool));
- SVN_ERR(svn_client__open_ra_session_internal(&target_ra_session, target_url,
- NULL, NULL, FALSE, FALSE, ctx,
- scratch_pool));
- SVN_ERR(svn_client__open_ra_session_internal(&source_ra_session, url2,
- NULL, NULL,
- FALSE, FALSE, ctx,
- scratch_pool));
+ SVN_ERR(svn_client__open_ra_session_internal(&target_ra_session, NULL,
+ target_url,
+ NULL, NULL, FALSE, FALSE,
+ ctx, scratch_pool));
+ SVN_ERR(svn_client__open_ra_session_internal(&source_ra_session, NULL,
+ url2, NULL, NULL, FALSE, FALSE,
+ ctx, scratch_pool));
SVN_ERR(svn_client__get_revision_number(&rev2, NULL, ctx->wc_ctx,
"",
@@ -10191,8 +10441,17 @@ merge_peg_locked(const char *source,
svn_boolean_t use_sleep = FALSE;
svn_error_t *err;
svn_boolean_t same_repos;
+ svn_node_kind_t target_kind;
SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
+
+ /* Make sure the target is really there. */
+ SVN_ERR(svn_io_check_path(target_abspath, &target_kind, scratch_pool));
+ if (target_kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Path '%s' does not exist"),
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
/* Make sure we're dealing with a real URL. */
SVN_ERR(svn_client_url_from_path2(&URL, source, ctx,
@@ -10202,6 +10461,14 @@ merge_peg_locked(const char *source,
_("'%s' has no URL"),
svn_dirent_local_style(source, scratch_pool));
+ SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, target_abspath, FALSE,
+ scratch_pool));
+ if (target_kind != svn_node_dir && target_kind != svn_node_file)
+ return svn_error_return(svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Merge target '%s' does not exist in the "
+ "working copy"), target_abspath));
+
/* Determine the working copy target's repository root URL. */
working_rev.kind = svn_opt_revision_working;
SVN_ERR(svn_client__get_repos_root(&wc_repos_root, target_abspath,
@@ -10210,8 +10477,9 @@ merge_peg_locked(const char *source,
/* Open an RA session to our source URL, and determine its root URL. */
sesspool = svn_pool_create(scratch_pool);
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, NULL, NULL,
- FALSE, TRUE, ctx, sesspool));
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL,
+ NULL, FALSE, TRUE,
+ ctx, sesspool));
SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_repos_root, scratch_pool));
/* Normalize our merge sources. */
Modified: subversion/branches/performance/subversion/libsvn_client/mergeinfo.c
URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_client/mergeinfo.c?rev=993141&r1=993140&r2=993141&view=diff
==============================================================================
--- subversion/branches/performance/subversion/libsvn_client/mergeinfo.c (original)
+++ subversion/branches/performance/subversion/libsvn_client/mergeinfo.c Mon Sep 6 20:02:15 2010
@@ -616,11 +616,9 @@ svn_client__get_wc_or_repos_mergeinfo_ca
else
{
sesspool = svn_pool_create(scratch_pool);
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
- url, NULL, NULL,
- FALSE, TRUE,
- ctx,
- sesspool));
+ SVN_ERR(svn_client__open_ra_session_internal(
+ &ra_session, NULL, url, NULL, NULL, FALSE,
+ TRUE, ctx, sesspool));
}
SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
@@ -725,9 +723,9 @@ svn_client__get_history_as_mergeinfo(svn
if (session == NULL)
{
sesspool = svn_pool_create(pool);
- SVN_ERR(svn_client__open_ra_session_internal(&session, url, NULL, NULL,
- FALSE, TRUE, ctx,
- sesspool));
+ SVN_ERR(svn_client__open_ra_session_internal(&session, NULL, url, NULL,
+ NULL, FALSE, TRUE,
+ ctx, sesspool));
}
/* Fetch the location segments for our URL@PEG_REVNUM. */
@@ -1031,9 +1029,10 @@ get_mergeinfo(svn_mergeinfo_catalog_t *m
svn_mergeinfo_catalog_t tmp_catalog;
SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", scratch_pool));
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, path_or_url,
- NULL, NULL, FALSE,
- TRUE, ctx, scratch_pool));
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL,
+ path_or_url, NULL, NULL,
+ FALSE, TRUE, ctx,
+ scratch_pool));
SVN_ERR(svn_client__get_revision_number(&rev, NULL, ctx->wc_ctx,
local_abspath, ra_session,
&peg_rev, scratch_pool));
@@ -1081,9 +1080,9 @@ get_mergeinfo(svn_mergeinfo_catalog_t *m
svn_boolean_t indirect;
/* Check server Merge Tracking capability. */
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
- NULL, NULL, FALSE,
- TRUE, ctx, scratch_pool));
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, url,
+ NULL, NULL, FALSE, TRUE,
+ ctx, scratch_pool));
SVN_ERR(svn_ra__assert_mergeinfo_capable_server(ra_session, path_or_url,
scratch_pool));
Modified: subversion/branches/performance/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_client/patch.c?rev=993141&r1=993140&r2=993141&view=diff
==============================================================================
--- subversion/branches/performance/subversion/libsvn_client/patch.c (original)
+++ subversion/branches/performance/subversion/libsvn_client/patch.c Mon Sep 6 20:02:15 2010
@@ -48,7 +48,7 @@
typedef struct hunk_info_t {
/* The hunk. */
- const svn_hunk_t *hunk;
+ const svn_diff_hunk_t *hunk;
/* The line where the hunk matched in the target file. */
svn_linenum_t matched_line;
@@ -113,18 +113,26 @@ typedef struct target_content_info_t {
} target_content_info_t;
typedef struct prop_patch_target_t {
+
+ /* The name of the property */
const char *name;
+
+ /* All the information that is specific to the content of the property. */
target_content_info_t *content_info;
+
+ /* Path to the temporary file underlying the result stream. */
const char *patched_path;
+ /* Represents the operation performed on the property. It can be added,
+ * deleted or modified.
+ * ### Should we use flags instead since we're not using all enum values? */
+ svn_diff_operation_kind_t operation;
+
/* ### Here we'll add flags telling if the prop was added, deleted,
* ### had_rejects, had_local_mods prior to patching and so on. */
} prop_patch_target_t;
typedef struct patch_target_t {
- /* The patch being applied. */
- const svn_patch_t *patch;
-
/* The target path as it appeared in the patch file,
* but in canonicalised form. */
const char *canon_path_from_patchfile;
@@ -137,7 +145,7 @@ typedef struct patch_target_t {
/* The absolute path of the target on the filesystem.
* Any symlinks the path from the patch file may contain are resolved.
* Is not always known, so it may be NULL. */
- char *local_abspath;
+ const char *local_abspath;
/* The target file, read-only, seekable. This is NULL in case the target
* file did not exist prior to patch application. */
@@ -168,6 +176,9 @@ typedef struct patch_target_t {
/* True if at least one hunk was rejected. */
svn_boolean_t had_rejects;
+ /* True if at least one property hunk was rejected. */
+ svn_boolean_t had_prop_rejects;
+
/* True if the target file had local modifications before the
* patch was applied to it. */
svn_boolean_t local_mods;
@@ -186,7 +197,13 @@ typedef struct patch_target_t {
/* True if the target has the executable bit set. */
svn_boolean_t executable;
- /* All the information that is specifict to the content of the target. */
+ /* True if the patch changed the text of the target. */
+ svn_boolean_t has_text_changes;
+
+ /* True if the patch changed any of the properties of the target. */
+ svn_boolean_t has_prop_changes;
+
+ /* All the information that is specific to the content of the target. */
target_content_info_t *content_info;
/* A hash table of prop_patch_target_t objects keyed by property names. */
@@ -239,7 +256,8 @@ strip_path(const char **result, const ch
return SVN_NO_ERROR;
}
-/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH, from WC_CTX.
+/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
+ * WC_CTX is a context for the working copy the patch is applied to.
* Use RESULT_POOL for allocations of fields in TARGET.
* Use SCRATCH_POOL for all other allocations. */
static svn_error_t *
@@ -254,7 +272,6 @@ obtain_eol_and_keywords_for_file(apr_has
apr_hash_t *props;
svn_string_t *keywords_val, *eol_style_val;
- /* Handle svn:keyword and svn:eol-style properties. */
SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
scratch_pool, scratch_pool));
keywords_val = apr_hash_get(props, SVN_PROP_KEYWORDS,
@@ -305,6 +322,9 @@ obtain_eol_and_keywords_for_file(apr_has
* Indicate in TARGET->SKIPPED whether the target should be skipped.
* STRIP_COUNT specifies the number of leading path components
* which should be stripped from target paths in the patch.
+ * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
+ * only property changes, and no content changes (in which case the target
+ * must be a directory).
* Use RESULT_POOL for allocations of fields in TARGET.
* Use SCRATCH_POOL for all other allocations. */
static svn_error_t *
@@ -312,17 +332,24 @@ resolve_target_path(patch_target_t *targ
const char *path_from_patchfile,
const char *local_abspath,
int strip_count,
+ svn_boolean_t prop_changes_only,
svn_wc_context_t *wc_ctx,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *stripped_path;
+ const char *full_path;
svn_wc_status3_t *status;
svn_error_t *err;
+ svn_boolean_t under_root;
target->canon_path_from_patchfile = svn_dirent_internal_style(
path_from_patchfile, result_pool);
- if (target->canon_path_from_patchfile[0] == '\0')
+
+ /* We allow properties to be set on the wc root dir.
+ * ### Do we need to check for empty paths here, shouldn't the parser
+ * ### guarentee that the paths returned are non-empty? */
+ if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
{
/* An empty patch target path? What gives? Skip this. */
target->skipped = TRUE;
@@ -341,6 +368,7 @@ resolve_target_path(patch_target_t *targ
{
target->local_relpath = svn_dirent_is_child(local_abspath, stripped_path,
result_pool);
+
if (! target->local_relpath)
{
/* The target path is either outside of the working copy
@@ -358,8 +386,11 @@ resolve_target_path(patch_target_t *targ
/* Make sure the path is secure to use. We want the target to be inside
* of the working copy and not be fooled by symlinks it might contain. */
- if (! svn_dirent_is_under_root(&target->local_abspath, local_abspath,
- target->local_relpath, result_pool))
+ SVN_ERR(svn_dirent_is_under_root(&under_root,
+ &full_path, local_abspath,
+ target->local_relpath, result_pool));
+
+ if (! under_root)
{
/* The target path is outside of the working copy. Skip it. */
target->skipped = TRUE;
@@ -367,6 +398,9 @@ resolve_target_path(patch_target_t *targ
return SVN_NO_ERROR;
}
+ SVN_ERR(svn_dirent_get_absolute(&target->local_abspath, full_path,
+ result_pool));
+
/* Skip things we should not be messing with. */
err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
result_pool, scratch_pool);
@@ -403,7 +437,11 @@ resolve_target_path(patch_target_t *targ
}
SVN_ERR(svn_wc_read_kind(&target->db_kind, wc_ctx, target->local_abspath,
FALSE, scratch_pool));
- if (target->db_kind == svn_node_dir)
+
+ /* If the target is a versioned directory present on disk,
+ * and there are only property changes in the patch, we accept
+ * a directory target. Else, we skip directories. */
+ if (target->db_kind == svn_node_dir && ! prop_changes_only)
{
/* ### We cannot yet replace a locally deleted dir with a file,
* ### but some day we might want to allow it. */
@@ -429,6 +467,7 @@ resolve_target_path(patch_target_t *targ
static svn_error_t *
init_prop_target(prop_patch_target_t **prop_target,
const char *prop_name,
+ svn_diff_operation_kind_t operation,
svn_stream_t *reject,
svn_boolean_t remove_tempfiles,
svn_wc_context_t *wc_ctx,
@@ -439,7 +478,7 @@ init_prop_target(prop_patch_target_t **p
target_content_info_t *content_info;
const svn_string_t *value;
const char *patched_path;
-
+ svn_error_t *err;
content_info = apr_pcalloc(result_pool, sizeof(*content_info));
@@ -455,20 +494,27 @@ init_prop_target(prop_patch_target_t **p
new_prop_target = apr_palloc(result_pool, sizeof(*new_prop_target));
new_prop_target->name = apr_pstrdup(result_pool, prop_name);
+ new_prop_target->operation = operation;
new_prop_target->content_info = content_info;
- SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
- result_pool, scratch_pool));
-
- if (value)
+ err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
+ result_pool, scratch_pool);
+ if (err)
{
- /* We assume that a property is small enough to be kept in memory during
- * the patch process. */
- content_info->stream = svn_stream_from_string(value, result_pool);
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ value = NULL;
+ }
+ else
+ return svn_error_return(err);
}
+ if (value)
+ content_info->stream = svn_stream_from_string(value, result_pool);
+
/* Create a temporary file to write the patched result to. For properties,
- * we don't have to worrry about different eol-style. */
+ * we don't have to worry about eol-style. ### Why not? */
SVN_ERR(svn_stream_open_unique(&content_info->patched,
&patched_path, NULL,
remove_tempfiles ?
@@ -490,7 +536,8 @@ init_prop_target(prop_patch_target_t **p
* should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
* treated as not fully initialized, e.g. the caller should not not do any
* further operations on the target if it is marked to be skipped.
- * REMOVE_TEMPFILES as in svn_client_patch().
+ * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
+ * soon as they are no longer needed.
* Use SCRATCH_POOL for all other allocations. */
static svn_error_t *
init_patch_target(patch_target_t **patch_target,
@@ -502,6 +549,25 @@ init_patch_target(patch_target_t **patch
{
patch_target_t *target;
target_content_info_t *content_info;
+ svn_boolean_t has_prop_changes = FALSE;
+ svn_boolean_t prop_changes_only = FALSE;
+
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
+ if (! has_prop_changes)
+ has_prop_changes = prop_patch->hunks->nelts > 0;
+ else
+ break;
+ }
+ }
+
+ prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
content_info = apr_pcalloc(result_pool, sizeof(*content_info));
@@ -517,7 +583,6 @@ init_patch_target(patch_target_t **patch
target = apr_pcalloc(result_pool, sizeof(*target));
/* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
- target->patch = patch;
target->db_kind = svn_node_none;
target->kind_on_disk = svn_node_none;
target->content_info = content_info;
@@ -525,8 +590,8 @@ init_patch_target(patch_target_t **patch
target->pool = result_pool;
SVN_ERR(resolve_target_path(target, patch->new_filename,
- base_dir, strip_count, wc_ctx,
- result_pool, scratch_pool));
+ base_dir, strip_count, prop_changes_only,
+ wc_ctx, result_pool, scratch_pool));
if (! target->skipped)
{
const char *diff_header;
@@ -534,18 +599,17 @@ init_patch_target(patch_target_t **patch
apr_size_t len;
svn_stream_t *patched_raw;
+ /* Create a temporary file, and associated streams,
+ * to write the patched result to. */
if (target->kind_on_disk == svn_node_file)
{
- /* Open the file. */
SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
APR_READ | APR_BINARY | APR_BUFFERED,
APR_OS_DEFAULT, result_pool));
- /* Create a stream to read from the target. */
content_info->stream = svn_stream_from_aprfile2(target->file,
FALSE, result_pool);
- /* Also check the file for local mods and the Xbit. */
SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
target->local_abspath, FALSE,
scratch_pool));
@@ -562,7 +626,14 @@ init_patch_target(patch_target_t **patch
scratch_pool));
}
- /* Create a temporary file to write the patched result to. */
+ /* ### Is it ok to set target->added here? Isn't the target supposed to
+ * ### be marked as added after it's been proven that it can be added?
+ * ### One alternative is to include a git_added flag. Or maybe we
+ * ### should have kept the patch field in patch_target_t? Then we
+ * ### could have checked for target->patch->operation == added */
+ if (patch->operation == svn_diff_op_added)
+ target->added = TRUE;
+
SVN_ERR(svn_stream_open_unique(&patched_raw,
&target->patched_path, NULL,
remove_tempfiles ?
@@ -570,16 +641,15 @@ init_patch_target(patch_target_t **patch
svn_io_file_del_none,
result_pool, scratch_pool));
- /* Expand keywords in the patched file.
- * Repair newlines if svn:eol-style dictates a particular style. */
+ /* We always expand keywords in the patched file, but repair newlines
+ * only if svn:eol-style dictates a particular style. */
repair_eol = (content_info->eol_style == svn_subst_eol_style_fixed
|| content_info->eol_style == svn_subst_eol_style_native);
content_info->patched = svn_subst_stream_translated(
patched_raw, content_info->eol_str, repair_eol,
content_info->keywords, TRUE, result_pool);
- /* We'll also need a stream to write rejected hunks to.
- * We don't expand keywords, nor normalise line-endings,
+ /* We don't expand keywords, nor normalise line-endings,
* in reject files. */
SVN_ERR(svn_stream_open_unique(&content_info->reject,
&target->reject_path, NULL,
@@ -597,7 +667,7 @@ init_patch_target(patch_target_t **patch
len = strlen(diff_header);
SVN_ERR(svn_stream_write(content_info->reject, diff_header, &len));
- /* Time for the properties */
+ /* Handle properties. */
if (! target->skipped)
{
apr_hash_index_t *hi;
@@ -607,17 +677,17 @@ init_patch_target(patch_target_t **patch
hi = apr_hash_next(hi))
{
const char *prop_name = svn__apr_hash_index_key(hi);
+ svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
prop_patch_target_t *prop_target;
- /* Obtain info about this property */
SVN_ERR(init_prop_target(&prop_target,
prop_name,
+ prop_patch->operation,
content_info->reject,
remove_tempfiles,
wc_ctx, target->local_abspath,
result_pool, scratch_pool));
- /* Store the info */
apr_hash_set(target->prop_targets, prop_name,
APR_HASH_KEY_STRING, prop_target);
}
@@ -730,7 +800,7 @@ seek_to_line(target_content_info_t *cont
* Do temporary allocations in POOL. */
static svn_error_t *
match_hunk(svn_boolean_t *matched, target_content_info_t *content_info,
- const svn_hunk_t *hunk, int fuzz,
+ const svn_diff_hunk_t *hunk, int fuzz,
svn_boolean_t ignore_whitespace,
svn_boolean_t match_modified, apr_pool_t *pool)
{
@@ -859,7 +929,7 @@ match_hunk(svn_boolean_t *matched, targe
static svn_error_t *
scan_for_match(svn_linenum_t *matched_line,
target_content_info_t *content_info,
- const svn_hunk_t *hunk, svn_boolean_t match_first,
+ const svn_diff_hunk_t *hunk, svn_boolean_t match_first,
svn_linenum_t upper_line, int fuzz,
svn_boolean_t ignore_whitespace,
svn_boolean_t match_modified,
@@ -931,7 +1001,7 @@ scan_for_match(svn_linenum_t *matched_li
static svn_error_t *
match_existing_target(svn_boolean_t *match,
target_content_info_t *content_info,
- const svn_hunk_t *hunk,
+ const svn_diff_hunk_t *hunk,
svn_stream_t *stream,
apr_pool_t *scratch_pool)
{
@@ -987,14 +1057,16 @@ match_existing_target(svn_boolean_t *mat
* RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
* line can be determined, set HI->REJECTED to TRUE.
* IGNORE_WHITESPACE tells whether whitespace should be considered when
- * matching. When this function returns, neither CONTENT_INFO->CURRENT_LINE nor
+ * matching. IS_PROP_HUNK indicates whether the hunk patches file content
+ * or a property.
+ * When this function returns, neither CONTENT_INFO->CURRENT_LINE nor
* the file offset in the target file will have changed.
* Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
* Do temporary allocations in POOL. */
static svn_error_t *
get_hunk_info(hunk_info_t **hi, patch_target_t *target,
target_content_info_t *content_info,
- const svn_hunk_t *hunk, int fuzz,
+ const svn_diff_hunk_t *hunk, int fuzz,
svn_boolean_t ignore_whitespace,
svn_boolean_t is_prop_hunk,
svn_cancel_func_t cancel_func, void *cancel_baton,
@@ -1030,7 +1102,7 @@ get_hunk_info(hunk_info_t **hi, patch_ta
SVN_ERR(match_existing_target(&file_matches, content_info, hunk,
stream, scratch_pool));
- svn_stream_close(stream);
+ SVN_ERR(svn_stream_close(stream));
if (file_matches)
{
@@ -1225,19 +1297,40 @@ copy_lines_to_target(target_content_info
* Do temporary allocations in POOL. */
static svn_error_t *
reject_hunk(patch_target_t *target, target_content_info_t *content_info,
- hunk_info_t *hi, svn_boolean_t is_prop_hunk, apr_pool_t *pool)
+ hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
{
const char *hunk_header;
apr_size_t len;
svn_boolean_t eof;
apr_pool_t *iterpool;
- hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
- svn_diff_hunk_get_original_start(hi->hunk),
- svn_diff_hunk_get_original_length(hi->hunk),
- svn_diff_hunk_get_modified_start(hi->hunk),
- svn_diff_hunk_get_modified_length(hi->hunk),
- APR_EOL_STR);
+ if (prop_name)
+ {
+ const char *prop_header;
+
+ /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Propperty'.
+ */
+ prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
+ len = strlen(prop_header);
+
+ SVN_ERR(svn_stream_write(content_info->reject, prop_header, &len));
+
+ /* ### What about just setting a variable to either "@@" or "##",
+ * ### and merging with the else clause below? */
+ hunk_header = apr_psprintf(pool, "## -%lu,%lu +%lu,%lu ##%s",
+ svn_diff_hunk_get_original_start(hi->hunk),
+ svn_diff_hunk_get_original_length(hi->hunk),
+ svn_diff_hunk_get_modified_start(hi->hunk),
+ svn_diff_hunk_get_modified_length(hi->hunk),
+ APR_EOL_STR);
+ }
+ else
+ hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
+ svn_diff_hunk_get_original_start(hi->hunk),
+ svn_diff_hunk_get_original_length(hi->hunk),
+ svn_diff_hunk_get_modified_start(hi->hunk),
+ svn_diff_hunk_get_modified_length(hi->hunk),
+ APR_EOL_STR);
len = strlen(hunk_header);
SVN_ERR(svn_stream_write(content_info->reject, hunk_header, &len));
@@ -1265,19 +1358,22 @@ reject_hunk(patch_target_t *target, targ
while (! eof);
svn_pool_destroy(iterpool);
- /* ### had_rejects is used for notification. We haven't yet turned on
- * ### notification for properties. */
- if (! is_prop_hunk)
+ if (prop_name)
+ target->had_prop_rejects = TRUE;
+ else
target->had_rejects = TRUE;
return SVN_NO_ERROR;
}
-/* Write the modified text of hunk described by HI to the patched
- * stream of CONTENT_INFO. Do temporary allocations in POOL. */
+/* Write the modified text of the hunk described by HI to the patched
+ * stream of CONTENT_INFO. TARGET is the patch target.
+ * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
+ * a property with the given name.
+ * Do temporary allocations in POOL. */
static svn_error_t *
apply_hunk(patch_target_t *target, target_content_info_t *content_info,
- hunk_info_t *hi, svn_boolean_t is_prop_hunk, apr_pool_t *pool)
+ hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
{
svn_linenum_t lines_read;
svn_boolean_t eof;
@@ -1285,7 +1381,7 @@ apply_hunk(patch_target_t *target, targe
/* ### Is there a cleaner way to describe if we have an existing target?
*/
- if (target->kind_on_disk == svn_node_file || is_prop_hunk)
+ if (target->kind_on_disk == svn_node_file || prop_name)
{
svn_linenum_t line;
@@ -1305,7 +1401,7 @@ apply_hunk(patch_target_t *target, targe
{
/* Seek failed, reject this hunk. */
hi->rejected = TRUE;
- SVN_ERR(reject_hunk(target, content_info, hi, is_prop_hunk, pool));
+ SVN_ERR(reject_hunk(target, content_info, hi, prop_name, pool));
return SVN_NO_ERROR;
}
}
@@ -1350,6 +1446,53 @@ apply_hunk(patch_target_t *target, targe
while (! eof);
svn_pool_destroy(iterpool);
+ if (prop_name)
+ target->has_prop_changes = TRUE;
+ else
+ target->has_text_changes = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Use client context CTX to send a suitable notification for hunk HI,
+ * using TARGET to determine the path. If the hunk is a property hunk,
+ * PROP_NAME must be the name of the property, else NULL.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+send_hunk_notification(const hunk_info_t *hi,
+ const patch_target_t *target,
+ const char *prop_name,
+ const svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action;
+
+ if (hi->already_applied)
+ action = svn_wc_notify_patch_hunk_already_applied;
+ else if (hi->rejected)
+ action = svn_wc_notify_patch_rejected_hunk;
+ else
+ action = svn_wc_notify_patch_applied_hunk;
+
+ notify = svn_wc_create_notify(target->local_abspath
+ ? target->local_abspath
+ : target->local_relpath,
+ action, pool);
+ notify->hunk_original_start =
+ svn_diff_hunk_get_original_start(hi->hunk);
+ notify->hunk_original_length =
+ svn_diff_hunk_get_original_length(hi->hunk);
+ notify->hunk_modified_start =
+ svn_diff_hunk_get_modified_start(hi->hunk);
+ notify->hunk_modified_length =
+ svn_diff_hunk_get_modified_length(hi->hunk);
+ notify->hunk_matched_line = hi->matched_line;
+ notify->hunk_fuzz = hi->fuzz;
+ notify->prop_name = prop_name;
+
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+
return SVN_NO_ERROR;
}
@@ -1396,8 +1539,13 @@ send_patch_notification(const patch_targ
notify->content_state = svn_wc_notify_state_conflicted;
else if (target->local_mods)
notify->content_state = svn_wc_notify_state_merged;
- else
+ else if (target->has_text_changes)
notify->content_state = svn_wc_notify_state_changed;
+
+ if (target->had_prop_rejects)
+ notify->prop_state = svn_wc_notify_state_conflicted;
+ else if (target->has_prop_changes)
+ notify->prop_state = svn_wc_notify_state_changed;
}
(*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
@@ -1406,39 +1554,44 @@ send_patch_notification(const patch_targ
{
int i;
apr_pool_t *iterpool;
+ apr_hash_index_t *hash_index;
iterpool = svn_pool_create(pool);
for (i = 0; i < target->content_info->hunks->nelts; i++)
{
- hunk_info_t *hi;
+ const hunk_info_t *hi;
svn_pool_clear(iterpool);
hi = APR_ARRAY_IDX(target->content_info->hunks, i, hunk_info_t *);
- if (hi->already_applied)
- action = svn_wc_notify_patch_hunk_already_applied;
- else if (hi->rejected)
- action = svn_wc_notify_patch_rejected_hunk;
- else
- action = svn_wc_notify_patch_applied_hunk;
+ SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
+ ctx, iterpool));
+ }
+
+ for (hash_index = apr_hash_first(pool, target->prop_targets);
+ hash_index;
+ hash_index = apr_hash_next(hash_index))
+ {
+ prop_patch_target_t *prop_target;
+
+ prop_target = svn__apr_hash_index_val(hash_index);
+
+ for (i = 0; i < prop_target->content_info->hunks->nelts; i++)
+ {
+ const hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
- notify = svn_wc_create_notify(target->local_abspath
- ? target->local_abspath
- : target->local_relpath,
- action, pool);
- notify->hunk_original_start =
- svn_diff_hunk_get_original_start(hi->hunk);
- notify->hunk_original_length =
- svn_diff_hunk_get_original_length(hi->hunk);
- notify->hunk_modified_start =
- svn_diff_hunk_get_modified_start(hi->hunk);
- notify->hunk_modified_length =
- svn_diff_hunk_get_modified_length(hi->hunk);
- notify->hunk_matched_line = hi->matched_line;
- notify->hunk_fuzz = hi->fuzz;
+ hi = APR_ARRAY_IDX(prop_target->content_info->hunks, i,
+ hunk_info_t *);
- (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ /* Don't notify on the hunk level for added or deleted props. */
+ if (prop_target->operation != svn_diff_op_added &&
+ prop_target->operation != svn_diff_op_deleted)
+ SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
+ ctx, iterpool));
+ }
}
svn_pool_destroy(iterpool);
}
@@ -1500,7 +1653,7 @@ apply_one_patch(patch_target_t **patch_t
/* Match hunks. */
for (i = 0; i < patch->hunks->nelts; i++)
{
- svn_hunk_t *hunk;
+ svn_diff_hunk_t *hunk;
hunk_info_t *hi;
int fuzz = 0;
@@ -1509,7 +1662,7 @@ apply_one_patch(patch_target_t **patch_t
if (cancel_func)
SVN_ERR((cancel_func)(cancel_baton));
- hunk = APR_ARRAY_IDX(patch->hunks, i, svn_hunk_t *);
+ hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
/* Determine the line the hunk should be applied at.
* If no match is found initially, try with fuzz. */
@@ -1539,11 +1692,11 @@ apply_one_patch(patch_target_t **patch_t
continue;
else if (hi->rejected)
SVN_ERR(reject_hunk(target, target->content_info, hi,
- FALSE /* is_prop_hunk */,
+ NULL /* prop_name */,
iterpool));
else
SVN_ERR(apply_hunk(target, target->content_info, hi,
- FALSE /* is_prop_hunk */, iterpool));
+ NULL /* prop_name */, iterpool));
}
if (target->kind_on_disk == svn_node_file)
@@ -1560,7 +1713,7 @@ apply_one_patch(patch_target_t **patch_t
}
}
- /* Match property hunks. */
+ /* Match property hunks. ### Can we use scratch_pool here? */
for (hash_index = apr_hash_first(result_pool, patch->prop_patches);
hash_index;
hash_index = apr_hash_next(hash_index))
@@ -1570,19 +1723,18 @@ apply_one_patch(patch_target_t **patch_t
prop_patch_target_t *prop_target;
target_content_info_t *prop_content_info;
- /* Fetching the parsed info for one property */
prop_name = svn__apr_hash_index_key(hash_index);
prop_patch = svn__apr_hash_index_val(hash_index);
- /* Fetch the prop_content_info we'll use to store the matched hunks
- * in. */
+ /* We'll store matched hunks in prop_content_info.
+ * ### Just use prop_target->content_info? */
prop_target = apr_hash_get(target->prop_targets, prop_name,
APR_HASH_KEY_STRING);
prop_content_info = prop_target->content_info;
for (i = 0; i < prop_patch->hunks->nelts; i++)
{
- svn_hunk_t *hunk;
+ svn_diff_hunk_t *hunk;
hunk_info_t *hi;
int fuzz = 0;
@@ -1591,7 +1743,7 @@ apply_one_patch(patch_target_t **patch_t
if (cancel_func)
SVN_ERR((cancel_func)(cancel_baton));
- hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_hunk_t *);
+ hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
/* Determine the line the hunk should be applied at.
* If no match is found initially, try with fuzz. */
@@ -1620,7 +1772,9 @@ apply_one_patch(patch_target_t **patch_t
const char *prop_patched_path;
prop_target = svn__apr_hash_index_val(hash_index);
+ /* ### Just use prop_target->content_info? */
prop_content_info = prop_target->content_info;
+ /* ### Just use prop_target->patched_path? */
prop_patched_path = prop_target->patched_path;
for (i = 0; i < prop_content_info->hunks->nelts; i++)
@@ -1634,11 +1788,11 @@ apply_one_patch(patch_target_t **patch_t
continue;
else if (hi->rejected)
SVN_ERR(reject_hunk(target, prop_content_info, hi,
- TRUE /* is_prop_hunk */,
+ prop_target->name,
iterpool));
else
SVN_ERR(apply_hunk(target, prop_content_info, hi,
- TRUE /* is_prop_hunk */,
+ prop_target->name,
iterpool));
}
@@ -1656,7 +1810,10 @@ apply_one_patch(patch_target_t **patch_t
* ### dannas: Do we really want to skip an entire target
* ### if one of the properties does not apply cleanly,
* ### e.g. both text changes and all prop changes will not be
- * ### installed. */
+ * ### installed.
+ * ### stsp: This is a "should never happen" situation.
+ * ### It means we've run out of disk space or something
+ * ### like that, so skipping is appropriate. */
target->skipped = TRUE;
}
}
@@ -1664,6 +1821,8 @@ apply_one_patch(patch_target_t **patch_t
svn_pool_destroy(iterpool);
+ /* ### Move this a separate function? apply_one_patch() has gotten quite
+ * ### big. We should consider splitting it up into several pieces. */
{
apr_hash_index_t *hi;
target_content_info_t *prop_content_info;
@@ -1683,9 +1842,9 @@ apply_one_patch(patch_target_t **patch_t
* ### stream to read from. Find a better way to store info on
* ### the existence of the target prop. */
if (prop_content_info->stream)
- svn_stream_close(prop_content_info->stream);
+ SVN_ERR(svn_stream_close(prop_content_info->stream));
- svn_stream_close(prop_content_info->patched);
+ SVN_ERR(svn_stream_close(prop_content_info->patched));
}
@@ -1726,9 +1885,13 @@ apply_one_patch(patch_target_t **patch_t
else if (patched_file.size == 0 && working_file.size == 0)
{
/* The target was empty or non-existent to begin with
- * and nothing has changed by patching.
- * Report this as skipped if it didn't exist. */
- if (target->kind_on_disk == svn_node_none)
+ * and no content was changed by patching.
+ * Report this as skipped if it didn't exist, unless in the special
+ * case of adding an empty file which has properties set on it or
+ * adding an empty file with a 'git diff' */
+ if (target->kind_on_disk == svn_node_none
+ && ! target->has_prop_changes
+ && ! target->added)
target->skipped = TRUE;
}
else if (patched_file.size > 0 && working_file.size == 0)
@@ -1766,14 +1929,15 @@ create_missing_parents(patch_target_t *t
apr_pool_t *iterpool;
/* Check if we can safely create the target's parent. */
- local_abspath = apr_pstrdup(scratch_pool, abs_wc_path);
+ local_abspath = abs_wc_path;
components = svn_path_decompose(target->local_relpath, scratch_pool);
present_components = 0;
iterpool = svn_pool_create(scratch_pool);
for (i = 0; i < components->nelts - 1; i++)
{
const char *component;
- svn_node_kind_t kind;
+ svn_node_kind_t wc_kind, disk_kind;
+ svn_boolean_t is_deleted;
svn_pool_clear(iterpool);
@@ -1781,49 +1945,48 @@ create_missing_parents(patch_target_t *t
const char *);
local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
- SVN_ERR(svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, TRUE,
+ SVN_ERR(svn_wc_read_kind(&wc_kind, ctx->wc_ctx, local_abspath, TRUE,
iterpool));
- if (kind == svn_node_file)
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
+
+ if (wc_kind != svn_node_none)
+ SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted,
+ ctx->wc_ctx,
+ local_abspath,
+ iterpool));
+ else
+ is_deleted = FALSE;
+
+ if (disk_kind == svn_node_file
+ || (wc_kind == svn_node_file && !is_deleted))
{
- /* Obstructed. */
+ /* on-disk files and missing files are obstructions */
target->skipped = TRUE;
break;
}
- else if (kind == svn_node_dir)
+ else if (wc_kind == svn_node_dir)
{
- /* ### wc-ng should eventually be able to replace
- * directories in-place, so this schedule conflict
- * check will go away. We could then also make the
- * svn_wc_read_kind() call above ignore hidden
- * nodes.*/
- svn_boolean_t is_deleted;
-
- SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted,
- ctx->wc_ctx,
- local_abspath,
- iterpool));
if (is_deleted)
{
target->skipped = TRUE;
break;
}
+ /* continue one level deeper */
present_components++;
}
+ else if (disk_kind == svn_node_dir)
+ {
+ /* Obstructed. ### BH: why? We can just add a directory */
+ target->skipped = TRUE;
+ break;
+ }
else
{
- /* The WC_DB doesn't know much about this node.
- * Check what's on disk. */
- svn_node_kind_t disk_kind;
-
- SVN_ERR(svn_io_check_path(local_abspath, &disk_kind,
- iterpool));
- if (disk_kind != svn_node_none)
- {
- /* An unversioned item is in the way. */
- target->skipped = TRUE;
- break;
- }
+ /* It's not a file, it's not a dir...
+ Let's add a dir */
+ break;
}
}
@@ -1839,6 +2002,15 @@ create_missing_parents(patch_target_t *t
component, scratch_pool);
}
+ if (!dry_run && present_components < components->nelts - 1)
+ SVN_ERR(svn_io_make_dir_recursively(
+ svn_dirent_join(
+ abs_wc_path,
+ svn_relpath_dirname(target->local_relpath,
+ scratch_pool),
+ scratch_pool),
+ scratch_pool));
+
for (i = present_components; i < components->nelts - 1; i++)
{
const char *component;
@@ -1869,8 +2041,6 @@ create_missing_parents(patch_target_t *t
* to version control. Allow cancellation since we
* have not modified the working copy yet for this
* target. */
- SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT,
- iterpool));
SVN_ERR(svn_wc_add4(ctx->wc_ctx, local_abspath,
svn_depth_infinity,
NULL, SVN_INVALID_REVNUM,
@@ -1923,7 +2093,25 @@ install_patched_target(patch_target_t *t
svn_dirent_dirname(target->local_abspath,
pool),
FALSE, pool));
- if (parent_db_kind != svn_node_dir)
+
+ /* We don't allow targets to be added under dirs scheduled for
+ * deletion. */
+ if (parent_db_kind == svn_node_dir)
+ {
+ const char *parent_abspath;
+ svn_boolean_t is_deleted;
+
+ parent_abspath = svn_dirent_dirname(target->local_abspath,
+ pool);
+ SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx,
+ parent_abspath, pool));
+ if (is_deleted)
+ {
+ target->skipped = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+ else
SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
dry_run, pool));
}
@@ -1953,15 +2141,144 @@ install_patched_target(patch_target_t *t
}
}
- /* Write out rejected hunks, if any. */
- if (! dry_run && ! target->skipped && target->had_rejects)
+ return SVN_NO_ERROR;
+}
+
+/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
+ * TRUE, don't modify the working copy.
+ * Do temporary allocations in POOL.
+ */
+static svn_error_t *
+write_out_rejected_hunks(patch_target_t *target,
+ svn_boolean_t dry_run,
+ apr_pool_t *pool)
+{
+ if (! dry_run && (target->had_rejects || target->had_prop_rejects))
{
+ /* Write out rejected hunks, if any. */
SVN_ERR(svn_io_copy_file(target->reject_path,
apr_psprintf(pool, "%s.svnpatch.rej",
- target->local_abspath),
+ target->local_abspath),
FALSE, pool));
/* ### TODO mark file as conflicted. */
}
+ return SVN_NO_ERROR;
+}
+
+/* Install the patched properties for TARGET. Use client context CTX to
+ * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+install_patched_prop_targets(patch_target_t *target,
+ svn_client_ctx_t *ctx, svn_boolean_t dry_run,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ if (dry_run)
+ {
+ if (! target->has_text_changes && target->kind_on_disk == svn_node_none)
+ target->added = TRUE;
+
+ return SVN_NO_ERROR;
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, target->prop_targets);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi);
+ apr_file_t *file;
+ svn_stream_t *patched_stream;
+ svn_stringbuf_t *line;
+ svn_stringbuf_t *prop_content;
+ const char *eol_str;
+ svn_boolean_t eof;
+
+ svn_pool_clear(iterpool);
+
+ /* For a deleted prop we only set the value to NULL. */
+ if (prop_target->operation == svn_diff_op_deleted)
+ {
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+ prop_target->name, NULL,
+ TRUE /* skip_checks */,
+ NULL, NULL, /* suppress notification */
+ iterpool));
+ continue;
+ }
+
+ /* A property is usually small, at most a couple of bytes.
+ * Start out assuming it won't be larger than a typical line of text. */
+ prop_content = svn_stringbuf_create_ensure(80, scratch_pool);
+
+ /* svn_wc_prop_set4() wants a svn_string_t for input so we need to
+ * open the tmp file for reading again.
+ * ### Just keep it open? */
+ SVN_ERR(svn_io_file_open(&file, prop_target->patched_path,
+ APR_READ | APR_BINARY, APR_OS_DEFAULT,
+ scratch_pool));
+
+ patched_stream = svn_stream_from_aprfile2(file, FALSE /* disown */,
+ iterpool);
+ do
+ {
+ SVN_ERR(svn_stream_readline_detect_eol(patched_stream,
+ &line, &eol_str,
+ &eof,
+ iterpool));
+
+ svn_stringbuf_appendstr(prop_content, line);
+
+ if (eol_str)
+ svn_stringbuf_appendcstr(prop_content, eol_str);
+ }
+ while (! eof);
+
+ SVN_ERR(svn_stream_close(patched_stream));
+
+ /* If the patch target doesn't exist yet, the patch wants to add an
+ * empty file with properties set on it. So create an empty file and
+ * add it to version control. But if the patch was in the 'git format'
+ * then the file has already been added.
+ *
+ * ### How can we tell whether the patch really wanted to create
+ * ### an empty directory? */
+ if (! target->has_text_changes
+ && target->kind_on_disk == svn_node_none
+ && ! target->added)
+ {
+ SVN_ERR(svn_io_file_create(target->local_abspath, "", scratch_pool));
+ SVN_ERR(svn_wc_add4(ctx->wc_ctx, target->local_abspath,
+ svn_depth_infinity,
+ NULL, SVN_INVALID_REVNUM,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ NULL, NULL, /* suppress notification */
+ iterpool));
+ target->added = TRUE;
+ }
+
+ /* ### How should we handle SVN_ERR_ILLEGAL_TARGET and
+ * ### SVN_ERR_BAD_MIME_TYPE?
+ *
+ * ### stsp: I'd say reject the property hunk.
+ * ### We should verify all modified prop hunk texts using
+ * ### svn_wc_canonicalize_svn_prop() before starting the
+ * ### patching process, to reject them as early as possible. */
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+ prop_target->name,
+ svn_string_create_from_buf(prop_content,
+ iterpool),
+ TRUE /* skip_checks */,
+ NULL, NULL,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
@@ -2328,13 +2645,24 @@ apply_patches(void *baton,
patch_target_info_t *) = target_info;
if (! target->skipped)
- SVN_ERR(install_patched_target(target, btn->abs_wc_path,
- btn->ctx, btn->dry_run,
- iterpool));
+ {
+ if (target->has_text_changes || target->added)
+ SVN_ERR(install_patched_target(target, btn->abs_wc_path,
+ btn->ctx, btn->dry_run,
+ iterpool));
+
+ if (target->has_prop_changes)
+ SVN_ERR(install_patched_prop_targets(target, btn->ctx,
+ btn->dry_run,
+ iterpool));
+
+ SVN_ERR(write_out_rejected_hunks(target, btn->dry_run,
+ iterpool));
+ }
SVN_ERR(send_patch_notification(target, btn->ctx, iterpool));
}
- SVN_ERR(svn_diff_close_patch(patch));
+ SVN_ERR(svn_diff_close_patch(patch, iterpool));
}
}
while (patch);
Modified: subversion/branches/performance/subversion/libsvn_client/prop_commands.c
URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_client/prop_commands.c?rev=993141&r1=993140&r2=993141&view=diff
==============================================================================
--- subversion/branches/performance/subversion/libsvn_client/prop_commands.c (original)
+++ subversion/branches/performance/subversion/libsvn_client/prop_commands.c Mon Sep 6 20:02:15 2010
@@ -118,7 +118,8 @@ propset_walk_cb(const char *local_abspat
err = svn_wc_prop_set4(wb->wc_ctx, local_abspath, wb->propname, wb->propval,
wb->force, wb->notify_func, wb->notify_baton, pool);
- if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
+ if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET
+ || err->apr_err == SVN_ERR_WC_INVALID_SCHEDULE))
{
svn_error_clear(err);
err = SVN_NO_ERROR;
@@ -195,6 +196,8 @@ propset_on_url(const char *propname,
svn_boolean_t skip_checks,
svn_revnum_t base_revision_for_url,
const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
@@ -214,7 +217,7 @@ propset_on_url(const char *propname,
/* Open an RA session for the URL. Note that we don't have a local
directory, nor a place to put temp files. */
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, target,
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, target,
NULL, NULL, FALSE, TRUE,
ctx, pool));
@@ -269,8 +272,8 @@ propset_on_url(const char *propname,
/* Fetch RA commit editor. */
SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
commit_revprops,
- ctx->commit_callback2,
- ctx->commit_baton,
+ commit_callback,
+ commit_baton,
NULL, TRUE, /* No lock tokens */
pool));
@@ -345,6 +348,8 @@ svn_client_propset4(const char *propname
svn_revnum_t base_revision_for_url,
const apr_array_header_t *changelists,
const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
@@ -396,7 +401,8 @@ svn_client_propset4(const char *propname
"'%s' is not supported"), propname, target);
return propset_on_url(propname, propval, target, skip_checks,
- base_revision_for_url, revprop_table, ctx, pool);
+ base_revision_for_url, revprop_table,
+ commit_callback, commit_baton, ctx, pool);
}
else
{
@@ -471,7 +477,7 @@ svn_client_revprop_set2(const char *prop
/* Open an RA session for the URL. Note that we don't have a local
directory, nor a place to put temp files. */
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, NULL,
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL,
NULL, FALSE, TRUE, ctx, pool));
/* Resolve the revision into something real, and return that to the
@@ -963,7 +969,7 @@ svn_client_revprop_get(const char *propn
/* Open an RA session for the URL. Note that we don't have a local
directory, nor a place to put temp files. */
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, NULL,
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL,
NULL, FALSE, TRUE, ctx, pool));
/* Resolve the revision into something real, and return that to the
@@ -1307,7 +1313,7 @@ svn_client_revprop_list(apr_hash_t **pro
/* Open an RA session for the URL. Note that we don't have a local
directory, nor a place to put temp files. */
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, NULL,
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL,
NULL, FALSE, TRUE, ctx, pool));
/* Resolve the revision into something real, and return that to the