[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

pin-externals branch: call for review

From: Stefan Sperling <stsp_at_elego.de>
Date: Sat, 24 Jan 2015 21:07:03 +0100

Hi,

I'm almost done with the pin-externals branch. At least I can't think of
much else that needs doing right now, apart from removing SVN_DBG() calls.

But I probably haven't thought of everything. There are likely some more
scenarios that could use testing. I'd be happy to hear your ideas.

The idea behind this branch is to implement a built-in replacement for
the svncopy.pl script from contrib. A copy with --pin-externals will
rewrite svn:externals properties during the copy operation to their
current last-changed revisions. This makes it easier to tag trees
containing svn:externals since otherwise "floating" externals can
potentially change the contents of a tag over time, if the content
is pulled in via svn:externals.

Using last-changed revisions instead of the current HEAD revision has
some advantages:
 - properly deals with copies made from revisions older than HEAD
 - the content of pinned svn:externals properties is relatively stable
   in the sense that not every commit will affect the corresponding
   'pinned' value of a particular externals definition

Already pinned externals are left untouched during copy, of course.

The full diff against trunk is below.

Brane mentioned that he would like to see some API changes made on this
branch, but I forgot the details. Brane, can you repeat these questions
please? Thanks.

No log message right now, sorry. I hope the above provides enough
context to help make sense of the diff.

Index: notes
===================================================================
--- notes (.../trunk) (revision 1654574)
+++ notes (.../branches/pin-externals) (working copy)

Property changes on: notes
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
   Merged /subversion/trunk/notes:r1643755-1653571
Index: subversion/libsvn_client/commit_util.c
===================================================================
--- subversion/libsvn_client/commit_util.c (.../trunk) (revision 1654574)
+++ subversion/libsvn_client/commit_util.c (.../branches/pin-externals) (working copy)
@@ -1754,12 +1754,15 @@ do_item_commit(void **dir_baton,
          repository, a "not found" error does not occur immediately
          upon opening the directory. It appears here during the delta
          transmisssion. */
- err = svn_wc_transmit_prop_deltas2(
- ctx->wc_ctx, local_abspath, editor,
- (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
+ if (local_abspath)
+ {
+ err = svn_wc_transmit_prop_deltas2(
+ ctx->wc_ctx, local_abspath, editor,
+ (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
 
- if (err)
- goto fixup_error;
+ if (err)
+ goto fixup_error;
+ }
 
       /* Make any additional client -> repository prop changes. */
       if (item->outgoing_prop_changes)
Index: subversion/libsvn_fs_x
===================================================================
--- subversion/libsvn_fs_x (.../trunk) (revision 1654574)
+++ subversion/libsvn_fs_x (.../branches/pin-externals) (working copy)

Property changes on: subversion/libsvn_fs_x
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
   Merged /subversion/trunk/subversion/libsvn_fs_x:r1643755-1653571
Index: subversion/tests/cmdline/externals_tests.py
===================================================================
--- subversion/tests/cmdline/externals_tests.py (.../trunk) (revision 1654574)
+++ subversion/tests/cmdline/externals_tests.py (.../branches/pin-externals) (working copy)
@@ -3547,6 +3547,186 @@ def replace_tree_with_foreign_external(sbox):
                                         None, None, None, None, None, 1,
                                         '-r', '2', wc_dir)
 
+def copy_pin_externals(sbox):
+ "test svn copy --pin-externals"
+
+ external_url_for = externals_test_setup(sbox)
+
+ wc_dir = sbox.wc_dir
+ repo_url = sbox.repo_url
+ other_repo_url = repo_url + ".other"
+
+ # Perform a repos->repos copy, pinning externals
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'copy',
+ repo_url + '/A',
+ repo_url + '/A_copy',
+ '-m', 'copy',
+ '--pin-externals')
+
+ # Verify that externals have been pinned.
+ last_changed_rev_gamma = 1
+ last_changed_rev_A = 5
+ A_copy_D_path = 'A_copy/D'
+ def verify_pinned_externals(base_path_or_url):
+ expected_output = [
+ '-r%d %s@%d gamma\n' % (last_changed_rev_gamma,
+ external_url_for["A/B/gamma"],
+ last_changed_rev_gamma),
+ '\n',
+ ]
+ if svntest.sandbox.is_url(base_path_or_url):
+ target = base_path_or_url + '/A_copy/B'
+ else:
+ target = sbox.ospath('A_copy/B')
+ svntest.actions.run_and_verify_svn(None, expected_output, [],
+ 'propget', 'svn:externals',
+ target)
+ expected_output = [
+ '-r3 %s_at_3 exdir_G\n' % external_url_for["A/C/exdir_G"],
+ # Note: A/D/H was last changed in r5, but exdir_H's external
+ # definition's URL is already pinned to r1.
+ '-r1 %s exdir_H\n' % external_url_for["A/C/exdir_H"],
+ '\n',
+ ]
+ if svntest.sandbox.is_url(base_path_or_url):
+ target = base_path_or_url + '/A_copy/C'
+ else:
+ target = sbox.ospath('A_copy/C')
+ svntest.actions.run_and_verify_svn(None, expected_output, [],
+ 'propget', 'svn:externals',
+ target)
+ expected_output = [
+ '-r%d %s@%d exdir_A\n' % (last_changed_rev_A,
+ external_url_for["A/D/exdir_A"],
+ last_changed_rev_A),
+ '-r3 %s_at_3 exdir_A/G\n' % external_url_for["A/D/exdir_A/G/"],
+ '-r1 %s_at_1 exdir_A/H\n' % external_url_for["A/D/exdir_A/H"],
+ '-r4 %s_at_4 x/y/z/blah\n' % external_url_for["A/D/x/y/z/blah"],
+ '\n',
+ ]
+ if svntest.sandbox.is_url(base_path_or_url):
+ target = base_path_or_url + '/' + A_copy_D_path
+ else:
+ target = sbox.ospath(A_copy_D_path)
+ svntest.actions.run_and_verify_svn(None, expected_output, [],
+ 'propget', 'svn:externals',
+ target)
+
+ verify_pinned_externals(repo_url)
+
+ # Clean up.
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'rm', repo_url + '/A_copy',
+ '-m', 'remove A_copy')
+
+ # Create a working copy.
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'checkout',
+ repo_url, wc_dir)
+
+ # Perform a repos->wc copy, pinning externals
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'copy',
+ repo_url + '/A',
+ wc_dir + '/A_copy',
+ '--pin-externals')
+ verify_pinned_externals(wc_dir)
+
+ # Clean up.
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'revert', '-R', wc_dir)
+ svntest.main.safe_rmtree(os.path.join(wc_dir, 'A_copy'))
+
+ # Perform a wc->repos copy, pinning externals
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'copy',
+ wc_dir + '/A',
+ repo_url + '/A_copy',
+ '-m', 'copy',
+ '--pin-externals')
+ verify_pinned_externals(repo_url)
+
+ # Clean up.
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'rm', repo_url + '/A_copy',
+ '-m', 'remove A_copy')
+
+ # Perform a wc->wc copy, pinning externals
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'copy',
+ wc_dir + '/A',
+ wc_dir + '/A_copy',
+ '--pin-externals')
+ verify_pinned_externals(wc_dir)
+
+ # Clean up.
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'revert', '-R', wc_dir)
+ svntest.main.safe_rmtree(os.path.join(wc_dir, 'A_copy'))
+
+ # Test behaviour for external URLs which were moved since
+ # their last-changed revision.
+ sbox.simple_move('A/D/gamma', 'A/D/gamma-moved')
+ sbox.simple_commit()
+ change_external(sbox.ospath('A/B'), '^/A/D/gamma-moved gamma', commit=True)
+ sbox.simple_update()
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'copy',
+ wc_dir + '/A',
+ wc_dir + '/A_copy',
+ '--pin-externals')
+ # gamma was moved so its path and expected last-changed revision change
+ last_changed_rev_gamma = 11
+ external_url_for["A/B/gamma"] = '^/A/D/gamma-moved'
+ verify_pinned_externals(wc_dir)
+
+ # Clean up.
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'revert', '-R', wc_dir)
+ svntest.main.safe_rmtree(os.path.join(wc_dir, 'A_copy'))
+
+ sbox.simple_update()
+ sbox.simple_move('A/D', 'A/D-moved')
+ change_external(sbox.ospath('A/B'), '^/A/D-moved/gamma-moved gamma', commit=False)
+ sbox.simple_commit()
+ sbox.simple_update()
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'copy',
+ wc_dir + '/A',
+ wc_dir + '/A_copy',
+ '--pin-externals')
+ # While gamma's path has changed by virtue of being moved along with
+ # its parent A/D, gamma's last-changed rev should not have changed.
+ # Therefore, the pinned external should resolve to the pre-move path,
+ # ie. '^/A/D/gamma-moved_at_11' instead of '^/A/D-moved/gamma-moved_at_11'.
+ #
+ # Note that the pinned external will use an absolute URLs in this case.
+ # See the "BUG:" comment in libsvn_client's pin_externals_prop() function.
+ external_url_for["A/B/gamma"] = ('%s/A/D/gamma-moved' % repo_url)
+ A_copy_D_path = 'A_copy/D-moved'
+ verify_pinned_externals(wc_dir)
+
+ # Clean up.
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'revert', '-R', wc_dir)
+ svntest.main.safe_rmtree(os.path.join(wc_dir, 'A_copy'))
+
+ # Test an already pinned external which was removed in HEAD.
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'rm',
+ other_repo_url + '/A/D/H',
+ '-m', 'remove A/D/H')
+ sbox.simple_update()
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'copy',
+ wc_dir + '/A',
+ wc_dir + '/A_copy',
+ '--pin-externals')
+ last_changed_rev_A = 6
+ verify_pinned_externals(wc_dir)
+
+
 ########################################################################
 # Run the tests
 
