Slow execution with needs lock property
Hi, In a repository that contains a large number of files and folders (over 100K), many of which have the needs-lock property set, If any user attempts to check out this entire repository, the large number of locks created on the server render the server almost unusable due to high load and cause the 'svn ls --xml' command to fail. I ve attached a test script which will create the environment to reproduce this problem. Some of the tests I did with different versions of subversion for the above problem revealed that there is something wrong with svn 1.6.x versions of subversion. Following are my findings, With svn 1.6.9, in the working copy, snip $ time /usr/sbin/svn ls . /tmp/ls.txt real0m5.745s user0m4.281s sys 0m1.653s $ tail /tmp/ls.txt test0.txt test1.txt test2.txt test3.txt test4.txt test5.txt test6.txt test7.txt test8.txt test9.txt $ time /usr/sbin/svn ls --xml . /tmp/ls.txt ### Waits infinitely with no meaningful output ### /snip With svn 1.4.2, in the working copy, snip $ time svn ls . /tmp/ls.txt real0m2.866s user0m1.890s sys 0m0.681s $ tail /tmp/ls.txt no output $ time svn ls --xml . /tmp/ls.txt real0m3.047s user0m1.842s sys 0m0.680s $ tail /tmp/ls.txt ?xml version=1.0? lists list path=. /list /lists /snip With svn 1.6.9, in the repository, snip $ time /usr/sbin/svn ls file:///tmp/repos /tmp/ls.txt real0m4.656s user0m1.686s sys 0m1.470s $ tail /tmp/ls.txt test0.txt test1.txt test2.txt test3.txt test4.txt test5.txt test6.txt test7.txt test8.txt test9.txt $ time /usr/sbin/svn ls --xml file:///tmp/repos /tmp/ls.txt real160m44.583s user113m42.129s sys 2m7.358s $ tail /tmp/ls.txt nametest9.txt/name size0/size commit revision=1 authorroot/author date2010-07-11T15:09:10.029363Z/date /commit /entry /list /lists Top stats as follows 29657 ssenthil 25 0 565m 418m 2896 R 36.6 20.8 23:33.42 svn 30838 ssenthil 25 0 217m 70m 2840 R 38.3 3.5 3:17.07 svn /snip With svn 1.4.2, in the repository, snip $ time svn ls file:///tmp/repos /tmp/ls.txt real0m2.849s user0m1.429s sys 0m0.177s [ssent...@cu041 tmp]$ tail /tmp/ls.txt test0.txt test1.txt test2.txt test3.txt test4.txt test5.txt test6.txt test7.txt test8.txt test9.txt $ time svn ls --xml file:///tmp/repos /tmp/ls.txt real0m27.708s user0m17.528s sys 0m7.224s $ tail /tmp/ls.txt nametest9.txt/name size0/size commit revision=1 authorssenthil/author date2010-07-03T03:48:04.471139Z/date /commit /entry /list /lists /snip Looking at the numbers furnished above, it is clear that subversion does real work of getting the directory listing in plain and in xml format without any issues. But the real problem is the time taken to generate output for '--xml', not to mention 1.4.2 version is faster than 1.6.9 in this aspect. Am looking at the cause of this behavior, meanwhile would like to post it here to find out if it rings some bell for someone :) Thank You. -- Senthil Kumaran S http://www.stylesen.org/ slow-execution.sh Description: Bourne shell script
Non-matching revision arguments in the diff code
Hi! The diff callbacks for handling text have revision arguments. The prop callbacks don't. I'm creating diff headers for the cases when we only have property changes. When creating those diff headers I need revisions. Since the prop diff callback doesn't have revision arguments I'm using the revision fields in struct diff_cmd_baton. Here's what the doc string says about those fields. [[[ /* These are the numeric representations of the revisions passed to svn_client_diff5, either may be SVN_INVALID_REVNUM. We need these because some of the svn_wc_diff_callbacks4_t don't get revision arguments. ### Perhaps we should change the callback signatures and eliminate ### these? */ svn_revnum_t revnum1; svn_revnum_t revnum2; ]]] But they don't match the revisions returned by the diff callbacks. Here's the output from diff_content_changed() comparing what the baton fields says and what the revision argument says (from diff_tests 32): $ svn di -r 1 | grep DBG DBG: diff.c: 692: btn-rev1 1, rev1 0, btn-revn2 -1, rev2 3 DBG: diff.c: 692: btn-rev1 1, rev1 0, btn-revn2 -1, rev2 3 I was under the impression that a revision is something fixed (after we've done the peg revision dance). Any suggestion to why the revisions may differ? Sidenote: svn diff -r PREV:BASE shows 'working copy' as revision in the diff label. I'm thinking 'working copy' must involve the working changes. Thanks, Daniel
Re: [PATCH] Add svnrdump
Blair Zajac venit, vidit, dixit 09.07.2010 02:42: On 07/08/2010 01:17 AM, Daniel Shahaf wrote: @Bert: could you please trim quoted patches to only the relevant parts? Scrolling is tedious when I don't have have line folding available... +1 on this, in Thunderbird, it took a while to scan through the whole email to see the comments. plug That is what the QuoteCollapse extension to Thunderbird was invented for. ;) /plug In fact, on the git list we tend to trim as little as possible. Michael
Re: svn commit: r961756 - /subversion/trunk/subversion/tests/cmdline/copy_tests.py
rhuij...@apache.org writes: Author: rhuijben Date: Thu Jul 8 13:13:52 2010 New Revision: 961756 URL: http://svn.apache.org/viewvc?rev=961756view=rev Log: * subversion/tests/cmdline/copy_tests.py (move_dir_containing_file): Following up on r961397, enable status call in test using the entry_rev property. The log message refers to the wrong test, and doesn't mention the newly added test either. -- Philip
Re: 'svn patch' issue
On Fri, Jul 09, 2010 at 12:47:05AM +0300, Daniel Shahaf wrote: Working on #3641. I had a changelist containing svnsync_tests.py, two dumpfiles (for the test), and libsvn_repos/replay.c. I saved a diff of the whole CL as ../i3.diff. I 'svn revert'ed replay.c and rebuilt. I then ran 'svn patch ../i3.diff'. Result: one hunk was forcefully re-applied, even though the test file hadn't changed between the 'diff' and the 'patch': Can you hack up a reproduction script? Thanks. Stefan [[[ Index: cmdline/svnsync_tests.py === --- cmdline/svnsync_tests.py (revision 959212) +++ cmdline/svnsync_tests.py (working copy) @@ -689,6 +689,11 @@ def url_encoding(sbox): test url encoding issues run_test(sbox, url-encoding-bug.dump) +# issue #3641 +def descend_into_replace(sbox): + descending into replaced dir looks in src + run_test(sbox, descend_into_replace.dump, subdir='/trunk/H', + exp_dump_file_name = descend_into_replace.expected.dump) # A test for copying revisions that lack a property that already exists # on the destination rev as part of the commit (i.e. svn:author in this @@ -775,6 +780,11 @@ def commit_a_copy_of_root(sbox): #Testcase for issue 3438. run_test(sbox, repo_with_copy_of_root_dir.dump) +# issue #3641 +def descend_into_replace(sbox): + descending into replaced dir looks in src + run_test(sbox, descend_into_replace.dump, subdir='/trunk/H', + exp_dump_file_name = descend_into_replace.expected.dump) # Run the tests @@ -815,6 +825,7 @@ test_list = [ None, copy_bad_line_endings, delete_svn_props, commit_a_copy_of_root, + XFail(descend_into_replace), ] if __name__ == '__main__': ]]] -- printf(Eh???/n);
Re: 'svn patch' issue
Stefan Sperling wrote on Fri, 9 Jul 2010 at 11:43 +0200: On Fri, Jul 09, 2010 at 12:47:05AM +0300, Daniel Shahaf wrote: Working on #3641. I had a changelist containing svnsync_tests.py, two dumpfiles (for the test), and libsvn_repos/replay.c. I saved a diff of the whole CL as ../i3.diff. I 'svn revert'ed replay.c and rebuilt. I then ran 'svn patch ../i3.diff'. Result: one hunk was forcefully re-applied, even though the test file hadn't changed between the 'diff' and the 'patch': Can you hack up a reproduction script? Thanks. Sure, inlined. I haven't narrowed it down to a minimal example file, would you prefer me to do that? [[[ #!/bin/sh URL=http://svn.apache.org/repos/asf/subversion/trunk/subversion/tests/cmdline svnadmin=${svnadmin-svnadmin} svn=${svn-svn} test -e before || $svn cat $URL/svnsync_tests...@r962376 before test -e after || $svn cat $URL/svnsync_tests...@r962377 after rm -rf repos wc $svnadmin create repos $svn co -q file://`pwd`/repos wc cp before wc/iota $svn add wc/iota $svn ci -m add unpatched version -q wc cat after wc/iota # svnversion wc/iota == 1M # no changes between 'diff' and 'patch', so hope for idempotency: # for 'patch' to realize the patch is applied and does nothing. cd wc $svn diff ../svn-diff $svn patch ../svn-diff cd .. diff -u before after | sed 's/^/EXPECTED: /' diff -u before wc/iota | sed 's/^/ACTUAL: /' ]]] Stefan [[[ Index: cmdline/svnsync_tests.py === --- cmdline/svnsync_tests.py(revision 959212) +++ cmdline/svnsync_tests.py(working copy) @@ -689,6 +689,11 @@ def url_encoding(sbox): test url encoding issues run_test(sbox, url-encoding-bug.dump) +# issue #3641 +def descend_into_replace(sbox): + descending into replaced dir looks in src + run_test(sbox, descend_into_replace.dump, subdir='/trunk/H', + exp_dump_file_name = descend_into_replace.expected.dump) # A test for copying revisions that lack a property that already exists # on the destination rev as part of the commit (i.e. svn:author in this @@ -775,6 +780,11 @@ def commit_a_copy_of_root(sbox): #Testcase for issue 3438. run_test(sbox, repo_with_copy_of_root_dir.dump) +# issue #3641 +def descend_into_replace(sbox): + descending into replaced dir looks in src + run_test(sbox, descend_into_replace.dump, subdir='/trunk/H', + exp_dump_file_name = descend_into_replace.expected.dump) # Run the tests @@ -815,6 +825,7 @@ test_list = [ None, copy_bad_line_endings, delete_svn_props, commit_a_copy_of_root, + XFail(descend_into_replace), ] if __name__ == '__main__': ]]]
Re: Non-matching revision arguments in the diff code
On Fri, 2010-07-09 at 09:52 +0200, Daniel Näslund wrote: Hi! The diff callbacks for handling text have revision arguments. The prop callbacks don't. I'm creating diff headers for the cases when we only have property changes. When creating those diff headers I need revisions. Since the prop diff callback doesn't have revision arguments I'm using the revision fields in struct diff_cmd_baton. Here's what the doc string says about those fields. [[[ /* These are the numeric representations of the revisions passed to svn_client_diff5, either may be SVN_INVALID_REVNUM. We need these because some of the svn_wc_diff_callbacks4_t don't get revision arguments. ### Perhaps we should change the callback signatures and eliminate ### these? */ svn_revnum_t revnum1; svn_revnum_t revnum2; ]]] But they don't match the revisions returned by the diff callbacks. Here's the output from diff_content_changed() comparing what the baton fields says and what the revision argument says (from diff_tests 32): $ svn di -r 1 | grep DBG DBG: diff.c: 692: btn-rev1 1, rev1 0, btn-revn2 -1, rev2 3 DBG: diff.c: 692: btn-rev1 1, rev1 0, btn-revn2 -1, rev2 3 Hi Daniel. For rev2, it looks like the caller is providing SVN_INVALID_REVNUM to mean HEAD, and then something is discovering the current value of HEAD is 3, but is not updating btn-revn2 with that information. I think, if possible, the code that discovers the actual HEAD revision number should store it in the baton so that everything else can refer to it. If this discovery happens on the client side, that should be possible. - Julian I was under the impression that a revision is something fixed (after we've done the peg revision dance). Any suggestion to why the revisions may differ? Sidenote: svn diff -r PREV:BASE shows 'working copy' as revision in the diff label. I'm thinking 'working copy' must involve the working changes. Thanks, Daniel
Re: Slow execution with needs lock property
Senthil Kumaran S sent...@collab.net writes: In a repository that contains a large number of files and folders (over 100K), many of which have the needs-lock property set, If any user attempts to check out this entire repository, the large number of locks created on the server render the server almost unusable due to high load and cause the 'svn ls --xml' command to fail. I think this is issue 3661. -- Philip
Re: [PATCH] Add svnrdump
Hi Daniel, Daniel Shahaf writes: + /* Use a temporary file to measure the text-content-length */ + apr_err = apr_temp_dir_get(tempdir, hb-pool); svn_io_temp_dir() Fixed. + if (apr_err != APR_SUCCESS) +SVN_ERR(svn_error_wrap_apr(apr_err, NULL)); + + hb-temp_filepath = apr_psprintf(eb-pool, %s/svn-fe-XX, tempdir); os.path.join() Err, I mean, svn_dirent_join(). Fixed. Thanks :) -- Ram
Re: [PATCH] Add svnrdump
Hi, Bert Huijben writes: + /* Cleanup */ + SVN_ERR(svn_io_file_close(hb-temp_file, hb-pool)); + SVN_ERR(svn_stream_close(hb-temp_filestream)); The standard handler already closes the stream for you and if you don't disown the file on mapping, this also closes the file. + svn_pool_destroy(hb-pool); And as you clear the pool that contains the file and stream here, closing yourself is not necessary. I realize this, but I closed the stream anyway to make debugging easier. The pool is only destroyed much later. Do you think this is bad policy? -- Ram
Re: [PATCH] Add svnrdump
Hi, Ramkumar Ramachandra writes: Where is svn_dirent_basename defined? I can't seem to find it in the codebase at all. np, I found it. It's svn_relpath_basename. -- Ram
[PATCH v2] Add svnrdump
Hi, Along with the changes suggested by Bert and Daniel, this new version includes a few small bugfixes and feature additions contributed by David and Will, among others. Unfortunately, a diff of the changes made is not available due to whitespace/ style conversion issues: please check the recent commits on my GitHub repository for a summary of these changes: ra-svn branch of http://github.com/artagnon/svn-dump-fast-export Thanks. -- Ram 88--- Index: svnrdump/dump_editor.c === --- svnrdump/dump_editor.c (revision 0) +++ svnrdump/dump_editor.c (working copy) @@ -0,0 +1,690 @@ +/* + * + *Licensed to the Apache Software Foundation (ASF) under one + *or more contributor license agreements. See the NOTICE file + *distributed with this work for additional information + *regarding copyright ownership. The ASF licenses this file + *to you under the Apache License, Version 2.0 (the + *License); you may not use this file except in compliance + *with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + *Unless required by applicable law or agreed to in writing, + *software distributed under the License is distributed on an + *AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + *KIND, either express or implied. See the License for the + *specific language governing permissions and limitations + *under the License. + * + */ + +#include svn_pools.h +#include svn_repos.h +#include svn_path.h +#include svn_props.h +#include svn_dirent_uri.h + +#include svnrdump.h +#include dump_editor.h + +#define ARE_VALID_COPY_ARGS(p,r) ((p) SVN_IS_VALID_REVNUM(r)) + +/* Make a directory baton to represent the directory was path + (relative to EDIT_BATON's path) is PATH. + + CMP_PATH/CMP_REV are the path/revision against which this directory + should be compared for changes. If either is omitted (NULL for the + path, SVN_INVALID_REVNUM for the rev), just compare this directory + PATH against itself in the previous revision. + + PARENT_DIR_BATON is the directory baton of this directory's parent, + or NULL if this is the top-level directory of the edit. ADDED + indicated if this directory is newly added in this revision. + Perform all allocations in POOL. */ +static struct dir_baton * +make_dir_baton(const char *path, + const char *cmp_path, + svn_revnum_t cmp_rev, + void *edit_baton, + void *parent_dir_baton, + svn_boolean_t added, + apr_pool_t *pool) +{ + struct dump_edit_baton *eb = edit_baton; + struct dir_baton *pb = parent_dir_baton; + struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); + const char *full_path; + apr_array_header_t *compose_path = apr_array_make(pool, 2, + sizeof(const char *)); + + /* A path relative to nothing? I don't think so. */ + SVN_ERR_ASSERT_NO_RETURN(!path || pb); + + /* Construct the full path of this node. */ + if (pb) { +APR_ARRAY_PUSH(compose_path, const char *) = /; +APR_ARRAY_PUSH(compose_path, const char *) = path; +full_path = svn_path_compose(compose_path, pool); + } + else +full_path = apr_pstrdup(pool, /); + + /* Remove leading slashes from copyfrom paths. */ + if (cmp_path) +cmp_path = ((*cmp_path == '/') ? cmp_path + 1 : cmp_path); + + new_db-eb = eb; + new_db-parent_dir_baton = pb; + new_db-path = full_path; + new_db-cmp_path = cmp_path ? apr_pstrdup(pool, cmp_path) : NULL; + new_db-cmp_rev = cmp_rev; + new_db-added = added; + new_db-written_out = FALSE; + new_db-deleted_entries = apr_hash_make(pool); + new_db-pool = pool; + + return new_db; +} +/* + * Write out a node record for PATH of type KIND under EB-FS_ROOT. + * ACTION describes what is happening to the node (see enum svn_node_action). + * Write record to writable EB-STREAM, using EB-BUFFER to write in chunks. + * + * If the node was itself copied, IS_COPY is TRUE and the + * path/revision of the copy source are in CMP_PATH/CMP_REV. If + * IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part + * of a copied subtree. + */ +static svn_error_t * +dump_node(struct dump_edit_baton *eb, + const char *path,/* an absolute path. */ + svn_node_kind_t kind, + enum svn_node_action action, + const char *cmp_path, + svn_revnum_t cmp_rev, + apr_pool_t *pool) +{ + /* Write out metadata headers for this file node. */ + SVN_ERR(svn_stream_printf(eb-stream, pool, + SVN_REPOS_DUMPFILE_NODE_PATH : %s\n, + (*path == '/') ? path + 1 : path)); + + if (kind
Re: Bug: svnserve fail to detect it is already running
On Fri, Jul 09, 2010 at 09:36:05AM -0500, Peter Samuelson wrote: [Stefan Sperling] This doesn't make any sense. I don't understand how an OS can allow a user process to break a system service simply by binding a socket to the same port. And yet ... http://msdn.microsoft.com/en-us/library/ms740621%28VS.85%29.aspx Once the second socket has successfully bound, the behavior for all sockets bound to that port is indeterminate. For example, if all of the sockets on the same port provide TCP service, any incoming TCP connection requests over the port cannot be guaranteed to be handled by the correct socket — the behavior is non-deterministic. Gasp! That's just crazy. From that article, it appears SO_REUSEADDR means something rather different in Windows than in Unix. Likewise, an active TCP port is a port that is currently in one of the following states: ESTABLISHED, FIN_WAIT, FIN_WAIT_2, or LAST_ACK. Ports without SO_EXCLUSIVEADDRUSE set may be reused as soon as the socket on which bind was previously called is closed. So here's how I interpret the situation: WindowsUnixMeaning --- defaultSO_REUSEADDRCan reuse a socket immediately after you close it, without waiting for the other end of the connection to close (or time out) SO_EXCLUSIVEADDR default Cannot reuse a socket until both ends of all connections have closed them (or timed out) SO_REUSEADDR not possibleUnsafe! Of course what we want is the first case. As far as I can tell there is little we can do to secure svnserve against this attack on Windows systems other than Server 2003, because APR won't let us set the SO_EXCLUSIVEADDR option. apr_socket_t is opaque so there seems to be no way svnserve can get at the actual socket. Not setting SO_REUSEADDR on Windows should at least fix the problem for Windows Server 2003 (patch below). Maybe we should rewrite svnserve to use the native socket API directly? I wonder how the Apache httpd server deals with this problem... Index: subversion/svnserve/main.c === --- subversion/svnserve/main.c (revision 962479) +++ subversion/svnserve/main.c (working copy) @@ -729,9 +729,18 @@ return svn_cmdline_handle_exit_error(err, pool, svnserve: ); } +#ifndef WIN32 /* Prevents socket in use errors when server is killed and quickly - * restarted. */ + * restarted. + * + * We must not set the SO_REUSEADDR socket option on Windows because + * it allows denial-of-service attacks against svnserve. + * http://msdn.microsoft.com/en-us/library/ms740621%28VS.85%29.aspx + * + * Unfortunately, APR does not support the SO_EXCLUSIVEADDRUSE option. + */ apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1); +#endif status = apr_socket_bind(sock, sa); if (status)
Re: Bug: svnserve fail to detect it is already running
On Fri, 2010-07-09 at 11:44 -0400, Stefan Sperling wrote: As far as I can tell there is little we can do to secure svnserve against this attack on Windows systems other than Server 2003, because APR won't let us set the SO_EXCLUSIVEADDR option. That's okay, we don't want the SO_EXCLUSIVEADDR behavior. We want the default behavior under Windows, which corresponds to the SO_REUSEADDR behavior under Unix.
Re: Bug: svnserve fail to detect it is already running
[Greg Hudson] That's okay, we don't want the SO_EXCLUSIVEADDR behavior. We want the default behavior under Windows, which corresponds to the SO_REUSEADDR behavior under Unix. Well, the attack Stefan is referring to is if a third-party app (aka virus) tries to bind with SO_REUSEADDR. Prior to Windows Server 2003, this will succeed in messing up svnserve, unless you use SO_EXCLUSIVEADDR. This seems to have been corrected in Windows Server 2003. -- Peter Samuelson | org-tld!p12n!peter | http://p12n.org/
Re: Bug: svnserve fail to detect it is already running
[Stefan Sperling] Before the SO_EXCLUSIVEADDRUSE socket option was introduced, there was very little a network application developer could do to prevent a malicious program from binding to the port on which the network application had its own sockets bound. So not using SO_EXCLUSIVEADDR means the denial-of-service still works? Well, the same article describes the changes made in Windows Server 2003: now this seems to be true only if the malicious app is running as the same user as svnserve. -- Peter Samuelson | org-tld!p12n!peter | http://p12n.org/
Re: Bug: svnserve fail to detect it is already running
On Fri, Jul 09, 2010 at 11:40:59AM -0500, Peter Samuelson wrote: [Stefan Sperling] Before the SO_EXCLUSIVEADDRUSE socket option was introduced, there was very little a network application developer could do to prevent a malicious program from binding to the port on which the network application had its own sockets bound. So not using SO_EXCLUSIVEADDR means the denial-of-service still works? Well, the same article describes the changes made in Windows Server 2003: now this seems to be true only if the malicious app is running as the same user as svnserve. Yes, Server 2003 should be OK without SO_EXCLUSIVEADDR. It's the older Windows systems that will still have problems, and I don't think we should be ignoring them (as much as I'd love it if everyone just ditched Windows for good). Stefan
Re: [PATCH] Add svnrdump
Michael J Gruber g...@drmicha.warpmail.net writes: Blair Zajac venit, vidit, dixit 09.07.2010 02:42: On 07/08/2010 01:17 AM, Daniel Shahaf wrote: @Bert: could you please trim quoted patches to only the relevant parts? Scrolling is tedious when I don't have have line folding available... +1 on this, in Thunderbird, it took a while to scan through the whole email to see the comments. plug That is what the QuoteCollapse extension to Thunderbird was invented for. ;) /plug In fact, on the git list we tend to trim as little as possible. What??? Perhaps we are on different git lists?
Re: CollabNet links in packages.html
On Fri, Jul 9, 2010 at 3:06 PM, Peter Samuelson pe...@p12n.org wrote: A user on IRC seemed to be confused at the lack of Windows binaries from tigris.org, a link we recently removed. Now the first link on the list of Windows binaries is CollabNet, but registration is required in order to download them. Greg and I think we should add (registration required) tags to the various CollabNet links on packages.html. Objections? I object, unless we intend to rigorously police all of the other links and keep the page up to date. BTW, the order of the links has not changed. They have always been alphabetical, both the list of OS and then within the list of downloads. So tigris was not first in the list before. The registration requirement is not new either, it has been required for 3 years or so. -- Thanks Mark Phippard http://markphip.blogspot.com/
Re: CollabNet links in packages.html
Mark Phippard wrote: On Fri, Jul 9, 2010 at 3:06 PM, Peter Samuelson pe...@p12n.org wrote: A user on IRC seemed to be confused at the lack of Windows binaries from tigris.org, a link we recently removed. Now the first link on the list of Windows binaries is CollabNet, but registration is required in order to download them. Greg and I think we should add (registration required) tags to the various CollabNet links on packages.html. Objections? I object, unless we intend to rigorously police all of the other links and keep the page up to date. I concur with Mark. - Julian