Branko Čibej | 29 Jan 08:08 2015

Re: svn commit: r1628536 - in /subversion/trunk/subversion: libsvn_client/externals.c tests/cmdline/externals_tests.py

> Author: stsp
> Date: Tue Sep 30 20:03:43 2014
> New Revision: 1628536
>
> URL: http://svn.apache.org/r1628536
> Log:
> Fix issue #4085, "external can shadow a versioned directory".

[...]

> Modified: subversion/trunk/subversion/libsvn_client/externals.c
> URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/externals.c?rev=1628536&r1=1628535&r2=1628536&view=diff
> ==============================================================================
> --- subversion/trunk/subversion/libsvn_client/externals.c (original)
> +++ subversion/trunk/subversion/libsvn_client/externals.c Tue Sep 30 20:03:43 2014
>  <at>  <at>  -169,6 +169,37  <at>  <at>  switch_dir_external(const char *local_ab
>    if (revision->kind == svn_opt_revision_number)
>      external_rev = revision->value.number;
>  
> +  /* 
> +   * The code below assumes existing versioned paths are *not* part of
> +   * the external's defining working copy.
> +   * The working copy library does not support registering externals
> +   * on top of existing BASE nodes and will error out if we try.
> +   * So if the external target is part of the defining working copy's
> +   * BASE tree, don't attempt to create the external. Doing so would
> +   * leave behind a switched path instead of an external (since the
> +   * switch succeeds but registration of the external in the DB fails).
> +   * The working copy then cannot be updated until the path is switched back.
> +   * See issue #4085.
(Continue reading)

Stefan Sperling | 28 Jan 10:54 2015
Picon

[vote] pin-externals branch to trunk

I'd like to start a vote about merging the pin-externals branch to trunk.

This command shows the changes to be merged:
    svn diff https://svn.apache.org/repos/asf/subversion/trunk <at> 1655241 \
	https://svn.apache.org/repos/asf/subversion/branches/pin-externals

Below is a full log message for the entire changeset.

[[[
Add a '--pin-externals' option to 'svn copy'.
This option enables automated pinning of URLs in svn:externals properties
during copy operations (issue #1258).

  --pin-externals          : pin externals with no explicit revision to their
                             last-changed revision (recommended when tagging)

This feature makes the svncopy.pl contrib script unnecessary.

Externals are "pinned" by adding a peg revision to the external's source URL.
For example, the external definition:
  ^/foo/bar ext_bar
might become:
  ^/foo/bar <at> 400 ext_bar

An external that is already pinned is left as-is.

There is a known problem where relative URLs in the externals definition
may become absolute when pinned. This happens if the URL of an external
resolves to a different path at its last-changed revision. For instance,
an external definition at r600 might look like this:
(Continue reading)

Ben Reser | 28 Jan 01:55 2015

[VOTE} Merge svn-auth-x509 branch to trunk?

I think we should get this merged to trunk.

The original email asking to start this merge happened back in August here:
https://mail-archives.apache.org/mod_mbox/subversion-dev/201408.mbox/%3C53E1C1D7.2040005%40reser.org%3E

Since that email the checksum formatting code was removed and there have been
some API changes to make the API more capable of fully representing the
certificates.  As well as quite a few bug fixes.

You can get a diff with:
svn diff ^/subversion/trunk <at> 1655188 ^/subversion/branches/svn-auth-x509

Per the decision in Berlin 2013, I'm asking for a vote to bring this branch
into trunk.  This is currently holding up 1.9 branch, so I'd like to get this
on trunk.

There are some further fixes I'd like to make but I'm going to hold off on
doing that for now and do so on trunk.

Daniel Shahaf | 27 Jan 11:14 2015

[PATCH] svn --version --verbose += /etc/os-release

[[[
svn --version --verbose: Support /etc/os-release, the systemd "what distro
am I running" API.

* subversion/libsvn_subr/sysinfo.c
  (linux_release_name): Try /etc/os-release if /usr/bin/lsb_release fails.
  (systemd_release): New helper for linux_release_name().
  (remove_shell_quoting): New helper function.
]]]

--- subversion/libsvn_subr/sysinfo.c
+++ subversion/libsvn_subr/sysinfo.c
 <at>  <at>  -410,6 +410,78  <at>  <at>  lsb_release(apr_pool_t *pool)
   return NULL;
 }

+/* Attempt to strip double quotes from a string.
+ *
+ * The string considered is (STR->DATA + OFFSET).  If it starts
+ * and ends with double quotes, and has no escape sequences, return
+ * the string delimited by the double quotes.  Otherwise, return
+ * the original string verbatim.
+ */
+static const char *
+remove_shell_quoting(svn_stringbuf_t *buf,
+                     int offset,
+                     apr_pool_t *result_pool)
+{
+  const char *str = buf->data + offset;
+  const char *second_double_quote;
(Continue reading)

Markus Schaber | 27 Jan 09:20 2015

Repository polling and svnrobots.txt

Hi,

Some of our clients[2] users want an automatic polling for updates and locks by other users.

To prevent the clients from "hammering" the servers, I want to implement some rate limiting. Is the
svnrobotx.txt[1] the recommended way to allow such rate limiting to be configured by the server admins?
Or are there other, similar specifications my client should respect (maybe in addition)?

(Remark: Due to the nature and culture of the world of IEC-61131-3 programming, most (if not all) our users
only connect to their own corporate servers, it is rather unlikely to see them using public servers like SourceForge.)

[1] Used by Stephan Küngs SVNCommitMontior, see http://stefanstools.sourceforge.net/svnrobots.html
[2] CODESYS SVN, based on SharpSVN, see http://store.codesys.com/codesys-svn.html

Best regards

Markus Schaber
--

-- 
CODESYS® a trademark of 3S-Smart Software Solutions GmbH

Inspiring Automation Solutions

3S-Smart Software Solutions GmbH
Dipl.-Inf. Markus Schaber | Product Development Core Technology
Memminger Str. 151 | 87439 Kempten | Germany
Tel. +49-831-54031-979 | Fax +49-831-54031-50

E-Mail: m.schaber <at> codesys.com | Web: http://www.codesys.com | CODESYS store: http://store.codesys.com
CODESYS forum: http://forum.codesys.com

(Continue reading)

Picon

[l10n] Translation status report for trunk r1654975

Translation status report for trunk <at> r1654975

  lang   trans untrans   fuzzy     obs
--------------------------------------
    de    2873       3      10     492  +++++++++++++++++++++++++++++~ooooo
    es    2265     611     830     528  ++++++++++++++++++UUUUU~~~~~~~oooo
    fr    2569     307     519     113  ++++++++++++++++++++++UUU~~~~~
    it    2125     751     953     340  ++++++++++++++++UUUUUU~~~~~~~~oo
    ja    2255     621     878     764  ++++++++++++++++++UUUU~~~~~~~~oooooo
    ko    2401     475     651     218  ++++++++++++++++++++UUUU~~~~~~o
    nb    2315     561     781     501  ++++++++++++++++++UUUUU~~~~~~~oooo
    pl    2340     536     748     298  +++++++++++++++++++UUUU~~~~~~~oo
 pt_BR    2101     775     966     321  ++++++++++++++++UUUUUU~~~~~~~~oo
    sv    2732     144     316      79  +++++++++++++++++++++++++UU~~~
 zh_CN    2652     224     388      23  ++++++++++++++++++++++++UU~~~~
 zh_TW    2042     834    1003     377  +++++++++++++++UUUUUUU~~~~~~~~oo

Hyrum K Wright | 27 Jan 05:07 2015

Stack overflow in checksum.c

There's a stack overflow bug in subversion/libsubr/checksum.c.

The functions svn_checksum__from_digest_fnv1a_32x4() and svn_checksum__from_digest_fnv1a_32() both look something like this:

svn_checksum_t *
svn_checksum__from_digest_fnv1a_32x4(const unsigned char *digest,
        apr_pool_t *result_pool)
{
  return checksum_create(svn_checksum_fnv1a_32x4, sizeof(digest), digest,
     result_pool);
}

The problem is that checksum_create() expects the length of the string pointed to by digest, but sizeof(digest) returns the number of bytes that a variable of type 'const unsigned char *' requires.  These methods are currently only called with unint32_t cast to an unsigned char, but on platforms which have 8-byte pointers, this leads to a buffer overflow in checksum_create() when it tries to read more than the provided 4 bytes.

I *think* the correct fix is to add a digest_size argument to svn_checksum__from_digest_fnv1a_32x4() and svn_checksum__from_digest_fnv1a_32(), but I'm not sure if there's a better way which somebody more familiar with this code would know about.

By all accounts, this code hasn't been released yet, so it's not a yet security issue (hence the post to dev <at> and not private <at> ), but I thought somebody would like to be aware of it. :)

-Hyrum
Stefan Fuhrmann | 27 Jan 02:54 2015

Issue 4554 - Heads up

This issue is about a gap between FSFS spec and implementation.
Generalized rep sharing in 1.8 made that gap visible but it has
always been there.

No data is being lost and checkout etc. should work.  However,
dump files generated from the affected repos will not load.  There
are 2 places in mod_dav_svn that call svn_fs_file_length() and
might not result in mere cosmetic problems.  Can someone please
check what happens when those falsely get file length 0?

The issue is fixed on /trunk, backport branches for 1.7 and 1.8
will be available soon.

-- Stefan^2.
Ben Reser | 26 Jan 19:59 2015

CentOS buildbot

Seems that the machine that was running our CentOS buildbot has disappeared.
We can get a new one setup (probably a VM, not sure if the old one was physical
hardware or a VM).

But the question is now what do we want.  The CentOS buildbot was running a
rather old version of CentOS, we actually had to install a few dependencies
manually (APR, serf) because the OS packaged versions didn't meet our minimum
requirements.  So the buildbot was acting somewhat of a test of minimal
requirements.

Do we want to try and duplicate that or do we want a more modern CentOS?

Andreas Stieger | 25 Jan 22:52 2015
Picon
Picon

[PATCH] Fix build errors in bindings with SWIG 3.0.4

Hello,

I saw build errors in the bindings with SWIG 3.0.4:
./subversion/bindings/swig/core.i:792: Error: Unknown SWIG preprocessor
directive: The (if this is a block of target language code, delimit it
with %{ and %})

The attached patch fixes the comment style issue in trunk and 1.8.x.

[[[
Fix build errors in bindings with SWIG 3.0.4

* subversion/bindings/swig/core.i
  (core_set_current_pool): Fix comment style.

* subversion/bindings/swig/include/proxy.swg
  (__getattr__,__setattr__): Same.

* subversion/bindings/swig/svn_delta.i
  (_ops_get): Same.
]]]

With kind regards,
Andreas Stieger
Attachment (fix-swig-3-error-0.patch): text/x-patch, 2795 bytes
Stefan Sperling | 24 Jan 21:07 2015
Picon

pin-externals branch: call for review

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)
 <at>  <at>  -1754,12 +1754,15  <at>  <at>  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)
 <at>  <at>  -3547,6 +3547,186  <at>  <at>  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 <at> %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 <at> %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
 
 <at>  <at>  -3607,6 +3787,7  <at>  <at>  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)
 <at>  <at>  -4486,6 +4486,10  <at>  <at>  typedef struct svn_client_copy_source_t
  * If  <at> a ignore_externals is set, don't process externals definitions
  * as part of this operation.
  *