@@ -3607,6 +3787,7 @@ test_list = [ None,
               switch_relative_externals,
               copy_file_external_to_repo,
               replace_tree_with_foreign_external,
+ copy_pin_externals,
              ]
 
 if __name__ == '__main__':
Index: subversion/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h (.../trunk) (revision 1654574)
+++ subversion/include/svn_client.h (.../branches/pin-externals) (working copy)
@@ -4486,6 +4486,10 @@ typedef struct svn_client_copy_source_t
  * If @a ignore_externals is set, don't process externals definitions
  * as part of this operation.
  *
+ * If @a pin_externals is set, pin URLs in copied externals definitions
+ * to their last-changed revision unless they were already pinned to a
+ * particular revision.
+ *
  * If non-NULL, @a revprop_table is a hash table holding additional,
  * custom revision properties (<tt>const char *</tt> names mapped to
  * <tt>svn_string_t *</tt> values) to be set on the new revision in
@@ -4504,7 +4508,26 @@ typedef struct svn_client_copy_source_t
  * @a commit_callback with @a commit_baton and a #svn_commit_info_t for
  * the commit.
  *
+ * @since New in 1.9.
+ */
+svn_error_t *
+svn_client_copy7(const apr_array_header_t *sources,
+ const char *dst_path,
+ svn_boolean_t copy_as_child,
+ svn_boolean_t make_parents,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_copy7(), but cannot pin externals.
+ *
  * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
  */
 svn_error_t *
 svn_client_copy6(const apr_array_header_t *sources,
Index: subversion/libsvn_client/client.h
===================================================================
--- subversion/libsvn_client/client.h (.../trunk) (revision 1654574)
+++ subversion/libsvn_client/client.h (.../branches/pin-externals) (working copy)
@@ -1179,6 +1179,40 @@ svn_client__arbitrary_nodes_diff(const char **root
                                  apr_pool_t *scratch_pool);
 
 
+/* Helper for the remote case of svn_client_propget.
+ *
+ * If PROPS is not null, then get the value of property PROPNAME in REVNUM,
+ * using RA_LIB and SESSION. Store the value ('svn_string_t *') in PROPS,
+ * under the path key "TARGET_PREFIX/TARGET_RELATIVE" ('const char *').
+ *
+ * If INHERITED_PROPS is not null, then set *INHERITED_PROPS to a
+ * depth-first ordered array of svn_prop_inherited_item_t * structures
+ * representing the PROPNAME properties inherited by the target. If
+ * INHERITABLE_PROPS in not null and no inheritable properties are found,
+ * then set *INHERITED_PROPS to an empty array.
+ *
+ * Recurse according to DEPTH, similarly to svn_client_propget3().
+ *
+ * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
+ * Yes, caller passes this; it makes the recursion more efficient :-).
+ *
+ * Allocate PROPS and *INHERITED_PROPS in RESULT_POOL, but do all temporary
+ * work in SCRATCH_POOL. The two pools can be the same; recursive
+ * calls may use a different SCRATCH_POOL, however.
+ */
+svn_error_t *
+svn_client__remote_propget(apr_hash_t *props,
+ apr_array_header_t **inherited_props,
+ const char *propname,
+ const char *target_prefix,
+ const char *target_relative,
+ svn_node_kind_t kind,
+ svn_revnum_t revnum,
+ svn_ra_session_t *ra_session,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
Index: subversion/libsvn_client/prop_commands.c
===================================================================
--- subversion/libsvn_client/prop_commands.c (.../trunk) (revision 1654574)
+++ subversion/libsvn_client/prop_commands.c (.../branches/pin-externals) (working copy)
@@ -526,39 +526,18 @@ svn_client_revprop_set2(const char *propname,
   return SVN_NO_ERROR;
 }
 
-/* Helper for the remote case of svn_client_propget.
- *
- * If PROPS is not null, then get the value of property PROPNAME in REVNUM,
- using RA_LIB and SESSION. Store the value ('svn_string_t *') in PROPS,
- under the path key "TARGET_PREFIX/TARGET_RELATIVE" ('const char *').
- *
- * If INHERITED_PROPS is not null, then set *INHERITED_PROPS to a
- * depth-first ordered array of svn_prop_inherited_item_t * structures
- * representing the PROPNAME properties inherited by the target. If
- * INHERITABLE_PROPS in not null and no inheritable properties are found,
- * then set *INHERITED_PROPS to an empty array.
- *
- * Recurse according to DEPTH, similarly to svn_client_propget3().
- *
- * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
- * Yes, caller passes this; it makes the recursion more efficient :-).
- *
- * Allocate PROPS and *INHERITED_PROPS in RESULT_POOL, but do all temporary
- * work in SCRATCH_POOL. The two pools can be the same; recursive
- * calls may use a different SCRATCH_POOL, however.
- */
-static svn_error_t *
-remote_propget(apr_hash_t *props,
- apr_array_header_t **inherited_props,
- const char *propname,
- const char *target_prefix,
- const char *target_relative,
- svn_node_kind_t kind,
- svn_revnum_t revnum,
- svn_ra_session_t *ra_session,
- svn_depth_t depth,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+svn_error_t *
+svn_client__remote_propget(apr_hash_t *props,
+ apr_array_header_t **inherited_props,
+ const char *propname,
+ const char *target_prefix,
+ const char *target_relative,
+ svn_node_kind_t kind,
+ svn_revnum_t revnum,
+ svn_ra_session_t *ra_session,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
 {
   apr_hash_t *dirents;
   apr_hash_t *prop_hash = NULL;
@@ -670,15 +649,15 @@ svn_client_revprop_set2(const char *propname,
           new_target_relative = svn_relpath_join(target_relative, this_name,
                                                  iterpool);
 
- SVN_ERR(remote_propget(props, NULL,
- propname,
- target_prefix,
- new_target_relative,
- this_ent->kind,
- revnum,
- ra_session,
- depth_below_here,
- result_pool, iterpool));
+ SVN_ERR(svn_client__remote_propget(props, NULL,
+ propname,
+ target_prefix,
+ new_target_relative,
+ this_ent->kind,
+ revnum,
+ ra_session,
+ depth_below_here,
+ result_pool, iterpool));
         }
 
       svn_pool_destroy(iterpool);
@@ -968,7 +947,8 @@ svn_client_propget5(apr_hash_t **props,
           if (!local_explicit_props)
             *props = apr_hash_make(result_pool);
 
- SVN_ERR(remote_propget(!local_explicit_props ? *props : NULL,
+ SVN_ERR(svn_client__remote_propget(
+ !local_explicit_props ? *props : NULL,
                                  !local_iprops ? inherited_props : NULL,
                                  propname, loc->url, "",
                                  kind, loc->rev, ra_session,
Index: subversion/libsvn_client/copy.c
===================================================================
--- subversion/libsvn_client/copy.c (.../trunk) (revision 1654574)
+++ subversion/libsvn_client/copy.c (.../branches/pin-externals) (working copy)
@@ -177,12 +177,312 @@ get_copy_pair_ancestors(const apr_array_header_t *
   return SVN_NO_ERROR;
 }
 
+struct external_location_segments_receiver_baton {
+ svn_revnum_t last_changed_rev;
+ const char *last_changed_repos_relpath;
+ apr_pool_t *result_pool;
+} external_location_segments_receiver_baton;
 
+/* Implements svn_location_segment_receiver_t. */
+static svn_error_t *
+external_location_segments_receiver(svn_location_segment_t *segment,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct external_location_segments_receiver_baton *b = baton;
+
+ if (segment->range_start <= b->last_changed_rev &&
+ segment->range_end >= b->last_changed_rev)
+ b->last_changed_repos_relpath = apr_pstrdup(b->result_pool, segment->path);
+
+ return SVN_NO_ERROR;
+}
+
+/* Pin all externals listed in EXTERNALS_PROP_VAL to their last-changed
+ * revision. Return a new property value in *PINNED_EXTERNALS allocated
+ * in RESULT_POOL. LOCAL_ABSPATH_OR_URL is the path defining the
+ * svn:externals property. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+pin_externals_prop(svn_string_t **pinned_externals,
+ svn_string_t *externals_prop_val,
+ const char *repos_root_url,
+ const char *local_abspath_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *buf;
+ apr_array_header_t *external_items;
+ const char *session_url;
+ int i;
+ apr_pool_t *iterpool;
+
+ SVN_DBG(("repos_root_url: %s", repos_root_url));
+ SVN_DBG(("local_abspath_or_url: %s", local_abspath_or_url));
+ SVN_DBG(("externals_prop_val: %s", externals_prop_val->data));
+
+ SVN_ERR(svn_wc_parse_externals_description3(&external_items,
+ local_abspath_or_url,
+ externals_prop_val->data,
+ FALSE /* canonicalize_url */,
+ scratch_pool));
+
+ buf = svn_stringbuf_create_empty(scratch_pool);
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < external_items->nelts; i++)
+ {
+ svn_wc_external_item2_t *item;
+ const char *pinned_desc;
+ const char *resolved_url;
+ const char *defining_url;
+ svn_ra_session_t *external_ra_session;
+ svn_revnum_t external_youngest_rev;
+ svn_dirent_t *dirent;
+
+ svn_pool_clear(iterpool);
+
+ if (!svn_path_is_url(local_abspath_or_url))
+ SVN_ERR(svn_wc__node_get_url(&defining_url, ctx->wc_ctx,
+ local_abspath_or_url,
+ iterpool, iterpool));
+ else
+ defining_url = local_abspath_or_url;
+
+ item = APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *);
+ SVN_ERR(svn_wc__resolve_relative_external_url(&resolved_url, item,
+ repos_root_url,
+ defining_url,
+ iterpool,
+ iterpool));
+ SVN_DBG(("resolved external url: %s", resolved_url));
+
+ SVN_ERR(svn_client__open_ra_session_internal(&external_ra_session,
+ NULL, resolved_url,
+ NULL, NULL, FALSE, FALSE,
+ ctx, iterpool,
+ iterpool));
+ SVN_ERR(svn_ra_get_session_url(external_ra_session, &session_url, scratch_pool));
+ SVN_DBG(("external ra session url: %s", session_url));
+ if (item->peg_revision.kind == svn_opt_revision_unspecified ||
+ item->peg_revision.kind == svn_opt_revision_head)
+ {
+ SVN_ERR(svn_ra_get_latest_revnum(external_ra_session,
+ &external_youngest_rev,
+ iterpool));
+ }
+ else
+ {
+ if (item->peg_revision.kind == svn_opt_revision_date)
+ {
+ item->peg_revision.kind = svn_opt_revision_number;
+ SVN_ERR(svn_ra_get_dated_revision(external_ra_session,
+ &item->peg_revision.value.number,
+ item->peg_revision.value.date,
+ iterpool));
+ }
+
+ SVN_ERR_ASSERT(item->peg_revision.kind == svn_opt_revision_number);
+ external_youngest_rev = item->peg_revision.value.number;
+ }
+
+ SVN_ERR(svn_ra_stat(external_ra_session, "",
+ external_youngest_rev,
+ &dirent,
+ iterpool));
+ if (dirent == NULL)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Cannot determine last-changed revision "
+ "of '%s'"), resolved_url);
+ SVN_DBG(("external's last-changed revision: %lu", dirent->created_rev));
+
+ if (external_youngest_rev > dirent->created_rev)
+ {
+ struct external_location_segments_receiver_baton b;
+
+ /* Find the item's URL location as of the last-changed revision. */
+ b.last_changed_rev = dirent->created_rev;
+ b.last_changed_repos_relpath = NULL;
+ b.result_pool = iterpool;
+ SVN_ERR(svn_ra_get_location_segments(
+ external_ra_session, "", external_youngest_rev,
+ external_youngest_rev, dirent->created_rev,
+ external_location_segments_receiver, &b, iterpool));
+ if (b.last_changed_repos_relpath)
+ {
+ const char *external_repos_root_url;
+ const char *last_changed_url;
+
+ SVN_ERR(svn_ra_get_repos_root2(external_ra_session,
+ &external_repos_root_url,
+ iterpool));
+
+
+ last_changed_url = svn_uri_canonicalize(
+ apr_pstrcat(iterpool,
+ external_repos_root_url, "/",
+ b.last_changed_repos_relpath,
+ SVN_VA_NULL),
+ iterpool);
+ SVN_DBG(("external's last-changed URL: %s", last_changed_url));
+ if (strcmp(resolved_url, last_changed_url) != 0)
+ {
+ /* The external was at a different location at its
+ * last-changed revision so we must fix up ITEM->URL.
+ * ### BUG: Even if the external's URL was relative the
+ * ### pinned URL will be absolute because we have no good
+ * ### way of transforming the resolved last-changed URL
+ * ### into a relative one. */
+ item->url = last_changed_url;
+ }
+ }
+ }
+
+ SVN_DBG(("external's URL: %s", item->url));
+
+ if (item->revision.kind == svn_opt_revision_date)
+ {
+ item->revision.kind = svn_opt_revision_number;
+ SVN_ERR(svn_ra_get_dated_revision(external_ra_session,
+ &item->revision.value.number,
+ item->revision.value.date,
+ iterpool));
+ }
+
+ if (item->revision.kind != svn_opt_revision_number)
+ {
+ item->revision.kind = svn_opt_revision_number;
+ item->revision.value.number = dirent->created_rev;
+ }
+
+ if (item->peg_revision.kind != svn_opt_revision_number)
+ {
+ item->peg_revision.kind = svn_opt_revision_number;
+ item->peg_revision.value.number = dirent->created_rev;
+ }
+
+ SVN_ERR_ASSERT(item->revision.kind == svn_opt_revision_number);
+ SVN_ERR_ASSERT(item->peg_revision.kind == svn_opt_revision_number);
+
+ pinned_desc = apr_psprintf(iterpool, "-r%lu %s@%lu %s%s",
+ item->revision.value.number,
+ item->url,
+ item->peg_revision.value.number,
+ item->target_dir,
+ APR_EOL_STR);
+ SVN_DBG(("pinned external: %s", pinned_desc));
+ svn_stringbuf_appendcstr(buf, pinned_desc);
+ }
+ svn_pool_destroy(iterpool);
+
+ *pinned_externals = svn_string_create_from_buf(buf, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+resolve_pinned_externals(apr_hash_t **new_externals,
+ svn_client__copy_pair_t *pair,
+ svn_ra_session_t *ra_session,
+ const char *repos_root_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *old_url = NULL;
+ apr_hash_t *externals_props;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ if (svn_path_is_url(pair->src_abspath_or_url))
+ {
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
+ pair->src_abspath_or_url,
+ scratch_pool));
+ SVN_DBG(("old_url: %s", old_url));
+ externals_props = apr_hash_make(scratch_pool);
+ SVN_ERR(svn_client__remote_propget(externals_props, NULL,
+ SVN_PROP_EXTERNALS,
+ pair->src_abspath_or_url, "",
+ svn_node_dir,
+ pair->src_revnum,
+ ra_session,
+ svn_depth_infinity,
+ scratch_pool,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL,
+ ctx->wc_ctx,
+ pair->src_abspath_or_url,
+ svn_depth_infinity,
+ scratch_pool, scratch_pool));
+
+ /* ### gather_definitions returns propvals as const char * */
+ for (hi = apr_hash_first(scratch_pool, externals_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *local_abspath_or_url = apr_hash_this_key(hi);
+ const char *propval = apr_hash_this_val(hi);
+ svn_string_t *new_propval = svn_string_create(propval, scratch_pool);
+
+ svn_hash_sets(externals_props, local_abspath_or_url, new_propval);
+ }
+ }
+
+ if (apr_hash_count(externals_props) == 0)
+ {
+ if (old_url)
+ SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ *new_externals = apr_hash_make(result_pool);
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, externals_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *local_abspath_or_url = apr_hash_this_key(hi);
+ svn_string_t *externals_propval = apr_hash_this_val(hi);
+ const char *relpath;
+ svn_string_t *new_propval;
+
+ svn_pool_clear(iterpool);
+
+ SVN_DBG(("repos root url: %s", repos_root_url));
+ SVN_DBG(("pinning externals for %s: %s", local_abspath_or_url, externals_propval->data));
+ SVN_ERR(pin_externals_prop(&new_propval, externals_propval,
+ repos_root_url, local_abspath_or_url, ctx,
+ result_pool, iterpool));
+ if (svn_path_is_url(pair->src_abspath_or_url))
+ relpath = svn_uri_skip_ancestor(pair->src_abspath_or_url,
+ local_abspath_or_url,
+ result_pool);
+ else
+ relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url,
+ local_abspath_or_url);
+ SVN_ERR_ASSERT(relpath);
+ svn_hash_sets(*new_externals, relpath, new_propval);
+ }
+ svn_pool_destroy(iterpool);
+
+ if (old_url)
+ SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
 /* The guts of do_wc_to_wc_copies */
 static svn_error_t *
 do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
                                    const apr_array_header_t *copy_pairs,
                                    const char *dst_parent,
+ svn_boolean_t pin_externals,
                                    svn_client_ctx_t *ctx,
                                    apr_pool_t *scratch_pool)
 {
@@ -195,6 +495,8 @@ do_wc_to_wc_copies_with_write_lock(svn_boolean_t *
       const char *dst_abspath;
       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
                                                     svn_client__copy_pair_t *);
+ apr_hash_t *pinned_externals = NULL;
+
       svn_pool_clear(iterpool);
 
       /* Check for cancellation */
@@ -201,6 +503,19 @@ do_wc_to_wc_copies_with_write_lock(svn_boolean_t *
       if (ctx->cancel_func)
         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
 
+ if (pin_externals)
+ {
+ const char *repos_root_url;
+
+ SVN_ERR(svn_wc__node_get_origin(NULL, NULL, NULL, &repos_root_url,
+ NULL, NULL, NULL, ctx->wc_ctx,
+ pair->src_abspath_or_url, FALSE,
+ scratch_pool, iterpool));
+ SVN_ERR(resolve_pinned_externals(&pinned_externals, pair, NULL,
+ repos_root_url, ctx,
+ iterpool, iterpool));
+ }
+
       /* Perform the copy */
       dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
                                     iterpool);
@@ -211,6 +526,31 @@ do_wc_to_wc_copies_with_write_lock(svn_boolean_t *
                          ctx->notify_func2, ctx->notify_baton2, iterpool);
       if (err)
         break;
+
+ if (pinned_externals)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(iterpool, pinned_externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *dst_relpath = apr_hash_this_key(hi);
+ svn_string_t *externals_propval = apr_hash_this_val(hi);
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
+ dst_relpath, iterpool);
+ SVN_DBG(("New externals for %s: %s", local_abspath, externals_propval->data));
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
+ SVN_PROP_EXTERNALS, externals_propval,
+ svn_depth_empty, TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL, /* no extra notification */
+ iterpool));
+ }
+ }
     }
   svn_pool_destroy(iterpool);
 
@@ -223,6 +563,7 @@ do_wc_to_wc_copies_with_write_lock(svn_boolean_t *
 static svn_error_t *
 do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
                    const apr_array_header_t *copy_pairs,
+ svn_boolean_t pin_externals,
                    svn_client_ctx_t *ctx,
                    apr_pool_t *pool)
 {
@@ -236,7 +577,7 @@ do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
 
   SVN_WC__CALL_WITH_WRITE_LOCK(
     do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
- ctx, pool),
+ pin_externals, ctx, pool),
     ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
 
   return SVN_NO_ERROR;
@@ -594,6 +935,8 @@ typedef struct path_driver_info_t
   svn_boolean_t resurrection;
   svn_boolean_t dir_add;
   svn_string_t *mergeinfo; /* the new mergeinfo for the target */
+ svn_string_t *externals; /* new externals definitions for the target */
+ svn_boolean_t only_pin_externals;
 } path_driver_info_t;
 
 
@@ -626,12 +969,13 @@ path_driver_cb_func(void **dir_baton,
   /* Initialize return value. */
   *dir_baton = NULL;
 
+ SVN_DBG(("%s: path=%s", __func__, path));
   /* This function should never get an empty PATH. We can neither
      create nor delete the empty PATH, so if someone is calling us
      with such, the code is just plain wrong. */
   SVN_ERR_ASSERT(! svn_path_is_empty(path));
 
- /* Check to see if we need to add the path as a directory. */
+ /* Check to see if we need to add the path as a parent directory. */
   if (path_info->dir_add)
     {
       return cb_baton->editor->add_directory(path, parent_baton, NULL,
@@ -662,7 +1006,7 @@ path_driver_cb_func(void **dir_baton,
       /* Not a move? This must just be the copy addition. */
       else
         {
- do_add = TRUE;
+ do_add = !path_info->only_pin_externals;
         }
     }
 
@@ -702,6 +1046,29 @@ path_driver_cb_func(void **dir_baton,
                                                       pool));
         }
     }