+ * If  <at> 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,  <at> 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
 <at>  <at>  -4504,7 +4508,26  <at>  <at>  typedef struct svn_client_copy_source_t
  *  <at> a commit_callback with  <at> a commit_baton and a #svn_commit_info_t for
  * the commit.
  *
+ *  <at> 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.
+ *
  *  <at> since New in 1.7.
+ *  <at> 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)
 <at>  <at>  -1179,6 +1179,40  <at>  <at>  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)
 <at>  <at>  -526,39 +526,18  <at>  <at>  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;
 <at>  <at>  -670,15 +649,15  <at>  <at>  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);
 <at>  <at>  -968,7 +947,8  <at>  <at>  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)
 <at>  <at>  -177,12 +177,312  <at>  <at>  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 <at> %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)
 {
 <at>  <at>  -195,6 +495,8  <at>  <at>  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 */
 <at>  <at>  -201,6 +503,19  <at>  <at>  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);
 <at>  <at>  -211,6 +526,31  <at>  <at>  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);
 
 <at>  <at>  -223,6 +563,7  <at>  <at>  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)
 {
 <at>  <at>  -236,7 +577,7  <at>  <at>  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;
 <at>  <at>  -594,6 +935,8  <at>  <at>  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;
 
 
 <at>  <at>  -626,12 +969,13  <at>  <at>  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,
 <at>  <at>  -662,7 +1006,7  <at>  <at>  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;
         }
     }
 
 <at>  <at>  -702,6 +1046,29  <at>  <at>  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;
 }
 
 <at>  <at>  -787,6 +1154,71  <at>  <at>  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,
 <at>  <at>  -794,6 +1226,7  <at>  <at>  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;
 <at>  <at>  -809,6 +1242,7  <at>  <at>  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 *);
 <at>  <at>  -1073,6 +1507,24  <at>  <at>  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))
 <at>  <at>  -1158,6 +1610,19  <at>  <at>  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));
 
 <at>  <at>  -1236,6 +1701,62  <at>  <at>  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
 <at>  <at>  -1249,6 +1770,7  <at>  <at>  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)
 {
 <at>  <at>  -1486,6 +2008,38  <at>  <at>  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. */
 <at>  <at>  -1589,6 +2143,7  <at>  <at>  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)
 <at>  <at>  -1697,6 +2252,43  <at>  <at>  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)
 <at>  <at>  -1756,6 +2348,7  <at>  <at>  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)
 <at>  <at>  -1820,7 +2413,7  <at>  <at>  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);
 <at>  <at>  -1833,6 +2426,7  <at>  <at>  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)
 {
 <at>  <at>  -1941,7 +2535,7  <at>  <at>  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;
 }
 <at>  <at>  -1967,6 +2561,7  <at>  <at>  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,
 <at>  <at>  -2267,7 +2862,9  <at>  <at>  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))
 <at>  <at>  -2274,7 +2871,8  <at>  <at>  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))
     {
 <at>  <at>  -2281,7 +2879,7  <at>  <at>  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
     {
 <at>  <at>  -2288,7 +2886,7  <at>  <at>  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));
     }
 }
 
 <at>  <at>  -2296,11 +2894,12  <at>  <at>  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,
 <at>  <at>  -2322,6 +2921,7  <at>  <at>  svn_error_t *
                  FALSE /* metadata_only */,
                  make_parents,
                  ignore_externals,
+                 pin_externals,
                  revprop_table,
                  commit_callback, commit_baton,
                  ctx,
 <at>  <at>  -2356,6 +2956,7  <at>  <at>  svn_error_t *
                      FALSE /* metadata_only */,
                      make_parents,
                      ignore_externals,
+                     pin_externals,
                      revprop_table,
                      commit_callback, commit_baton,
                      ctx,
 <at>  <at>  -2417,6 +3018,7  <at>  <at>  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,
 <at>  <at>  -2450,6 +3052,7  <at>  <at>  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)
 <at>  <at>  -627,6 +627,25  <at>  <at>  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)
 <at>  <at>  -144,7 +144,8  <at>  <at>  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;
 
 
 <at>  <at>  -418,6 +419,10  <at>  <at>  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
    *
 <at>  <at>  -608,7 +613,8  <at>  <at>  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"
 <at>  <at>  -2384,6 +2390,9  <at>  <at>  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)
 <at>  <at>  -167,8 +167,9  <at>  <at>  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)
 <at>  <at>  -248,6 +248,7  <at>  <at>  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





Gmane