+
+ if (path_info->externals)
+ {
+ svn_boolean_t opened_dir = FALSE;
+
+ if (*dir_baton == NULL)
+ {
+ SVN_ERR(cb_baton->editor->open_directory(path, parent_baton,
+ SVN_INVALID_REVNUM,
+ pool, dir_baton));
+ opened_dir = TRUE;
+ }
+
+ SVN_DBG(("New externals for %s: %s", path_info->dst_path, path_info->externals->data));
+ SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
+ path_info->externals, pool));
+ if (opened_dir)
+ {
+ SVN_ERR(cb_baton->editor->close_directory(*dir_baton, pool));
+ *dir_baton = NULL;
+ }
+ }
+
   return SVN_NO_ERROR;
 }
 
@@ -787,6 +1154,71 @@ find_absent_parents2(svn_ra_session_t *ra_session,
 }
 
 static svn_error_t *
+queue_externals_change_path_infos(apr_array_header_t *new_path_infos,
+ apr_array_header_t *path_infos,
+ apr_hash_t *pinned_externals,
+ path_driver_info_t *parent_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, pinned_externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *dst_relpath = apr_hash_this_key(hi);
+ svn_string_t *externals_prop = apr_hash_this_val(hi);
+ const char *src_url;
+ path_driver_info_t *info;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ src_url = svn_path_url_add_component2(parent_info->src_url,
+ dst_relpath, iterpool);
+
+ /* Try to find a path info the external change can be applied to. */
+ info = NULL;
+ for (i = 0; i < path_infos->nelts; i++)
+ {
+ path_driver_info_t *existing_info;
+
+ existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
+ if (strcmp(src_url, existing_info->src_url) == 0)
+ {
+ info = existing_info;
+ break;
+ }
+ }
+
+ if (info == NULL)
+ {
+ /* A copied-along child needs its externals pinned.
+ Create a new path info for this property change. */
+ info = apr_pcalloc(result_pool, sizeof(*info));
+ info->src_url = svn_path_url_add_component2(
+ parent_info->src_url, dst_relpath,
+ result_pool);
+ info->src_path = svn_relpath_join(parent_info->src_path,
+ dst_relpath,
+ result_pool);
+ info->dst_path = svn_relpath_join(parent_info->dst_path,
+ dst_relpath,
+ result_pool);
+ info->src_kind = svn_node_dir;
+ info->only_pin_externals = TRUE;
+ APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info;
+ }
+
+ info->externals = externals_prop;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
 repos_to_repos_copy(const apr_array_header_t *copy_pairs,
                     svn_boolean_t make_parents,
                     const apr_hash_t *revprop_table,
@@ -794,6 +1226,7 @@ repos_to_repos_copy(const apr_array_header_t *copy
                     void *commit_baton,
                     svn_client_ctx_t *ctx,
                     svn_boolean_t is_move,
+ svn_boolean_t pin_externals,
                     apr_pool_t *pool)
 {
   svn_error_t *err;
@@ -809,6 +1242,7 @@ repos_to_repos_copy(const apr_array_header_t *copy
   struct path_driver_cb_baton cb_baton;
   apr_array_header_t *new_dirs = NULL;
   apr_hash_t *commit_revprops;
+ apr_array_header_t *pin_externals_only_infos = NULL;
   int i;
   svn_client__copy_pair_t *first_pair =
     APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
@@ -1073,6 +1507,24 @@ repos_to_repos_copy(const apr_array_header_t *copy
       svn_hash_sets(action_hash, info->dst_path, info);
       if (is_move && (! info->resurrection))
         svn_hash_sets(action_hash, info->src_path, info);
+
+ if (pin_externals)
+ {
+ apr_hash_t *pinned_externals;
+
+ SVN_ERR(resolve_pinned_externals(&pinned_externals, pair,
+ ra_session, repos_root,
+ ctx, pool, pool));
+ if (pin_externals_only_infos == NULL)
+ {
+ pin_externals_only_infos =
+ apr_array_make(pool, 0, sizeof(path_driver_info_t *));
+ }
+ SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos,
+ path_infos,
+ pinned_externals,
+ info, pool, pool));
+ }
     }
 
   if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
@@ -1158,6 +1610,19 @@ repos_to_repos_copy(const apr_array_header_t *copy
         APR_ARRAY_PUSH(paths, const char *) = info->src_path;
     }
 
+ /* Add any items which only need their externals pinned. */
+ if (pin_externals_only_infos)
+ {
+ for (i = 0; i < pin_externals_only_infos->nelts; i++)
+ {
+ path_driver_info_t *info;
+
+ info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *);
+ APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
+ svn_hash_sets(action_hash, info->dst_path, info);
+ }
+ }
+
   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
                                            message, ctx, pool));
 
@@ -1236,6 +1701,62 @@ check_url_kind(void *baton,
   return SVN_NO_ERROR;
 }
 
+/* Queue a property change to COMMIT_URL in the COMMIT_ITEMS list.
+ * If the list does not already have a commit item for LOCAL_ABSPATH
+ * add a new commit item for the property change.
+ * Allocate results in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+queue_prop_change_commit_items(const char *commit_url,
+ apr_array_header_t *commit_items,
+ const char *propname,
+ svn_string_t *propval,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_commit_item3_t *item = NULL;
+ svn_prop_t *prop;
+ int i;
+
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *existing_item;
+
+ existing_item = APR_ARRAY_IDX(commit_items, i,
+ svn_client_commit_item3_t *);
+ if (strcmp(existing_item->url, commit_url) == 0)
+ {
+ SVN_DBG(("using existing item for %s", commit_url));
+ item = existing_item;
+ break;
+ }
+ }
+
+ if (item == NULL)
+ {
+ SVN_DBG(("using new item for %s", commit_url));
+ item = svn_client_commit_item3_create(result_pool);
+ item->url = commit_url;
+ item->kind = svn_node_dir;
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ }
+ else
+ item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
+
+ if (item->outgoing_prop_changes == NULL)
+ item->outgoing_prop_changes = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_t *));
+
+ SVN_DBG(("%s: item->url=%s item->state_flags=0x%x propval=%s", __func__, item->url, item->state_flags, propval->data));
+ prop = apr_palloc(result_pool, sizeof(*prop));
+ prop->name = propname;
+ prop->value = propval;
+ APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop;
+
+ return SVN_NO_ERROR;
+}
+
 /* ### Copy ...
  * COMMIT_INFO_P is ...
  * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
@@ -1249,6 +1770,7 @@ wc_to_repos_copy(const apr_array_header_t *copy_pa
                  const apr_hash_t *revprop_table,
                  svn_commit_callback2_t commit_callback,
                  void *commit_baton,
+ svn_boolean_t pin_externals,
                  svn_client_ctx_t *ctx,
                  apr_pool_t *scratch_pool)
 {
@@ -1486,6 +2008,38 @@ wc_to_repos_copy(const apr_array_header_t *copy_pa
           APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
             = mergeinfo_prop;
         }
+
+ if (pin_externals)
+ {
+ apr_hash_t *pinned_externals;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(resolve_pinned_externals(&pinned_externals, pair,
+ ra_session, cukb.repos_root_url,
+ ctx, scratch_pool, iterpool));
+ for (hi = apr_hash_first(scratch_pool, pinned_externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *dst_relpath = apr_hash_this_key(hi);
+ svn_string_t *externals_propval = apr_hash_this_val(hi);
+ const char *dst_url;
+ const char *commit_url;
+
+ if (svn_path_is_url(pair->dst_abspath_or_url))
+ dst_url = pair->dst_abspath_or_url;
+ else
+ SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx,
+ pair->dst_abspath_or_url,
+ scratch_pool, iterpool));
+ commit_url = svn_path_url_add_component2(dst_url, dst_relpath,
+ scratch_pool);
+ SVN_ERR(queue_prop_change_commit_items(commit_url, commit_items,
+ SVN_PROP_EXTERNALS,
+ externals_propval,
+ scratch_pool, iterpool));
+ }
+ }
     }
 
   /* Sort and condense our COMMIT_ITEMS. */
@@ -1589,6 +2143,7 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_s
                         svn_client__copy_pair_t *pair,
                         svn_boolean_t same_repositories,
                         svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
                         svn_ra_session_t *ra_session,
                         svn_client_ctx_t *ctx,
                         apr_pool_t *pool)
@@ -1697,6 +2252,43 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_s
 
           return SVN_NO_ERROR;
         }
+
+ if (pin_externals)
+ {
+ apr_hash_t *pinned_externals;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ const char *repos_root_url;
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
+ SVN_ERR(resolve_pinned_externals(&pinned_externals, pair,
+ ra_session, repos_root_url,
+ ctx, pool, pool));
+
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, pinned_externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *dst_relpath = apr_hash_this_key(hi);
+ svn_string_t *externals_propval = apr_hash_this_val(hi);
+ const char *local_abspath;
+
+ svn_pool_clear(iterpool);
+
+ local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
+ dst_relpath, iterpool);
+ SVN_DBG(("New externals for %s: %s", local_abspath, externals_propval->data));
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
+ SVN_PROP_EXTERNALS, externals_propval,
+ svn_depth_empty, TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL, /* no extra notification */
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
     } /* end directory case */
 
   else if (pair->src_kind == svn_node_file)
@@ -1756,6 +2348,7 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_s
                         const apr_array_header_t *copy_pairs,
                         const char *top_dst_path,
                         svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
                         svn_ra_session_t *ra_session,
                         svn_client_ctx_t *ctx,
                         apr_pool_t *scratch_pool)
@@ -1820,7 +2413,7 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_s
                                       APR_ARRAY_IDX(copy_pairs, i,
                                                     svn_client__copy_pair_t *),
                                       same_repositories,
- ignore_externals,
+ ignore_externals, pin_externals,
                                       ra_session, ctx, iterpool));
     }
   svn_pool_destroy(iterpool);
@@ -1833,6 +2426,7 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
                  const apr_array_header_t *copy_pairs,
                  svn_boolean_t make_parents,
                  svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
                  svn_client_ctx_t *ctx,
                  apr_pool_t *pool)
 {
@@ -1941,7 +2535,7 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
   SVN_WC__CALL_WITH_WRITE_LOCK(
     repos_to_wc_copy_locked(timestamp_sleep,
                             copy_pairs, top_dst_path, ignore_externals,
- ra_session, ctx, pool),
+ pin_externals, ra_session, ctx, pool),
     ctx->wc_ctx, lock_abspath, FALSE, pool);
   return SVN_NO_ERROR;
 }
@@ -1967,6 +2561,7 @@ try_copy(svn_boolean_t *timestamp_sleep,
          svn_boolean_t metadata_only,
          svn_boolean_t make_parents,
          svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
          const apr_hash_t *revprop_table,
          svn_commit_callback2_t commit_callback,
          void *commit_baton,
@@ -2267,7 +2862,9 @@ try_copy(svn_boolean_t *timestamp_sleep,
           /* We ignore these values, so assert the default value */
           SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only);
           return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
- copy_pairs, ctx, pool));
+ copy_pairs,
+ pin_externals,
+ ctx, pool));
         }
     }
   else if ((! srcs_are_urls) && (dst_is_url))
@@ -2274,7 +2871,8 @@ try_copy(svn_boolean_t *timestamp_sleep,
     {
       return svn_error_trace(
         wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
- commit_callback, commit_baton, ctx, pool));
+ commit_callback, commit_baton,
+ pin_externals, ctx, pool));
     }
   else if ((srcs_are_urls) && (! dst_is_url))
     {
@@ -2281,7 +2879,7 @@ try_copy(svn_boolean_t *timestamp_sleep,
       return svn_error_trace(
         repos_to_wc_copy(timestamp_sleep,
                          copy_pairs, make_parents, ignore_externals,
- ctx, pool));
+ pin_externals, ctx, pool));
     }
   else
     {
@@ -2288,7 +2886,7 @@ try_copy(svn_boolean_t *timestamp_sleep,
       return svn_error_trace(
         repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
                             commit_callback, commit_baton, ctx, is_move,
- pool));
+ pin_externals, pool));
     }
 }
 
@@ -2296,11 +2894,12 @@ try_copy(svn_boolean_t *timestamp_sleep,
 
 /* Public Interfaces */
 svn_error_t *
-svn_client_copy6(const apr_array_header_t *sources,
+svn_client_copy7(const apr_array_header_t *sources,
                  const char *dst_path,
                  svn_boolean_t copy_as_child,
                  svn_boolean_t make_parents,
                  svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
                  const apr_hash_t *revprop_table,
                  svn_commit_callback2_t commit_callback,
                  void *commit_baton,
@@ -2322,6 +2921,7 @@ svn_error_t *
                  FALSE /* metadata_only */,
                  make_parents,
                  ignore_externals,
+ pin_externals,
                  revprop_table,
                  commit_callback, commit_baton,
                  ctx,
@@ -2356,6 +2956,7 @@ svn_error_t *
                      FALSE /* metadata_only */,
                      make_parents,
                      ignore_externals,
+ pin_externals,
                      revprop_table,
                      commit_callback, commit_baton,
                      ctx,
@@ -2417,6 +3018,7 @@ svn_client_move7(const apr_array_header_t *src_pat
                  metadata_only,
                  make_parents,
                  FALSE /* ignore_externals */,
+ FALSE /* pin_externals */,
                  revprop_table,
                  commit_callback, commit_baton,
                  ctx,
@@ -2450,6 +3052,7 @@ svn_client_move7(const apr_array_header_t *src_pat
                      metadata_only,
                      make_parents,
                      FALSE /* ignore_externals */,
+ FALSE /* pin_externals */,
                      revprop_table,
                      commit_callback, commit_baton,
                      ctx,
Index: subversion/libsvn_client/deprecated.c
===================================================================
--- subversion/libsvn_client/deprecated.c (.../trunk) (revision 1654574)
+++ subversion/libsvn_client/deprecated.c (.../branches/pin-externals) (working copy)
@@ -627,6 +627,25 @@ svn_client_commit(svn_client_commit_info_t **commi
 
 /*** From copy.c ***/
 svn_error_t *
+svn_client_copy6(const apr_array_header_t *sources,
+ const char *dst_path,
+ svn_boolean_t copy_as_child,
+ svn_boolean_t make_parents,
+ svn_boolean_t ignore_externals,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_client_copy7(sources, dst_path, copy_as_child,
+ make_parents, ignore_externals,
+ FALSE, revprop_table,
+ commit_callback, commit_baton,
+ ctx, pool));
+}
+
+svn_error_t *
 svn_client_copy5(svn_commit_info_t **commit_info_p,
                  const apr_array_header_t *sources,
                  const char *dst_path,
Index: subversion/svn/svn.c
===================================================================
--- subversion/svn/svn.c (.../trunk) (revision 1654574)
+++ subversion/svn/svn.c (.../branches/pin-externals) (working copy)
@@ -144,7 +144,8 @@ typedef enum svn_cl__longopt_t {
   opt_remove_unversioned,
   opt_remove_ignored,
   opt_no_newline,
- opt_show_passwords
+ opt_show_passwords,
+ opt_pin_externals,
 } svn_cl__longopt_t;
 
 
@@ -418,6 +419,10 @@ const apr_getopt_option_t svn_cl__options[] =
   {"remove-ignored", opt_remove_ignored, 0, N_("remove ignored items")},
   {"no-newline", opt_no_newline, 0, N_("do not output the trailing newline")},
   {"show-passwords", opt_show_passwords, 0, N_("show cached passwords")},
+ {"pin-externals", opt_pin_externals, 0,
+ N_("pin externals with no explicit revision to their\n"
+ " "
+ "last-changed revision (recommended when tagging)")},
 
   /* Long-opt Aliases
    *
@@ -608,7 +613,8 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table
      " contact the repository. As such, they may not, by default, be able\n"
      " to propagate merge tracking information from the source of the copy\n"
      " to the destination.\n"),
- {'r', 'q', opt_ignore_externals, opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
+ {'r', 'q', opt_ignore_externals, opt_parents, SVN_CL__LOG_MSG_OPTIONS,
+ opt_pin_externals} },
 
   { "delete", svn_cl__delete, {"del", "remove", "rm"}, N_
     ("Remove files and directories from version control.\n"
@@ -2384,6 +2390,9 @@ sub_main(int *exit_code, int argc, const char *arg
       case opt_show_passwords:
         opt_state.show_passwords = TRUE;
         break;
+ case opt_pin_externals:
+ opt_state.pin_externals = TRUE;
+ break;
       default:
         /* Hmmm. Perhaps this would be a good place to squirrel away
            opts that commands like svn diff might need. Hmmm indeed. */
Index: subversion/svn/copy-cmd.c
===================================================================
--- subversion/svn/copy-cmd.c (.../trunk) (revision 1654574)
+++ subversion/svn/copy-cmd.c (.../branches/pin-externals) (working copy)
@@ -167,8 +167,9 @@ svn_cl__copy(apr_getopt_t *os,
     SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
                                        NULL, ctx->config, pool));
 
- err = svn_client_copy6(sources, dst_path, TRUE,
+ err = svn_client_copy7(sources, dst_path, TRUE,
                          opt_state->parents, opt_state->ignore_externals,
+ opt_state->pin_externals,
                          opt_state->revprop_table,
                          (opt_state->quiet ? NULL : svn_cl__print_commit_info),
                          NULL,
Index: subversion/svn/cl.h
===================================================================
--- subversion/svn/cl.h (.../trunk) (revision 1654574)
+++ subversion/svn/cl.h (.../branches/pin-externals) (working copy)
@@ -248,6 +248,7 @@ typedef struct svn_cl__opt_state_t
   svn_boolean_t remove_ignored; /* remove ignored items */
   svn_boolean_t no_newline; /* do not output the trailing newline */
   svn_boolean_t show_passwords; /* show cached passwords */
+ svn_boolean_t pin_externals; /* pin externals to last-changed revisions */
 } svn_cl__opt_state_t;
 
 
Index: .
===================================================================
--- . (.../trunk) (revision 1654574)
+++ . (.../branches/pin-externals) (working copy)

Property changes on: .
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
   Merged /subversion/trunk:r1643755-1654573
Received on 2015-01-24 21:08:40 CET

This is an archived mail posted to the Subversion Dev mailing list.