summaryrefslogtreecommitdiff
path: root/tools/client-side
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2015-03-18 13:33:26 +0000
committer <>2015-07-08 14:41:01 +0000
commitbb0ef45f7c46b0ae221b26265ef98a768c33f820 (patch)
tree98bae10dde41c746c51ae97ec4f879e330415aa7 /tools/client-side
parent239dfafe71711b2f4c43d7b90a1228d7bdc5195e (diff)
downloadsubversion-tarball-bb0ef45f7c46b0ae221b26265ef98a768c33f820.tar.gz
Imported from /home/lorry/working-area/delta_subversion-tarball/subversion-1.8.13.tar.gz.subversion-1.8.13
Diffstat (limited to 'tools/client-side')
-rw-r--r--tools/client-side/bash_completion68
-rwxr-xr-xtools/client-side/detach.py271
-rwxr-xr-xtools/client-side/mergeinfo-sanitizer.py319
-rw-r--r--tools/client-side/svn-bench/cl.h198
-rw-r--r--tools/client-side/svn-bench/client_errors.h97
-rw-r--r--tools/client-side/svn-bench/help-cmd.c94
-rw-r--r--tools/client-side/svn-bench/notify.c1045
-rw-r--r--tools/client-side/svn-bench/null-export-cmd.c346
-rw-r--r--tools/client-side/svn-bench/null-list-cmd.c169
-rw-r--r--tools/client-side/svn-bench/null-log-cmd.c243
-rw-r--r--tools/client-side/svn-bench/svn-bench.c954
-rw-r--r--tools/client-side/svn-bench/util.c92
-rwxr-xr-xtools/client-side/svn-ssl-fingerprints.sh2
-rwxr-xr-xtools/client-side/svn-viewspec.py2
-rwxr-xr-xtools/client-side/svnmucc/svnmucc-test.py359
-rw-r--r--tools/client-side/svnmucc/svnmucc.c1206
16 files changed, 3870 insertions, 1595 deletions
diff --git a/tools/client-side/bash_completion b/tools/client-side/bash_completion
index e45c3f6..eabc15c 100644
--- a/tools/client-side/bash_completion
+++ b/tools/client-side/bash_completion
@@ -479,7 +479,7 @@ _svn()
[[ $previous = '--extensions' || $previous = '-x' ]] && \
values="--unified --ignore-space-change \
- --ignore-all-space --ignore-eol-style"
+ --ignore-all-space --ignore-eol-style --show-c-functions"
[[ $previous = '--depth' ]] && \
values='empty files immediates infinity'
@@ -494,8 +494,8 @@ _svn()
# from svn help resolve
values='base working mine-full theirs-full'
else # checkout merge switch update
- # not implemented yet: mine-conflict theirs-conflict
- values='postpone base mine-full theirs-full edit launch'
+ values="postpone base mine-full theirs-full edit launch \
+ mine-conflict theirs-conflict"
fi
}
@@ -647,14 +647,6 @@ _svn()
[[ ${COMPREPLY} ]] && return 0
fi
- # force mandatory --accept option for 'resolve' command
- if [[ $cmd = 'resolve' && ! $acceptOpt ]]
- then
- COMPREPLY=( $( compgen -W '--accept' -- $cur ) )
- # force option now! others will be available on later completions
- return 0
- fi
-
# maximum number of additional arguments expected in various forms
case $cmd in
merge)
@@ -789,7 +781,7 @@ _svn()
# otherwise build possible options for the command
pOpts="--username --password --no-auth-cache --non-interactive \
- --trust-server-cert"
+ --trust-server-cert --force-interactive"
mOpts="-m --message -F --file --encoding --force-log --with-revprop"
rOpts="-r --revision"
qOpts="-q --quiet"
@@ -826,7 +818,8 @@ _svn()
;;
commit|ci)
cmdOpts="$mOpts $qOpts $nOpts --targets --editor-cmd $pOpts \
- --no-unlock $cOpts --keep-changelists"
+ --no-unlock $cOpts --keep-changelists \
+ --include-externals"
;;
copy|cp)
cmdOpts="$mOpts $rOpts $qOpts --editor-cmd $pOpts --parents \
@@ -840,7 +833,9 @@ _svn()
cmdOpts="$rOpts -x --extensions --diff-cmd --no-diff-deleted \
$nOpts $pOpts --force --old --new --notice-ancestry \
-c --change --summarize $cOpts --xml --git \
- --internal-diff --show-copies-as-adds"
+ --internal-diff --show-copies-as-adds \
+ --ignore-properties --properties-only --no-diff-added \
+ --patch-compatible"
;;
export)
cmdOpts="$rOpts $qOpts $pOpts $nOpts --force --native-eol \
@@ -859,7 +854,7 @@ _svn()
;;
list|ls)
cmdOpts="$rOpts -v --verbose -R --recursive $pOpts \
- --incremental --xml --depth"
+ --incremental --xml --depth --include-externals"
;;
lock)
cmdOpts="-m --message -F --file --encoding --force-log \
@@ -870,13 +865,13 @@ _svn()
--incremental --xml $qOpts -l --limit -c --change \
$gOpts --with-all-revprops --with-revprop --depth \
--diff --diff-cmd -x --extensions --internal-diff \
- --with-no-revprops"
+ --with-no-revprops --search --search-and"
;;
merge)
cmdOpts="$rOpts $nOpts $qOpts --force --dry-run --diff3-cmd \
$pOpts --ignore-ancestry -c --change -x --extensions \
--record-only --accept --reintegrate \
- --allow-mixed-revisions"
+ --allow-mixed-revisions -v --verbose"
;;
mergeinfo)
cmdOpts="$rOpts $pOpts --depth --show-revs -R --recursive"
@@ -886,10 +881,11 @@ _svn()
;;
move|mv|rename|ren)
cmdOpts="$mOpts $rOpts $qOpts --force --editor-cmd $pOpts \
- --parents"
+ --parents --allow-mixed-revisions"
;;
patch)
- cmdOpts="$qOpts $pOpts --dry-run --ignore-whitespace --reverse-diff --strip"
+ cmdOpts="$qOpts $pOpts --dry-run --ignore-whitespace \
+ --reverse-diff --strip"
;;
propdel|pdel|pd)
cmdOpts="$qOpts -R --recursive $rOpts $pOpts $cOpts \
@@ -902,13 +898,13 @@ _svn()
cmdOpts="$cmdOpts --revprop $rOpts"
;;
propget|pget|pg)
- cmdOpts="-v --verbose -R --recursive $rOpts --strict $pOpts $cOpts \
- --depth --xml"
+ cmdOpts="-v --verbose -R --recursive $rOpts --strict \
+ $pOpts $cOpts --depth --xml --show-inherited-props"
[[ $isRevProp || ! $prop ]] && cmdOpts="$cmdOpts --revprop"
;;
proplist|plist|pl)
cmdOpts="-v --verbose -R --recursive $rOpts --revprop $qOpts \
- $pOpts $cOpts --depth --xml"
+ $pOpts $cOpts --depth --xml --show-inherited-props"
;;
propset|pset|ps)
cmdOpts="$qOpts --targets -R --recursive \
@@ -1034,9 +1030,9 @@ _svnadmin ()
cur=${COMP_WORDS[COMP_CWORD]}
# Possible expansions, without pure-prefix abbreviations such as "h".
- cmds='crashtest create deltify dump help hotcopy list-dblogs \
- list-unused-dblogs load lslocks lstxns pack recover rmlocks \
- rmtxns setlog setrevprop setuuid upgrade verify --version'
+ cmds='crashtest create deltify dump freeze help hotcopy list-dblogs \
+ list-unused-dblogs load lock lslocks lstxns pack recover rmlocks \
+ rmtxns setlog setrevprop setuuid unlock upgrade verify --version'
if [[ $COMP_CWORD -eq 1 ]] ; then
COMPREPLY=( $( compgen -W "$cmds" -- $cur ) )
@@ -1045,7 +1041,8 @@ _svnadmin ()
# options that require a parameter
# note: continued lines must end '|' continuing lines must start '|'
- optsParam="-r|--revision|--parent-dir|--fs-type"
+ optsParam="-r|--revision|--parent-dir|--fs-type|-M|--memory-cache-size"
+ optsParam="$optsParam|-F|--file"
# if not typing an option, or if the previous option required a
# parameter, then fallback on ordinary filename expansion
@@ -1060,13 +1057,18 @@ _svnadmin ()
case ${COMP_WORDS[1]} in
create)
cmdOpts="--bdb-txn-nosync --bdb-log-keep --config-dir \
- --fs-type --pre-1.4-compatible --pre-1.5-compatible"
+ --fs-type --pre-1.4-compatible --pre-1.5-compatible \
+ --pre-1.6-compatible --compatible-version"
;;
deltify)
cmdOpts="-r --revision -q --quiet"
;;
dump)
- cmdOpts="-r --revision --incremental -q --quiet --deltas"
+ cmdOpts="-r --revision --incremental -q --quiet --deltas \
+ -M --memory-cache-size"
+ ;;
+ freeze)
+ cmdOpts="-F --file"
;;
help|h|\?)
cmdOpts="$cmds"
@@ -1076,7 +1078,11 @@ _svnadmin ()
;;
load)
cmdOpts="--ignore-uuid --force-uuid --parent-dir -q --quiet \
- --use-pre-commit-hook --use-post-commit-hook"
+ --use-pre-commit-hook --use-post-commit-hook \
+ --bypass-prop-validation -M --memory-cache-size"
+ ;;
+ lock|unlock)
+ cmdOpts="--bypass-hooks"
;;
recover)
cmdOpts="--wait"
@@ -1120,6 +1126,10 @@ _svnadmin ()
--help) cmdOpts=${cmdOpts/ -h / } ;;
-r) cmdOpts=${cmdOpts/ --revision / } ;;
--revision) cmdOpts=${cmdOpts/ -r / } ;;
+ -F) cmdOpts=${cmdOpts/ --file / } ;;
+ --file) cmdOpts=${cmdOpts/ -F / } ;;
+ -M) cmdOpts=${cmdOpts/ --memory-cache-size / } ;;
+ --memory-cache-size) cmdOpts=${cmdOpts/ --M / } ;;
esac
# skip next option if this one requires a parameter
diff --git a/tools/client-side/detach.py b/tools/client-side/detach.py
new file mode 100755
index 0000000..84c725a
--- /dev/null
+++ b/tools/client-side/detach.py
@@ -0,0 +1,271 @@
+#!/usr/bin/env python
+#
+# ====================================================================
+# 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.
+# ====================================================================
+
+# TODO: if this was part of core subversion, we'd have all sorts of nifty
+# checks, and could use a lot of existing code.
+
+import os
+import re
+import sys
+import shutil
+import sqlite3
+
+
+def usage():
+ print("""usage: %s WC_SRC TARGET
+
+Detatch the working copy subdirectory given by WC_SRC to TARGET. This is
+equivalent to copying WC_SRC to TARGET, but it inserts a new set of Subversion
+metadata into TARGET/.svn, making TARGET a proper independent working copy.
+""" % sys.argv[0])
+ sys.exit(1)
+
+
+def find_wcroot(wcdir):
+ wcroot = os.path.abspath(wcdir)
+ old_wcroot = ''
+ while wcroot != old_wcroot:
+ if os.path.exists(os.path.join(wcroot, '.svn', 'wc.db')):
+ return wcroot
+
+ old_wcroot = wcroot
+ wcroot = os.path.dirname(wcroot)
+
+ return None
+
+
+def migrate_sqlite(wc_src, target, wcroot):
+ src_conn = sqlite3.connect(os.path.join(wcroot, '.svn', 'wc.db'))
+ dst_conn = sqlite3.connect(os.path.join(target, '.svn', 'wc.db'))
+
+ local_relsrc = os.path.relpath(wc_src, wcroot)
+
+ src_c = src_conn.cursor()
+ dst_c = dst_conn.cursor()
+
+ # We're only going to attempt this if there are no locks or work queue
+ # items in the source database
+ ### This could probably be tightened up, but for now this suffices
+ src_c.execute('select count(*) from wc_lock')
+ count = int(src_c.fetchone()[0])
+ assert count == 0
+
+ src_c.execute('select count(*) from work_queue')
+ count = int(src_c.fetchone()[0])
+ assert count == 0
+
+ # Copy over the schema
+ src_c.execute('pragma user_version')
+ user_version = src_c.fetchone()[0]
+ # We only know how to handle format 29 working copies
+ assert user_version == 29
+ ### For some reason, sqlite doesn't like to parameterize the pragma statement
+ dst_c.execute('pragma user_version = %d' % user_version)
+
+ src_c.execute('select name, sql from sqlite_master')
+ for row in src_c:
+ if not row[0].startswith('sqlite_'):
+ dst_c.execute(row[1])
+
+ # Insert wcroot row
+ dst_c.execute('insert into wcroot (id, local_abspath) values (?, ?)',
+ (1, None))
+
+ # Copy repositories rows
+ ### Perhaps prune the repositories based upon the new NODES set?
+ src_c.execute('select * from repository')
+ for row in src_c:
+ dst_c.execute('insert into repository values (?, ?, ?)',
+ row)
+
+ # Copy the root node
+ src_c.execute('select * from nodes where local_relpath = ?',
+ (local_relsrc,))
+ row = list(src_c.fetchone())
+ row[1] = ''
+ row[3] = None
+ dst_c.execute('''insert into nodes values
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
+ ?, ?, ?, ?, ?, ?, ?, ?)''', row)
+
+ # Copy children nodes rows
+ src_c.execute('select * from nodes where local_relpath like ?',
+ (local_relsrc + '/%', ))
+ for row in src_c:
+ row = list(row)
+ row[1] = row[1][len(local_relsrc) + 1:]
+ row[3] = row[3][len(local_relsrc) + 1:]
+ dst_c.execute('''insert into nodes values
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
+ ?, ?, ?, ?, ?, ?, ?, ?)''',
+ row)
+
+ # Copy root actual_node
+ src_c.execute('select * from actual_node where local_relpath = ?',
+ (local_relsrc, ))
+ row = src_c.fetchone()
+ if row:
+ row = list(row)
+ row[1] = ''
+ row[2] = None
+ dst_c.execute('''insert into actual_node values
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', row)
+
+ src_c.execute('select * from actual_node where local_relpath like ?',
+ (local_relsrc + '/%', ))
+ for row in src_c:
+ row = list(row)
+ row[1] = row[1][len(local_relsrc) + 1:]
+ row[2] = row[2][len(local_relsrc) + 1:]
+ dst_c.execute('''insert into actual_node values
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', row)
+
+ # Hard to know which locks we care about, so just copy 'em all (there aren't
+ # likely to be many)
+ src_c.execute('select * from lock')
+ for row in src_c:
+ dst_c.execute('insert into locks values (?, ?, ?, ?, ?, ?)', row)
+
+ # EXTERNALS
+ src_c.execute('select * from externals where local_relpath = ?',
+ (local_relsrc, ))
+ row = src_c.fetchone()
+ if row:
+ row = list(row)
+ row[1] = ''
+ row[2] = None
+ dst_c.execute('''insert into externals values
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', row)
+
+ src_c.execute('select * from externals where local_relpath like ?',
+ (local_relsrc + '/%', ))
+ for row in src_c:
+ row = list(row)
+ row[1] = row[1][len(local_relsrc) + 1:]
+ row[2] = row[2][len(local_relsrc) + 1:]
+ dst_c.execute('''insert into externals values
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', row)
+
+ dst_conn.commit()
+ src_conn.close()
+ dst_conn.close()
+
+
+def migrate_pristines(wc_src, target, wcroot):
+ src_conn = sqlite3.connect(os.path.join(wcroot, '.svn', 'wc.db'))
+ dst_conn = sqlite3.connect(os.path.join(target, '.svn', 'wc.db'))
+
+ src_c = src_conn.cursor()
+ dst_c = dst_conn.cursor()
+
+ regex = re.compile('\$((?:md5 *)|(?:sha1))\$(.*)')
+ src_proot = os.path.join(wcroot, '.svn', 'pristine')
+ target_proot = os.path.join(target, '.svn', 'pristine')
+
+ checksums = {}
+
+ # Grab anything which needs a pristine
+ src_c.execute('''select checksum from nodes
+ union
+ select older_checksum from actual_node
+ union
+ select left_checksum from actual_node
+ union
+ select right_checksum from actual_node''')
+ for row in src_c:
+ if row[0]:
+ match = regex.match(row[0])
+ assert match
+
+ pristine = match.group(2)
+ if pristine in checksums:
+ checksums[pristine] += 1
+ else:
+ checksums[pristine] = 1
+
+ for pristine, count in checksums.items():
+ # Copy the pristines themselves over
+ pdir = os.path.join(target_proot, pristine[0:2])
+ if not os.path.exists(pdir):
+ os.mkdir(pdir)
+ path = os.path.join(pristine[0:2], pristine + '.svn-base')
+ if os.path.exists(os.path.join(target_proot, path)):
+ dst_c.execute
+ else:
+ shutil.copy2(os.path.join(src_proot, path),
+ os.path.join(target_proot, path))
+
+ src_c.execute('select size, md5_checksum from pristine where checksum=?',
+ ('$sha1$' + pristine, ) )
+ (size, md5) = src_c.fetchone()
+
+ # Insert a db row for the pristine
+ dst_c.execute('insert into pristine values (?, NULL, ?, ?, ?)',
+ ('$sha1$' + pristine, size, count, md5))
+
+ dst_conn.commit()
+ src_conn.close()
+ dst_conn.close()
+
+
+def migrate_metadata(wc_src, target, wcroot):
+ # Make paths
+ os.mkdir(os.path.join(target, '.svn'))
+ os.mkdir(os.path.join(target, '.svn', 'tmp'))
+ os.mkdir(os.path.join(target, '.svn', 'pristine'))
+ open(os.path.join(target, '.svn', 'format'), 'w').write('12')
+ open(os.path.join(target, '.svn', 'entries'), 'w').write('12')
+
+ # Two major bits: sqlite data and pristines
+ migrate_sqlite(wc_src, os.path.abspath(target), wcroot)
+ migrate_pristines(wc_src, target, wcroot)
+
+
+def main():
+ if len(sys.argv) < 3:
+ usage()
+
+ wc_src = os.path.normpath(sys.argv[1])
+ if not os.path.isdir(wc_src):
+ print("%s does not exist or is not a directory" % wc_src)
+ sys.exit(1)
+
+ target = os.path.normpath(sys.argv[2])
+ if os.path.exists(target):
+ print("Target '%s' already exists" % target)
+ sys.exit(1)
+
+ wcroot = find_wcroot(wc_src)
+ if not wcroot:
+ print("'%s' is not part of a working copy" % wc_src)
+ sys.exit(1)
+
+ # Use the OS to copy the subdirectory over to the target
+ shutil.copytree(wc_src, target)
+
+ # Now migrate the worky copy data
+ migrate_metadata(wc_src, target, wcroot)
+
+
+if __name__ == '__main__':
+ raise Exception("""This script is unfinished and not ready to be used on live data.
+ Trust us.""")
+ main()
diff --git a/tools/client-side/mergeinfo-sanitizer.py b/tools/client-side/mergeinfo-sanitizer.py
new file mode 100755
index 0000000..54d415c
--- /dev/null
+++ b/tools/client-side/mergeinfo-sanitizer.py
@@ -0,0 +1,319 @@
+#!/usr/bin/env python
+#
+# ====================================================================
+# 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.
+# ====================================================================
+import svn
+import sys
+import os
+import getopt
+import hashlib
+import pickle
+import getpass
+from svn import client, core, ra, wc
+
+## This script first fetches the mergeinfo of the working copy and tries
+## to fetch the location segments for the source paths in the respective
+## revisions present in the mergeinfo. With the obtained location segments
+## result, it creates a new mergeinfo. The depth is infinity by default.
+## This script would stop proceeding if there are any local modifications in the
+## working copy.
+
+try:
+ my_getopt = getopt.gnu_getopt
+except AttributeError:
+ my_getopt = getopt.getopt
+mergeinfo = {}
+
+def usage():
+ sys.stderr.write(""" Usage: %s WCPATH [OPTION]
+
+Analyze the mergeinfo property of the given WCPATH.
+Look for the existence of merge_source's locations at their recorded
+merge ranges. If non-existent merge source is found fix the mergeinfo.
+
+Valid Options:
+ -f [--fix] : set the svn:mergeinfo property. Not committing the changes.
+ -h [--help] : display the usage
+
+""" % os.path.basename(sys.argv[0]) )
+
+
+##
+# This function would 'svn propset' the new mergeinfo to the working copy
+##
+def set_new_mergeinfo(wcpath, newmergeinfo, ctx):
+ client.propset3("svn:mergeinfo", newmergeinfo, wcpath, core.svn_depth_empty,
+ 0, core.SVN_INVALID_REVNUM, None, None, ctx)
+
+
+##
+# Returns the md5 hash of the file
+##
+def md5_of_file(f, block_size = 2*20):
+ md5 = hashlib.md5()
+ while True:
+ data = f.read(block_size)
+ if not data:
+ break
+ md5.update(data)
+ return md5.digest()
+
+
+
+def hasher(hash_file, newmergeinfo_file):
+ new_mergeinfo = core.svn_mergeinfo_to_string(mergeinfo)
+ with open(newmergeinfo_file, "a") as buffer_file:
+ pickle.dump(new_mergeinfo, buffer_file)
+ buffer_file.close()
+
+ with open(newmergeinfo_file, "rb") as buffer_file:
+ hash_of_buffer_file = md5_of_file(buffer_file)
+ buffer_file.close()
+
+ with open(hash_file, "w") as hash_file:
+ pickle.dump(hash_of_buffer_file, hash_file)
+ hash_file.close()
+
+
+def location_segment_callback(segment, pool):
+ if segment.path is not None:
+ source_path = '/' + segment.path
+ path_ranges = mergeinfo.get(source_path, [])
+ range = svn.core.svn_merge_range_t()
+ range.start = segment.range_start - 1
+ range.end = segment.range_end
+ range.inheritable = 1
+ path_ranges.append(range)
+ mergeinfo[source_path] = path_ranges
+
+##
+# This function does the authentication in an interactive way
+##
+def prompt_func_ssl_unknown_cert(realm, failures, cert_info, may_save, pool):
+ print "The certificate details are as follows:"
+ print "--------------------------------------"
+ print "Issuer : " + str(cert_info.issuer_dname)
+ print "Hostname : " + str(cert_info.hostname)
+ print "ValidFrom : " + str(cert_info.valid_from)
+ print "ValidUpto : " + str(cert_info.valid_until)
+ print "Fingerprint: " + str(cert_info.fingerprint)
+ print ""
+ ssl_trust = core.svn_auth_cred_ssl_server_trust_t()
+ if may_save:
+ choice = raw_input( "accept (t)temporarily (p)permanently: ")
+ else:
+ choice = raw_input( "(r)Reject or accept (t)temporarily: ")
+ if choice[0] == "t" or choice[0] == "T":
+ ssl_trust.may_save = False
+ ssl_trust.accepted_failures = failures
+ elif choice[0] == "p" or choice[0] == "P":
+ ssl_trust.may_save = True
+ ssl_trust.accepted_failures = failures
+ else:
+ ssl_trust = None
+ return ssl_trust
+
+def prompt_func_simple_prompt(realm, username, may_save, pool):
+ username = raw_input("username: ")
+ password = getpass.getpass(prompt="password: ")
+ simple_cred = core.svn_auth_cred_simple_t()
+ simple_cred.username = username
+ simple_cred.password = password
+ simple_cred.may_save = False
+ return simple_cred
+
+##
+# This function tries to authenticate(if needed) and fetch the
+# location segments for the available mergeinfo and create a new
+# mergeinfo dictionary
+##
+def get_new_location_segments(parsed_original_mergeinfo, repo_root,
+ wcpath, ctx):
+
+ for path in parsed_original_mergeinfo:
+ full_url = repo_root + path
+ ra_callbacks = ra.callbacks_t()
+ ra_callbacks.auth_baton = core.svn_auth_open([
+ core.svn_auth_get_ssl_server_trust_file_provider(),
+ core.svn_auth_get_simple_prompt_provider(prompt_func_simple_prompt, 2),
+ core.svn_auth_get_ssl_server_trust_prompt_provider(prompt_func_ssl_unknown_cert),
+ svn.client.get_simple_provider(),
+ svn.client.get_username_provider()
+ ])
+ try:
+ ctx.config = core.svn_config_get_config(None)
+ ra_session = ra.open(full_url, ra_callbacks, None, ctx.config)
+
+ for revision_range in parsed_original_mergeinfo[path]:
+ try:
+ ra.get_location_segments(ra_session, "", revision_range.end,
+ revision_range.end, revision_range.start + 1, location_segment_callback)
+ except svn.core.SubversionException:
+ sys.stderr.write(" Could not find location segments for %s \n" % path)
+ except Exception, e:
+ sys.stderr.write("")
+
+
+def sanitize_mergeinfo(parsed_original_mergeinfo, repo_root, wcpath,
+ ctx, hash_file, newmergeinfo_file, temp_pool):
+ full_mergeinfo = {}
+ for entry in parsed_original_mergeinfo:
+ get_new_location_segments(parsed_original_mergeinfo[entry], repo_root, wcpath, ctx)
+ full_mergeinfo.update(parsed_original_mergeinfo[entry])
+
+ hasher(hash_file, newmergeinfo_file)
+ diff_mergeinfo = core.svn_mergeinfo_diff(full_mergeinfo,
+ mergeinfo, 1, temp_pool)
+ #There should be no mergeinfo added by our population. There should only
+ #be deletion of mergeinfo. so take it from diff_mergeinfo[0]
+ print "The bogus mergeinfo summary:"
+ bogus_mergeinfo_deleted = diff_mergeinfo[0]
+ for bogus_mergeinfo_path in bogus_mergeinfo_deleted:
+ sys.stdout.write(bogus_mergeinfo_path + ": ")
+ for revision_range in bogus_mergeinfo_deleted[bogus_mergeinfo_path]:
+ sys.stdout.write(str(revision_range.start + 1) + "-" + str(revision_range.end) + ",")
+ print ""
+
+##
+# This function tries to 'propset the new mergeinfo into the working copy.
+# It reads the new mergeinfo from the .newmergeinfo file and verifies its
+# hash against the hash in the .hashfile
+##
+def fix_sanitized_mergeinfo(parsed_original_mergeinfo, repo_root, wcpath,
+ ctx, hash_file, newmergeinfo_file, temp_pool):
+ has_local_modification = check_local_modifications(wcpath, temp_pool)
+ old_hash = ''
+ new_hash = ''
+ try:
+ with open(hash_file, "r") as f:
+ old_hash = pickle.load(f)
+ f.close
+ except IOError, e:
+ get_new_location_segments(parsed_original_mergeinfo, repo_root, wcpath, ctx)
+ hasher(hash_file, newmergeinfo_file)
+ try:
+ with open(hash_file, "r") as f:
+ old_hash = pickle.load(f)
+ f.close
+ except IOError:
+ hasher(hash_file, newmergeinfo_file)
+ try:
+ with open(newmergeinfo_file, "r") as f:
+ new_hash = md5_of_file(f)
+ f.close
+ except IOError, e:
+ if not mergeinfo:
+ get_new_location_segments(parsed_original_mergeinfo, repo_root, wcpath, ctx)
+ hasher(hash_file, newmergeinfo_file)
+ with open(newmergeinfo_file, "r") as f:
+ new_hash = md5_of_file(f)
+ f.close
+ if old_hash == new_hash:
+ with open(newmergeinfo_file, "r") as f:
+ newmergeinfo = pickle.load(f)
+ f.close
+ set_new_mergeinfo(wcpath, newmergeinfo, ctx)
+ if os.path.exists(newmergeinfo_file):
+ os.remove(newmergeinfo_file)
+ os.remove(hash_file)
+ else:
+ print "The hashes are not matching. Probable chance of unwanted tweaking in the mergeinfo"
+
+
+##
+# This function checks the working copy for any local modifications
+##
+def check_local_modifications(wcpath, temp_pool):
+ has_local_mod = wc.svn_wc_revision_status(wcpath, None, 0, None, temp_pool)
+ if has_local_mod.modified:
+ print """The working copy has local modifications. Please revert them or clean
+the working copy before running the script."""
+ sys.exit(1)
+
+def get_original_mergeinfo(wcpath, revision, depth, ctx, temp_pool):
+ propget_list = client.svn_client_propget3("svn:mergeinfo", wcpath,
+ revision, revision, depth, None,
+ ctx, temp_pool)
+
+ pathwise_mergeinfo = ""
+ pathwise_mergeinfo_list = []
+ mergeinfo_catalog = propget_list[0]
+ mergeinfo_catalog_dict = {}
+ for entry in mergeinfo_catalog:
+ mergeinfo_catalog_dict[entry] = core.svn_mergeinfo_parse(mergeinfo_catalog[entry], temp_pool)
+ return mergeinfo_catalog_dict
+
+
+def main():
+ try:
+ opts, args = my_getopt(sys.argv[1:], "h?f", ["help", "fix"])
+ except Exception, e:
+ sys.stderr.write(""" Improperly used """)
+ sys.exit(1)
+
+ if len(args) == 1:
+ wcpath = args[0]
+ wcpath = os.path.abspath(wcpath)
+ else:
+ usage()
+ sys.exit(1)
+
+ fix = 0
+ current_path = os.getcwd()
+ hash_file = os.path.join(current_path, ".hashfile")
+ newmergeinfo_file = os.path.join(current_path, ".newmergeinfo")
+
+ temp_pool = core.svn_pool_create()
+ ctx = client.svn_client_create_context(temp_pool)
+ depth = core.svn_depth_infinity
+ revision = core.svn_opt_revision_t()
+ revision.kind = core.svn_opt_revision_unspecified
+
+ for opt, values in opts:
+ if opt == "--help" or opt in ("-h", "-?"):
+ usage()
+ elif opt == "--fix" or opt == "-f":
+ fix = 1
+
+ # Check for any local modifications in the working copy
+ check_local_modifications(wcpath, temp_pool)
+
+ parsed_original_mergeinfo = get_original_mergeinfo(wcpath, revision,
+ depth, ctx, temp_pool)
+
+ repo_root = client.svn_client_root_url_from_path(wcpath, ctx, temp_pool)
+
+ core.svn_config_ensure(None)
+
+ if fix == 0:
+ sanitize_mergeinfo(parsed_original_mergeinfo, repo_root, wcpath, ctx,
+ hash_file, newmergeinfo_file, temp_pool)
+ if fix == 1:
+ fix_sanitized_mergeinfo(parsed_original_mergeinfo, repo_root, wcpath,
+ ctx, hash_file, newmergeinfo_file, temp_pool)
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except KeyboardInterrupt:
+ print ""
+ sys.stderr.write("The script is interrupted and stopped manually.")
+ print ""
+
diff --git a/tools/client-side/svn-bench/cl.h b/tools/client-side/svn-bench/cl.h
new file mode 100644
index 0000000..7a1e48d
--- /dev/null
+++ b/tools/client-side/svn-bench/cl.h
@@ -0,0 +1,198 @@
+/*
+ * cl.h: shared stuff in the command line program
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#ifndef SVN_CL_H
+#define SVN_CL_H
+
+/*** Includes. ***/
+
+#include <apr_tables.h>
+
+#include "svn_client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Command dispatch. ***/
+
+/* Hold results of option processing that are shared by multiple
+ commands. */
+typedef struct svn_cl__opt_state_t
+{
+ /* An array of svn_opt_revision_range_t *'s representing revisions
+ ranges indicated on the command-line via the -r and -c options.
+ For each range in the list, if only one revision was provided
+ (-rN), its 'end' member remains 'svn_opt_revision_unspecified'.
+ This array always has at least one element, even if that is a
+ null range in which both ends are 'svn_opt_revision_unspecified'. */
+ apr_array_header_t *revision_ranges;
+
+ /* These are simply a copy of the range start and end values present
+ in the first item of the revision_ranges list. */
+ svn_opt_revision_t start_revision;
+ svn_opt_revision_t end_revision;
+
+ /* Flag which is only set if the '-c' option was used. */
+ svn_boolean_t used_change_arg;
+
+ /* Flag which is only set if the '-r' option was used. */
+ svn_boolean_t used_revision_arg;
+
+ /* Max number of log messages to get back from svn_client_log2. */
+ int limit;
+
+ /* After option processing is done, reflects the switch actually
+ given on the command line, or svn_depth_unknown if none. */
+ svn_depth_t depth;
+
+ svn_boolean_t quiet; /* sssh...avoid unnecessary output */
+ svn_boolean_t non_interactive; /* do no interactive prompting */
+ svn_boolean_t version; /* print version information */
+ svn_boolean_t verbose; /* be verbose */
+ svn_boolean_t strict; /* do strictly what was requested */
+ const char *encoding; /* the locale/encoding of the data*/
+ svn_boolean_t help; /* print usage message */
+ const char *auth_username; /* auth username */ /* UTF-8! */
+ const char *auth_password; /* auth password */ /* UTF-8! */
+ const char *extensions; /* subprocess extension args */ /* UTF-8! */
+ apr_array_header_t *targets; /* target list from file */ /* UTF-8! */
+ svn_boolean_t no_auth_cache; /* do not cache authentication information */
+ svn_boolean_t stop_on_copy; /* don't cross copies during processing */
+ const char *config_dir; /* over-riding configuration directory */
+ apr_array_header_t *config_options; /* over-riding configuration options */
+ svn_boolean_t all_revprops; /* retrieve all revprops */
+ svn_boolean_t no_revprops; /* retrieve no revprops */
+ apr_hash_t *revprop_table; /* table of revision properties to get/set */
+ svn_boolean_t use_merge_history; /* use/display extra merge information */
+ svn_boolean_t trust_server_cert; /* trust server SSL certs that would
+ otherwise be rejected as "untrusted" */
+} svn_cl__opt_state_t;
+
+
+typedef struct svn_cl__cmd_baton_t
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_client_ctx_t *ctx;
+} svn_cl__cmd_baton_t;
+
+
+/* Declare all the command procedures */
+svn_opt_subcommand_t
+ svn_cl__help,
+ svn_cl__null_export,
+ svn_cl__null_list,
+ svn_cl__null_log;
+
+
+/* See definition in main.c for documentation. */
+extern const svn_opt_subcommand_desc2_t svn_cl__cmd_table[];
+
+/* See definition in main.c for documentation. */
+extern const int svn_cl__global_options[];
+
+/* See definition in main.c for documentation. */
+extern const apr_getopt_option_t svn_cl__options[];
+
+
+/* A helper for the many subcommands that wish to merely warn when
+ * invoked on an unversioned, nonexistent, or otherwise innocuously
+ * errorful resource. Meant to be wrapped with SVN_ERR().
+ *
+ * If ERR is null, return SVN_NO_ERROR.
+ *
+ * Else if ERR->apr_err is one of the error codes supplied in varargs,
+ * then handle ERR as a warning (unless QUIET is true), clear ERR, and
+ * return SVN_NO_ERROR, and push the value of ERR->apr_err into the
+ * ERRORS_SEEN array, if ERRORS_SEEN is not NULL.
+ *
+ * Else return ERR.
+ *
+ * Typically, error codes like SVN_ERR_UNVERSIONED_RESOURCE,
+ * SVN_ERR_ENTRY_NOT_FOUND, etc, are supplied in varargs. Don't
+ * forget to terminate the argument list with SVN_NO_ERROR.
+ */
+svn_error_t *
+svn_cl__try(svn_error_t *err,
+ apr_array_header_t *errors_seen,
+ svn_boolean_t quiet,
+ ...);
+
+
+/* Our cancellation callback. */
+svn_error_t *
+svn_cl__check_cancel(void *baton);
+
+
+
+/*** Notification functions to display results on the terminal. */
+
+/* Set *NOTIFY_FUNC_P and *NOTIFY_BATON_P to a notifier/baton for all
+ * operations, allocated in POOL.
+ */
+svn_error_t *
+svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
+ void **notify_baton_p,
+ apr_pool_t *pool);
+
+/* Make the notifier for use with BATON print the appropriate summary
+ * line at the end of the output.
+ */
+svn_error_t *
+svn_cl__notifier_mark_export(void *baton);
+
+/* Like svn_client_args_to_target_array() but, if the only error is that some
+ * arguments are reserved file names, then print warning messages for those
+ * targets, store the rest of the targets in TARGETS_P and return success. */
+svn_error_t *
+svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_dest_origpath_on_truepath_collision,
+ apr_pool_t *pool);
+
+/* Return an error if TARGET is a URL; otherwise return SVN_NO_ERROR. */
+svn_error_t *
+svn_cl__check_target_is_local_path(const char *target);
+
+/* Return a copy of PATH, converted to the local path style, skipping
+ * PARENT_PATH if it is non-null and is a parent of or equal to PATH.
+ *
+ * This function assumes PARENT_PATH and PATH are both absolute "dirents"
+ * or both relative "dirents". */
+const char *
+svn_cl__local_style_skip_ancestor(const char *parent_path,
+ const char *path,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CL_H */
diff --git a/tools/client-side/svn-bench/client_errors.h b/tools/client-side/svn-bench/client_errors.h
new file mode 100644
index 0000000..19f0bdf
--- /dev/null
+++ b/tools/client-side/svn-bench/client_errors.h
@@ -0,0 +1,97 @@
+/*
+ * client_errors.h: error codes this command line client features
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#ifndef SVN_CLIENT_ERRORS_H
+#define SVN_CLIENT_ERRORS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * This error defining system is copied from and explained in
+ * ../../include/svn_error_codes.h
+ */
+
+/* Process this file if we're building an error array, or if we have
+ not defined the enumerated constants yet. */
+#if defined(SVN_ERROR_BUILD_ARRAY) || !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED)
+
+#if defined(SVN_ERROR_BUILD_ARRAY)
+
+#error "Need to update err_defn for r1464679 and un-typo 'CDMLINE'"
+
+#define SVN_ERROR_START \
+ static const err_defn error_table[] = { \
+ { SVN_ERR_CDMLINE__WARNING, "Warning" },
+#define SVN_ERRDEF(n, s) { n, s },
+#define SVN_ERROR_END { 0, NULL } };
+
+#elif !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED)
+
+#define SVN_ERROR_START \
+ typedef enum svn_client_errno_t { \
+ SVN_ERR_CDMLINE__WARNING = SVN_ERR_LAST + 1,
+#define SVN_ERRDEF(n, s) n,
+#define SVN_ERROR_END SVN_ERR_CMDLINE__ERR_LAST } svn_client_errno_t;
+
+#define SVN_CMDLINE_ERROR_ENUM_DEFINED
+
+#endif
+
+/* Define custom command line client error numbers */
+
+SVN_ERROR_START
+
+ /* BEGIN Client errors */
+
+SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_WRITE,
+ "Failed writing to temporary file.")
+
+ SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_STAT,
+ "Failed getting info about temporary file.")
+
+ SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_OPEN,
+ "Failed opening temporary file.")
+
+ /* END Client errors */
+
+
+SVN_ERROR_END
+
+#undef SVN_ERROR_START
+#undef SVN_ERRDEF
+#undef SVN_ERROR_END
+
+#endif /* SVN_ERROR_BUILD_ARRAY || !SVN_CMDLINE_ERROR_ENUM_DEFINED */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CLIENT_ERRORS_H */
diff --git a/tools/client-side/svn-bench/help-cmd.c b/tools/client-side/svn-bench/help-cmd.c
new file mode 100644
index 0000000..a3302ec
--- /dev/null
+++ b/tools/client-side/svn-bench/help-cmd.c
@@ -0,0 +1,94 @@
+/*
+ * help-cmd.c -- Provide help
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_version.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__help(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state;
+
+ /* xgettext: the %s is for SVN_VER_NUMBER. */
+ char help_header_template[] =
+ N_("usage: svn-bench <subcommand> [options] [args]\n"
+ "Subversion command-line client, version %s.\n"
+ "Type 'svn-bench help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svn-bench --version' to see the program version and RA modules\n"
+ " or 'svn-bench --version --quiet' to see just the version number.\n"
+ "\n"
+ "Most subcommands take file and/or directory arguments, recursing\n"
+ "on the directories. If no arguments are supplied to such a\n"
+ "command, it recurses on the current directory (inclusive) by default.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ char help_footer[] =
+ N_("Subversion is a tool for version control.\n"
+ "For additional information, see http://subversion.apache.org/\n");
+
+ char *help_header =
+ apr_psprintf(pool, _(help_header_template), SVN_VER_NUMBER);
+
+ const char *ra_desc_start
+ = _("The following repository access (RA) modules are available:\n\n");
+
+ svn_stringbuf_t *version_footer;
+
+ if (baton)
+ opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ else
+ opt_state = NULL;
+
+ version_footer = svn_stringbuf_create(ra_desc_start, pool);
+ SVN_ERR(svn_ra_print_modules(version_footer, pool));
+
+ return svn_opt_print_help4(os,
+ "svn-bench", /* ### erm, derive somehow? */
+ opt_state ? opt_state->version : FALSE,
+ opt_state ? opt_state->quiet : FALSE,
+ opt_state ? opt_state->verbose : FALSE,
+ version_footer->data,
+ help_header, /* already gettext()'d */
+ svn_cl__cmd_table,
+ svn_cl__options,
+ svn_cl__global_options,
+ _(help_footer),
+ pool);
+}
diff --git a/tools/client-side/svn-bench/notify.c b/tools/client-side/svn-bench/notify.c
new file mode 100644
index 0000000..5e19d8a
--- /dev/null
+++ b/tools/client-side/svn-bench/notify.c
@@ -0,0 +1,1045 @@
+/*
+ * notify.c: feedback handlers for cmdline client.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/* Baton for notify and friends. */
+struct notify_baton
+{
+ svn_boolean_t received_some_change;
+ svn_boolean_t is_checkout;
+ svn_boolean_t is_export;
+ svn_boolean_t is_wc_to_repos_copy;
+ svn_boolean_t sent_first_txdelta;
+ svn_boolean_t in_external;
+ svn_boolean_t had_print_error; /* Used to not keep printing error messages
+ when we've already had one print error. */
+
+ /* Conflict stats for update and merge. */
+ unsigned int text_conflicts;
+ unsigned int prop_conflicts;
+ unsigned int tree_conflicts;
+ unsigned int skipped_paths;
+ apr_hash_t *conflicted_paths;
+
+ /* The cwd, for use in decomposing absolute paths. */
+ const char *path_prefix;
+};
+
+
+/* Add a conflicted path to the list of conflicted paths stored
+ * in the notify baton. */
+static void
+add_conflicted_path(struct notify_baton *nb, const char *path)
+{
+ apr_hash_set(nb->conflicted_paths,
+ apr_pstrdup(apr_hash_pool_get(nb->conflicted_paths), path),
+ APR_HASH_KEY_STRING, "");
+}
+
+/* This implements `svn_wc_notify_func2_t'.
+ * NOTE: This function can't fail, so we just ignore any print errors. */
+static void
+notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
+{
+ struct notify_baton *nb = baton;
+ char statchar_buf[5] = " ";
+ const char *path_local;
+ svn_error_t *err;
+
+ if (n->url)
+ path_local = n->url;
+ else
+ {
+ if (n->path_prefix)
+ path_local = svn_cl__local_style_skip_ancestor(n->path_prefix, n->path,
+ pool);
+ else /* skip nb->path_prefix, if it's non-null */
+ path_local = svn_cl__local_style_skip_ancestor(nb->path_prefix, n->path,
+ pool);
+ }
+
+ switch (n->action)
+ {
+ case svn_wc_notify_skip:
+ nb->skipped_paths++;
+ if (n->content_state == svn_wc_notify_state_missing)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped missing target: '%s'\n"),
+ path_local)))
+ goto print_error;
+ }
+ else if (n->content_state == svn_wc_notify_state_source_missing)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped target: '%s' -- copy-source is missing\n"),
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped '%s'\n"), path_local)))
+ goto print_error;
+ }
+ break;
+ case svn_wc_notify_update_skip_obstruction:
+ nb->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- An obstructing working copy was found\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_skip_working_only:
+ nb->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Has no versioned parent\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_skip_access_denied:
+ nb->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Access denied\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_skip_conflicted:
+ nb->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Node remains in conflict\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_delete:
+ case svn_wc_notify_exclude:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "D %s\n", path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_broken_lock:
+ if ((err = svn_cmdline_printf(pool, "B %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_external_removed:
+ nb->received_some_change = TRUE;
+ if (n->err && n->err->message)
+ {
+ if ((err = svn_cmdline_printf(pool, "Removed external '%s': %s\n",
+ path_local, n->err->message)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "Removed external '%s'\n",
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_left_local_modifications:
+ if ((err = svn_cmdline_printf(pool, "Left local modifications as '%s'\n",
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_replace:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "R %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_add:
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ nb->text_conflicts++;
+ add_conflicted_path(nb, n->path);
+ if ((err = svn_cmdline_printf(pool, "C %s\n", path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "A %s\n", path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_exists:
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ nb->text_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[0] = 'C';
+ }
+ else
+ statchar_buf[0] = 'E';
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ nb->prop_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_merged)
+ statchar_buf[1] = 'G';
+
+ if ((err = svn_cmdline_printf(pool, "%s %s\n", statchar_buf, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_restore:
+ if ((err = svn_cmdline_printf(pool, _("Restored '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revert:
+ if ((err = svn_cmdline_printf(pool, _("Reverted '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_revert:
+ if (( err = svn_cmdline_printf(pool, _("Failed to revert '%s' -- "
+ "try updating instead.\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_resolved:
+ if ((err = svn_cmdline_printf(pool,
+ _("Resolved conflicted state of '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_add:
+ /* We *should* only get the MIME_TYPE if PATH is a file. If we
+ do get it, and the mime-type is not textual, note that this
+ is a binary addition. */
+ if (n->mime_type && (svn_mime_type_is_binary(n->mime_type)))
+ {
+ if ((err = svn_cmdline_printf(pool, "A (bin) %s\n",
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "A %s\n",
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_delete:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "D %s\n",
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_patch:
+ {
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ nb->text_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[0] = 'C';
+ }
+ else if (n->kind == svn_node_file)
+ {
+ if (n->content_state == svn_wc_notify_state_merged)
+ statchar_buf[0] = 'G';
+ else if (n->content_state == svn_wc_notify_state_changed)
+ statchar_buf[0] = 'U';
+ }
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ nb->prop_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_changed)
+ statchar_buf[1] = 'U';
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
+ {
+ if ((err = svn_cmdline_printf(pool, "%s %s\n",
+ statchar_buf, path_local)))
+ goto print_error;
+ }
+ }
+ break;
+
+ case svn_wc_notify_patch_applied_hunk:
+ nb->received_some_change = TRUE;
+ if (n->hunk_original_start != n->hunk_matched_line)
+ {
+ apr_uint64_t off;
+ const char *s;
+ const char *minus;
+
+ if (n->hunk_matched_line > n->hunk_original_start)
+ {
+ off = n->hunk_matched_line - n->hunk_original_start;
+ minus = "";
+ }
+ else
+ {
+ off = n->hunk_original_start - n->hunk_matched_line;
+ minus = "-";
+ }
+
+ /* ### We're creating the localized strings without
+ * ### APR_INT64_T_FMT since it isn't translator-friendly */
+ if (n->hunk_fuzz)
+ {
+
+ if (n->prop_name)
+ {
+ s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with offset %s");
+
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT
+ " and fuzz %lu (%s)\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->hunk_fuzz,
+ n->prop_name);
+ }
+ else
+ {
+ s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with offset %s");
+
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT
+ " and fuzz %lu\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->hunk_fuzz);
+ }
+
+ if (err)
+ goto print_error;
+ }
+ else
+ {
+
+ if (n->prop_name)
+ {
+ s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with offset %s");
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT" (%s)\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->prop_name);
+ }
+ else
+ {
+ s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with offset %s");
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT"\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off);
+ }
+
+ if (err)
+ goto print_error;
+ }
+ }
+ else if (n->hunk_fuzz)
+ {
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with fuzz %lu (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->hunk_fuzz,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with fuzz %lu\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->hunk_fuzz);
+ if (err)
+ goto print_error;
+
+ }
+ break;
+
+ case svn_wc_notify_patch_rejected_hunk:
+ nb->received_some_change = TRUE;
+
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> rejected hunk "
+ "## -%lu,%lu +%lu,%lu ## (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> rejected hunk "
+ "@@ -%lu,%lu +%lu,%lu @@\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_patch_hunk_already_applied:
+ nb->received_some_change = TRUE;
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> hunk "
+ "## -%lu,%lu +%lu,%lu ## "
+ "already applied (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> hunk "
+ "@@ -%lu,%lu +%lu,%lu @@ "
+ "already applied\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_update:
+ case svn_wc_notify_merge_record_info:
+ {
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ nb->text_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[0] = 'C';
+ }
+ else if (n->kind == svn_node_file)
+ {
+ if (n->content_state == svn_wc_notify_state_merged)
+ statchar_buf[0] = 'G';
+ else if (n->content_state == svn_wc_notify_state_changed)
+ statchar_buf[0] = 'U';
+ }
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ nb->prop_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_merged)
+ statchar_buf[1] = 'G';
+ else if (n->prop_state == svn_wc_notify_state_changed)
+ statchar_buf[1] = 'U';
+
+ if (n->lock_state == svn_wc_notify_lock_state_unlocked)
+ statchar_buf[2] = 'B';
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
+ nb->received_some_change = TRUE;
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' '
+ || statchar_buf[2] != ' ')
+ {
+ if ((err = svn_cmdline_printf(pool, "%s %s\n",
+ statchar_buf, path_local)))
+ goto print_error;
+ }
+ }
+ break;
+
+ case svn_wc_notify_update_external:
+ /* Remember that we're now "inside" an externals definition. */
+ nb->in_external = TRUE;
+
+ /* Currently this is used for checkouts and switches too. If we
+ want different output, we'll have to add new actions. */
+ if ((err = svn_cmdline_printf(pool,
+ _("\nFetching external item into '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_external:
+ /* If we are currently inside the handling of an externals
+ definition, then we can simply present n->err as a warning
+ and feel confident that after this, we aren't handling that
+ externals definition any longer. */
+ if (nb->in_external)
+ {
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ nb->in_external = FALSE;
+ if ((err = svn_cmdline_printf(pool, "\n")))
+ goto print_error;
+ }
+ /* Otherwise, we'll just print two warnings. Why? Because
+ svn_handle_warning2() only shows the single "best message",
+ but we have two pretty important ones: that the external at
+ '/some/path' didn't pan out, and then the more specific
+ reason why (from n->err). */
+ else
+ {
+ svn_error_t *warn_err =
+ svn_error_createf(SVN_ERR_BASE, NULL,
+ _("Error handling externals definition for '%s':"),
+ path_local);
+ svn_handle_warning2(stderr, warn_err, "svn: ");
+ svn_error_clear(warn_err);
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ }
+ break;
+
+ case svn_wc_notify_update_started:
+ if (! (nb->in_external ||
+ nb->is_checkout ||
+ nb->is_export))
+ {
+ if ((err = svn_cmdline_printf(pool, _("Updating '%s':\n"),
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_update_completed:
+ {
+ if (SVN_IS_VALID_REVNUM(n->revision))
+ {
+ if (nb->is_export)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Exported external at revision %ld.\n")
+ : _("Exported revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else if (nb->is_checkout)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Checked out external at revision %ld.\n")
+ : _("Checked out revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else
+ {
+ if (nb->received_some_change)
+ {
+ nb->received_some_change = FALSE;
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Updated external to revision %ld.\n")
+ : _("Updated to revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External at revision %ld.\n")
+ : _("At revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ }
+ }
+ else /* no revision */
+ {
+ if (nb->is_export)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External export complete.\n")
+ : _("Export complete.\n"))))
+ goto print_error;
+ }
+ else if (nb->is_checkout)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External checkout complete.\n")
+ : _("Checkout complete.\n"))))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External update complete.\n")
+ : _("Update complete.\n"))))
+ goto print_error;
+ }
+ }
+ }
+
+ if (nb->in_external)
+ {
+ nb->in_external = FALSE;
+ if ((err = svn_cmdline_printf(pool, "\n")))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_status_external:
+ if ((err = svn_cmdline_printf
+ (pool, _("\nPerforming status on external item at '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_status_completed:
+ if (SVN_IS_VALID_REVNUM(n->revision))
+ if ((err = svn_cmdline_printf(pool,
+ _("Status against revision: %6ld\n"),
+ n->revision)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_modified:
+ /* xgettext: Align the %s's on this and the following 4 messages */
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Sending copy of %s\n")
+ : _("Sending %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_added:
+ case svn_wc_notify_commit_copied:
+ if (n->mime_type && svn_mime_type_is_binary(n->mime_type))
+ {
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Adding copy of (bin) %s\n")
+ : _("Adding (bin) %s\n"),
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Adding copy of %s\n")
+ : _("Adding %s\n"),
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_commit_deleted:
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Deleting copy of %s\n")
+ : _("Deleting %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_replaced:
+ case svn_wc_notify_commit_copied_replaced:
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Replacing copy of %s\n")
+ : _("Replacing %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_postfix_txdelta:
+ if (! nb->sent_first_txdelta)
+ {
+ nb->sent_first_txdelta = TRUE;
+ if ((err = svn_cmdline_printf(pool,
+ _("Transmitting file data "))))
+ goto print_error;
+ }
+
+ if ((err = svn_cmdline_printf(pool, ".")))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_locked:
+ if ((err = svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
+ path_local, n->lock->owner)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_unlocked:
+ if ((err = svn_cmdline_printf(pool, _("'%s' unlocked.\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_lock:
+ case svn_wc_notify_failed_unlock:
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ break;
+
+ case svn_wc_notify_changelist_set:
+ if ((err = svn_cmdline_printf(pool, "A [%s] %s\n",
+ n->changelist_name, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_changelist_clear:
+ case svn_wc_notify_changelist_moved:
+ if ((err = svn_cmdline_printf(pool,
+ "D [%s] %s\n",
+ n->changelist_name, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_begin:
+ if (n->merge_range == NULL)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging differences between "
+ "repository URLs into '%s':\n"),
+ path_local);
+ else if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(pool, _("--- Merging r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging r%ld through r%ld into "
+ "'%s':\n"),
+ n->merge_range->start + 1,
+ n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging r%ld through r%ld "
+ "into '%s':\n"),
+ n->merge_range->start,
+ n->merge_range->end + 1, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_record_info_begin:
+ if (!n->merge_range)
+ {
+ err = svn_cmdline_printf(pool,
+ _("--- Recording mergeinfo for merge "
+ "between repository URLs into '%s':\n"),
+ path_local);
+ }
+ else
+ {
+ if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for merge of r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for reverse merge of r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for merge of r%ld through r%ld into '%s':\n"),
+ n->merge_range->start + 1, n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for reverse merge of r%ld through r%ld into '%s':\n"),
+ n->merge_range->start, n->merge_range->end + 1, path_local);
+ }
+
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_elide_info:
+ if ((err = svn_cmdline_printf(pool,
+ _("--- Eliding mergeinfo from '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_foreign_merge_begin:
+ if (n->merge_range == NULL)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging differences between "
+ "foreign repository URLs into '%s':\n"),
+ path_local);
+ else if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging (from foreign repository) "
+ "r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging (from foreign "
+ "repository) r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging (from foreign repository) "
+ "r%ld through r%ld into '%s':\n"),
+ n->merge_range->start + 1,
+ n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging (from foreign "
+ "repository) r%ld through r%ld into "
+ "'%s':\n"),
+ n->merge_range->start,
+ n->merge_range->end + 1, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_tree_conflict:
+ nb->tree_conflicts++;
+ add_conflicted_path(nb, n->path);
+ if ((err = svn_cmdline_printf(pool, " C %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_add:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " A %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_update:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " U %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_delete:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " D %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_modified:
+ case svn_wc_notify_property_added:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' set on '%s'\n"),
+ n->prop_name, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_deleted:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' deleted from '%s'.\n"),
+ n->prop_name, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_deleted_nonexistent:
+ err = svn_cmdline_printf(pool,
+ _("Attempting to delete nonexistent "
+ "property '%s' on '%s'\n"), n->prop_name,
+ path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revprop_set:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' set on repository revision %ld\n"),
+ n->prop_name, n->revision);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revprop_deleted:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' deleted from repository revision %ld\n"),
+ n->prop_name, n->revision);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_upgraded_path:
+ err = svn_cmdline_printf(pool, _("Upgraded '%s'\n"), path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_url_redirect:
+ err = svn_cmdline_printf(pool, _("Redirecting to URL '%s':\n"),
+ n->url);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_path_nonexistent:
+ err = svn_cmdline_printf(pool, _("'%s' is not under version control"),
+ path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_conflict_resolver_starting:
+ /* Once all operations invoke the interactive conflict resolution after
+ * they've completed, we can run svn_cl__print_conflict_stats() here. */
+ break;
+
+ case svn_wc_notify_conflict_resolver_done:
+ break;
+
+ default:
+ break;
+ }
+
+ if ((err = svn_cmdline_fflush(stdout)))
+ goto print_error;
+
+ return;
+
+ print_error:
+ /* If we had no errors before, print this error to stderr. Else, don't print
+ anything. The user already knows there were some output errors,
+ so there is no point in flooding her with an error per notification. */
+ if (!nb->had_print_error)
+ {
+ nb->had_print_error = TRUE;
+ /* Issue #3014:
+ * Don't print anything on broken pipes. The pipe was likely
+ * closed by the process at the other end. We expect that
+ * process to perform error reporting as necessary.
+ *
+ * ### This assumes that there is only one error in a chain for
+ * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
+ if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
+ svn_handle_error2(err, stderr, FALSE, "svn: ");
+ }
+ svn_error_clear(err);
+}
+
+
+svn_error_t *
+svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
+ void **notify_baton_p,
+ apr_pool_t *pool)
+{
+ struct notify_baton *nb = apr_pcalloc(pool, sizeof(*nb));
+
+ nb->received_some_change = FALSE;
+ nb->sent_first_txdelta = FALSE;
+ nb->is_checkout = FALSE;
+ nb->is_export = FALSE;
+ nb->is_wc_to_repos_copy = FALSE;
+ nb->in_external = FALSE;
+ nb->had_print_error = FALSE;
+ nb->text_conflicts = 0;
+ nb->prop_conflicts = 0;
+ nb->tree_conflicts = 0;
+ nb->skipped_paths = 0;
+ nb->conflicted_paths = apr_hash_make(pool);
+ SVN_ERR(svn_dirent_get_absolute(&nb->path_prefix, "", pool));
+
+ *notify_func_p = notify;
+ *notify_baton_p = nb;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__notifier_mark_export(void *baton)
+{
+ struct notify_baton *nb = baton;
+
+ nb->is_export = TRUE;
+ return SVN_NO_ERROR;
+}
diff --git a/tools/client-side/svn-bench/null-export-cmd.c b/tools/client-side/svn-bench/null-export-cmd.c
new file mode 100644
index 0000000..8220bfb
--- /dev/null
+++ b/tools/client-side/svn-bench/null-export-cmd.c
@@ -0,0 +1,346 @@
+/*
+ * export-cmd.c -- Subversion export command
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_cmdline.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+#include "private/svn_client_private.h"
+
+/*** The export editor code. ***/
+
+/* ---------------------------------------------------------------------- */
+
+/*** A dedicated 'export' editor, which does no .svn/ accounting. ***/
+
+typedef struct edit_baton_t
+{
+ apr_int64_t file_count;
+ apr_int64_t dir_count;
+ apr_int64_t byte_count;
+ apr_int64_t prop_count;
+ apr_int64_t prop_byte_count;
+} edit_baton_t;
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+
+/* Just ensure that the main export directory exists. */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ *root_baton = edit_baton;
+ return SVN_NO_ERROR;
+}
+
+
+/* Ensure the directory exists, and send feedback. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ edit_baton_t *eb = parent_baton;
+ eb->dir_count++;
+
+ *baton = parent_baton;
+ return SVN_NO_ERROR;
+}
+
+
+/* Build a file baton. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ edit_baton_t *eb = parent_baton;
+ eb->file_count++;
+
+ *baton = parent_baton;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ edit_baton_t *eb = baton;
+ if (window != NULL)
+ eb->byte_count += window->tview_len;
+
+ return SVN_NO_ERROR;
+}
+
+/* Write incoming data into the tmpfile stream */
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ *handler_baton = file_baton;
+ *handler = window_handler;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ edit_baton_t *eb = file_baton;
+ eb->prop_count++;
+ eb->prop_byte_count += value->len;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ edit_baton_t *eb = dir_baton;
+ eb->prop_count++;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+
+/*** Public Interfaces ***/
+
+static svn_error_t *
+bench_null_export(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ svn_opt_revision_t *peg_revision,
+ svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t quiet,
+ apr_pool_t *pool)
+{
+ svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
+ svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
+
+ SVN_ERR_ASSERT(peg_revision != NULL);
+ SVN_ERR_ASSERT(revision != NULL);
+
+ if (peg_revision->kind == svn_opt_revision_unspecified)
+ peg_revision->kind = svn_path_is_url(from_path_or_url)
+ ? svn_opt_revision_head
+ : svn_opt_revision_working;
+
+ if (revision->kind == svn_opt_revision_unspecified)
+ revision = peg_revision;
+
+ if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
+ {
+ svn_client__pathrev_t *loc;
+ svn_ra_session_t *ra_session;
+ svn_node_kind_t kind;
+
+ /* Get the RA connection. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ from_path_or_url, NULL,
+ peg_revision,
+ revision, ctx, pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
+
+ if (kind == svn_node_file)
+ {
+ apr_hash_t *props;
+
+ /* Since you cannot actually root an editor at a file, we
+ * manually drive a few functions of our editor. */
+
+ /* Step outside the editor-likeness for a moment, to actually talk
+ * to the repository. */
+ /* ### note: the stream will not be closed */
+ SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
+ svn_stream_empty(pool),
+ NULL, &props, pool));
+ }
+ else if (kind == svn_node_dir)
+ {
+ void *edit_baton = NULL;
+ const svn_delta_editor_t *export_editor = NULL;
+ const svn_ra_reporter3_t *reporter;
+ void *report_baton;
+
+ svn_delta_editor_t *editor = svn_delta_default_editor(pool);
+
+ editor->set_target_revision = set_target_revision;
+ editor->open_root = open_root;
+ editor->add_directory = add_directory;
+ editor->add_file = add_file;
+ editor->apply_textdelta = apply_textdelta;
+ editor->close_file = close_file;
+ editor->change_file_prop = change_file_prop;
+ editor->change_dir_prop = change_dir_prop;
+
+ /* for ra_svn, we don't need an editior in quiet mode */
+ if (!quiet || strncmp(loc->repos_root_url, "svn:", 4))
+ SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
+ ctx->cancel_baton,
+ editor,
+ baton,
+ &export_editor,
+ &edit_baton,
+ pool));
+
+ /* Manufacture a basic 'report' to the update reporter. */
+ SVN_ERR(svn_ra_do_update3(ra_session,
+ &reporter, &report_baton,
+ loc->rev,
+ "", /* no sub-target */
+ depth,
+ FALSE, /* don't want copyfrom-args */
+ FALSE, /* don't want ignore_ancestry */
+ export_editor, edit_baton,
+ pool, pool));
+
+ SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
+ /* Depth is irrelevant, as we're
+ passing start_empty=TRUE anyway. */
+ svn_depth_infinity,
+ TRUE, /* "help, my dir is empty!" */
+ NULL, pool));
+
+ SVN_ERR(reporter->finish_report(report_baton, pool));
+ }
+ else if (kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' doesn't exist"),
+ from_path_or_url);
+ }
+ /* kind == svn_node_unknown not handled */
+ }
+
+
+ if (result_rev)
+ *result_rev = edit_revision;
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__null_export(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *from;
+ apr_array_header_t *targets;
+ svn_error_t *err;
+ svn_opt_revision_t peg_revision;
+ const char *truefrom;
+ edit_baton_t eb = { 0 };
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* We want exactly 1 or 2 targets for this subcommand. */
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ /* The first target is the `from' path. */
+ from = APR_ARRAY_IDX(targets, 0, const char *);
+
+ /* Get the peg revision if present. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truefrom, from, pool));
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ /* Do the export. */
+ err = bench_null_export(NULL, truefrom, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->depth,
+ &eb,
+ ctx, opt_state->quiet, pool);
+
+ if (!opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("%15s directories\n"
+ "%15s files\n"
+ "%15s bytes in files\n"
+ "%15s properties\n"
+ "%15s bytes in properties\n"),
+ svn__ui64toa_sep(eb.dir_count, ',', pool),
+ svn__ui64toa_sep(eb.file_count, ',', pool),
+ svn__ui64toa_sep(eb.byte_count, ',', pool),
+ svn__ui64toa_sep(eb.prop_count, ',', pool),
+ svn__ui64toa_sep(eb.prop_byte_count, ',', pool)));
+
+ return svn_error_trace(err);
+}
diff --git a/tools/client-side/svn-bench/null-list-cmd.c b/tools/client-side/svn-bench/null-list-cmd.c
new file mode 100644
index 0000000..8aa08cd
--- /dev/null
+++ b/tools/client-side/svn-bench/null-list-cmd.c
@@ -0,0 +1,169 @@
+/*
+ * list-cmd.c -- list a URL
+ *
+ * ====================================================================
+ * 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_cmdline.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_opt.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+
+
+
+/* Baton used when printing directory entries. */
+struct print_baton {
+ svn_boolean_t verbose;
+ apr_int64_t directories;
+ apr_int64_t files;
+ apr_int64_t locks;
+ svn_client_ctx_t *ctx;
+};
+
+/* This implements the svn_client_list_func2_t API, printing a single
+ directory entry in text format. */
+static svn_error_t *
+print_dirent(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ const char *external_parent_url,
+ const char *external_target,
+ apr_pool_t *pool)
+{
+ struct print_baton *pb = baton;
+
+ if (pb->ctx->cancel_func)
+ SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
+
+ if (dirent->kind == svn_node_dir)
+ pb->directories++;
+ if (dirent->kind == svn_node_file)
+ pb->files++;
+ if (lock)
+ pb->locks++;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__null_list(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ int i;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_uint32_t dirent_fields;
+ struct print_baton pb = { FALSE };
+ svn_boolean_t seen_nonexistent_target = FALSE;
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ if (opt_state->verbose)
+ dirent_fields = SVN_DIRENT_ALL;
+ else
+ dirent_fields = SVN_DIRENT_KIND; /* the only thing we actually need... */
+
+ pb.ctx = ctx;
+ pb.verbose = opt_state->verbose;
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_immediates;
+
+ /* For each target, try to list it. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Get peg revisions. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ err = svn_client_list3(truepath, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->depth,
+ dirent_fields,
+ opt_state->verbose,
+ FALSE, /* include externals */
+ print_dirent,
+ &pb, ctx, subpool);
+
+ if (err)
+ {
+ /* If one of the targets is a non-existent URL or wc-entry,
+ don't bail out. Just warn and move on to the next target. */
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ svn_handle_warning2(stderr, err, "svn-bench: ");
+ else
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = NULL;
+ seen_nonexistent_target = TRUE;
+ }
+ else if (!opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("%15s directories\n"
+ "%15s files\n"
+ "%15s locks\n"),
+ svn__ui64toa_sep(pb.directories, ',', pool),
+ svn__ui64toa_sep(pb.files, ',', pool),
+ svn__ui64toa_sep(pb.locks, ',', pool)));
+ }
+
+ svn_pool_destroy(subpool);
+
+ if (seen_nonexistent_target)
+ return svn_error_create(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Could not list all targets because some targets don't exist"));
+ else
+ return SVN_NO_ERROR;
+}
diff --git a/tools/client-side/svn-bench/null-log-cmd.c b/tools/client-side/svn-bench/null-log-cmd.c
new file mode 100644
index 0000000..b35c8f2
--- /dev/null
+++ b/tools/client-side/svn-bench/null-log-cmd.c
@@ -0,0 +1,243 @@
+/*
+ * log-cmd.c -- Display log messages
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_STDIO
+#include <apr_want.h>
+
+#include "svn_cmdline.h"
+#include "svn_compat.h"
+#include "svn_path.h"
+#include "svn_props.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+
+
+/*** Code. ***/
+
+/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
+struct log_receiver_baton
+{
+ /* Client context. */
+ svn_client_ctx_t *ctx;
+
+ /* Level of merge revision nesting */
+ apr_size_t merge_depth;
+
+ /* collect counters? */
+ svn_boolean_t quiet;
+
+ /* total revision counters */
+ apr_int64_t revisions;
+ apr_int64_t changes;
+ apr_int64_t message_lines;
+
+ /* part that came from merges */
+ apr_int64_t merges;
+ apr_int64_t merged_revs;
+ apr_int64_t merged_changes;
+ apr_int64_t merged_message_lines;
+};
+
+
+/* Implement `svn_log_entry_receiver_t', printing the logs in
+ * a human-readable and machine-parseable format.
+ *
+ * BATON is of type `struct log_receiver_baton'.
+ */
+static svn_error_t *
+log_entry_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_receiver_baton *lb = baton;
+ const char *author;
+ const char *date;
+ const char *message;
+
+ if (lb->ctx->cancel_func)
+ SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
+
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ lb->merge_depth--;
+ return SVN_NO_ERROR;
+ }
+
+ /* if we don't want counters, we are done */
+ if (lb->quiet)
+ return SVN_NO_ERROR;
+
+ /* extract the message and do all the other counting */
+ svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+ if (log_entry->revision == 0 && message == NULL)
+ return SVN_NO_ERROR;
+
+ lb->revisions++;
+ if (lb->merge_depth)
+ lb->merged_revs++;
+
+ if (message != NULL)
+ {
+ int count = svn_cstring_count_newlines(message) + 1;
+ lb->message_lines += count;
+ if (lb->merge_depth)
+ lb->merged_message_lines += count;
+ }
+
+ if (log_entry->changed_paths2)
+ {
+ unsigned count = apr_hash_count(log_entry->changed_paths2);
+ lb->changes += count;
+ if (lb->merge_depth)
+ lb->merged_changes += count;
+ }
+
+ if (log_entry->has_children)
+ {
+ lb->merge_depth++;
+ lb->merges++;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__null_log(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ struct log_receiver_baton lb = { 0 };
+ const char *target;
+ int i;
+ apr_array_header_t *revprops;
+ svn_opt_revision_t target_peg_revision;
+ const char *target_path_or_url;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ /* Determine if they really want a two-revision range. */
+ if (opt_state->used_change_arg)
+ {
+ if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1)
+ {
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("-c and -r are mutually exclusive"));
+ }
+ for (i = 0; i < opt_state->revision_ranges->nelts; i++)
+ {
+ svn_opt_revision_range_t *range;
+ range = APR_ARRAY_IDX(opt_state->revision_ranges, i,
+ svn_opt_revision_range_t *);
+ if (range->start.value.number < range->end.value.number)
+ range->start.value.number++;
+ else
+ range->end.value.number++;
+ }
+ }
+
+ /* Parse the first target into path-or-url and peg revision. */
+ target = APR_ARRAY_IDX(targets, 0, const char *);
+ SVN_ERR(svn_opt_parse_path(&target_peg_revision, &target_path_or_url,
+ target, pool));
+ if (target_peg_revision.kind == svn_opt_revision_unspecified)
+ target_peg_revision.kind = (svn_path_is_url(target)
+ ? svn_opt_revision_head
+ : svn_opt_revision_working);
+ APR_ARRAY_IDX(targets, 0, const char *) = target_path_or_url;
+
+ if (svn_path_is_url(target))
+ {
+ for (i = 1; i < targets->nelts; i++)
+ {
+ target = APR_ARRAY_IDX(targets, i, const char *);
+
+ if (svn_path_is_url(target) || target[0] == '/')
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Only relative paths can be specified"
+ " after a URL for 'svn-bench log', "
+ "but '%s' is not a relative path"),
+ target);
+ }
+ }
+
+ lb.ctx = ctx;
+ lb.quiet = opt_state->quiet;
+
+ revprops = apr_array_make(pool, 3, sizeof(char *));
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+ if (!opt_state->quiet)
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+ SVN_ERR(svn_client_log5(targets,
+ &target_peg_revision,
+ opt_state->revision_ranges,
+ opt_state->limit,
+ opt_state->verbose,
+ opt_state->stop_on_copy,
+ opt_state->use_merge_history,
+ revprops,
+ log_entry_receiver,
+ &lb,
+ ctx,
+ pool));
+
+ if (!opt_state->quiet)
+ {
+ if (opt_state->use_merge_history)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("%15s revisions, %15s merged in %s merges\n"
+ "%15s msg lines, %15s in merged revisions\n"
+ "%15s changes, %15s in merged revisions\n"),
+ svn__ui64toa_sep(lb.revisions, ',', pool),
+ svn__ui64toa_sep(lb.merged_revs, ',', pool),
+ svn__ui64toa_sep(lb.merges, ',', pool),
+ svn__ui64toa_sep(lb.message_lines, ',', pool),
+ svn__ui64toa_sep(lb.merged_message_lines, ',', pool),
+ svn__ui64toa_sep(lb.changes, ',', pool),
+ svn__ui64toa_sep(lb.merged_changes, ',', pool)));
+ else
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("%15s revisions\n"
+ "%15s msg lines\n"
+ "%15s changes\n"),
+ svn__ui64toa_sep(lb.revisions, ',', pool),
+ svn__ui64toa_sep(lb.message_lines, ',', pool),
+ svn__ui64toa_sep(lb.changes, ',', pool)));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/tools/client-side/svn-bench/svn-bench.c b/tools/client-side/svn-bench/svn-bench.c
new file mode 100644
index 0000000..bf8964e
--- /dev/null
+++ b/tools/client-side/svn-bench/svn-bench.c
@@ -0,0 +1,954 @@
+/*
+ * main.c: Subversion command line client.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <assert.h>
+
+#include <apr_signal.h>
+
+#include "svn_cmdline.h"
+#include "svn_dirent_uri.h"
+#include "svn_pools.h"
+#include "svn_utf.h"
+#include "svn_version.h"
+
+#include "cl.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Option Processing ***/
+
+/* Add an identifier here for long options that don't have a short
+ option. Options that have both long and short options should just
+ use the short option letter as identifier. */
+typedef enum svn_cl__longopt_t {
+ opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID,
+ opt_auth_username,
+ opt_config_dir,
+ opt_config_options,
+ opt_depth,
+ opt_no_auth_cache,
+ opt_non_interactive,
+ opt_stop_on_copy,
+ opt_strict,
+ opt_targets,
+ opt_version,
+ opt_with_revprop,
+ opt_with_all_revprops,
+ opt_with_no_revprops,
+ opt_trust_server_cert
+} svn_cl__longopt_t;
+
+
+/* Option codes and descriptions for the command line client.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ */
+const apr_getopt_option_t svn_cl__options[] =
+{
+ {"help", 'h', 0, N_("show help on a subcommand")},
+ {NULL, '?', 0, N_("show help on a subcommand")},
+ {"quiet", 'q', 0, N_("print nothing, or only summary information")},
+ {"recursive", 'R', 0, N_("descend recursively, same as --depth=infinity")},
+ {"non-recursive", 'N', 0, N_("obsolete; try --depth=files or --depth=immediates")},
+ {"change", 'c', 1,
+ N_("the change made by revision ARG (like -r ARG-1:ARG)\n"
+ " "
+ "If ARG is negative this is like -r ARG:ARG-1\n"
+ " "
+ "If ARG is of the form ARG1-ARG2 then this is like\n"
+ " "
+ "ARG1:ARG2, where ARG1 is inclusive")},
+ {"revision", 'r', 1,
+ N_("ARG (some commands also take ARG1:ARG2 range)\n"
+ " "
+ "A revision argument can be one of:\n"
+ " "
+ " NUMBER revision number\n"
+ " "
+ " '{' DATE '}' revision at start of the date\n"
+ " "
+ " 'HEAD' latest in repository\n"
+ " "
+ " 'BASE' base rev of item's working copy\n"
+ " "
+ " 'COMMITTED' last commit at or before BASE\n"
+ " "
+ " 'PREV' revision just before COMMITTED")},
+ {"version", opt_version, 0, N_("show program version information")},
+ {"verbose", 'v', 0, N_("print extra information")},
+ {"username", opt_auth_username, 1, N_("specify a username ARG")},
+ {"password", opt_auth_password, 1, N_("specify a password ARG")},
+ {"targets", opt_targets, 1,
+ N_("pass contents of file ARG as additional args")},
+ {"depth", opt_depth, 1,
+ N_("limit operation by depth ARG ('empty', 'files',\n"
+ " "
+ "'immediates', or 'infinity')")},
+ {"strict", opt_strict, 0, N_("use strict semantics")},
+ {"stop-on-copy", opt_stop_on_copy, 0,
+ N_("do not cross copies while traversing history")},
+ {"no-auth-cache", opt_no_auth_cache, 0,
+ N_("do not cache authentication tokens")},
+ {"trust-server-cert", opt_trust_server_cert, 0,
+ N_("accept SSL server certificates from unknown\n"
+ " "
+ "certificate authorities without prompting (but only\n"
+ " "
+ "with '--non-interactive')") },
+ {"non-interactive", opt_non_interactive, 0,
+ N_("do no interactive prompting")},
+ {"config-dir", opt_config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+ {"config-option", opt_config_options, 1,
+ N_("set user configuration option in the format:\n"
+ " "
+ " FILE:SECTION:OPTION=[VALUE]\n"
+ " "
+ "For example:\n"
+ " "
+ " servers:global:http-library=serf")},
+ {"limit", 'l', 1, N_("maximum number of log entries")},
+ {"with-all-revprops", opt_with_all_revprops, 0,
+ N_("retrieve all revision properties")},
+ {"with-no-revprops", opt_with_no_revprops, 0,
+ N_("retrieve no revision properties")},
+ {"with-revprop", opt_with_revprop, 1,
+ N_("set revision property ARG in new revision\n"
+ " "
+ "using the name[=value] format")},
+ {"use-merge-history", 'g', 0,
+ N_("use/display additional information from merge\n"
+ " "
+ "history")},
+
+ /* Long-opt Aliases
+ *
+ * These have NULL desriptions, but an option code that matches some
+ * other option (whose description should probably mention its aliases).
+ */
+
+ {0, 0, 0, 0},
+};
+
+
+
+/*** Command dispatch. ***/
+
+/* Our array of available subcommands.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ *
+ * In most of the help text "PATH" is used where a working copy path is
+ * required, "URL" where a repository URL is required and "TARGET" when
+ * either a path or a url can be used. Hmm, should this be part of the
+ * help text?
+ */
+
+/* Options that apply to all commands. (While not every command may
+ currently require authentication or be interactive, allowing every
+ command to take these arguments allows scripts to just pass them
+ willy-nilly to every invocation of 'svn') . */
+const int svn_cl__global_options[] =
+{ opt_auth_username, opt_auth_password, opt_no_auth_cache, opt_non_interactive,
+ opt_trust_server_cert, opt_config_dir, opt_config_options, 0
+};
+
+const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
+{
+ { "help", svn_cl__help, {"?", "h"}, N_
+ ("Describe the usage of this program or its subcommands.\n"
+ "usage: help [SUBCOMMAND...]\n"),
+ {0} },
+ /* This command is also invoked if we see option "--help", "-h" or "-?". */
+
+ { "null-export", svn_cl__null_export, {0}, N_
+ ("Create an unversioned copy of a tree.\n"
+ "usage: null-export [-r REV] URL[@PEGREV]\n"
+ "\n"
+ " Exports a clean directory tree from the repository specified by\n"
+ " URL, at revision REV if it is given, otherwise at HEAD.\n"
+ "\n"
+ " If specified, PEGREV determines in which revision the target is first\n"
+ " looked up.\n"),
+ {'r', 'q', 'N', opt_depth} },
+
+ { "null-list", svn_cl__null_list, {"ls"}, N_
+ ("List directory entries in the repository.\n"
+ "usage: list [TARGET[@REV]...]\n"
+ "\n"
+ " List each TARGET file and the contents of each TARGET directory as\n"
+ " they exist in the repository. If TARGET is a working copy path, the\n"
+ " corresponding repository URL will be used. If specified, REV determines\n"
+ " in which revision the target is first looked up.\n"
+ "\n"
+ " The default TARGET is '.', meaning the repository URL of the current\n"
+ " working directory.\n"
+ "\n"
+ " With --verbose, the following fields will be fetched for each item:\n"
+ "\n"
+ " Revision number of the last commit\n"
+ " Author of the last commit\n"
+ " If locked, the letter 'O'. (Use 'svn info URL' to see details)\n"
+ " Size (in bytes)\n"
+ " Date and time of the last commit\n"),
+ {'r', 'v', 'q', 'R', opt_depth} },
+
+ { "null-log", svn_cl__null_log, {0}, N_
+ ("Fetch the log messages for a set of revision(s) and/or path(s).\n"
+ "usage: 1. null-log [PATH][@REV]\n"
+ " 2. null-log URL[@REV] [PATH...]\n"
+ "\n"
+ " 1. Fetch the log messages for the URL corresponding to PATH\n"
+ " (default: '.'). If specified, REV is the revision in which the\n"
+ " URL is first looked up, and the default revision range is REV:1.\n"
+ " If REV is not specified, the default revision range is BASE:1,\n"
+ " since the URL might not exist in the HEAD revision.\n"
+ "\n"
+ " 2. Fetch the log messages for the PATHs (default: '.') under URL.\n"
+ " If specified, REV is the revision in which the URL is first\n"
+ " looked up, and the default revision range is REV:1; otherwise,\n"
+ " the URL is looked up in HEAD, and the default revision range is\n"
+ " HEAD:1.\n"
+ "\n"
+ " Multiple '-c' or '-r' options may be specified (but not a\n"
+ " combination of '-c' and '-r' options), and mixing of forward and\n"
+ " reverse ranges is allowed.\n"
+ "\n"
+ " With -v, also print all affected paths with each log message.\n"
+ " With -q, don't print the log message body itself (note that this is\n"
+ " compatible with -v).\n"
+ "\n"
+ " Each log message is printed just once, even if more than one of the\n"
+ " affected paths for that revision were explicitly requested. Logs\n"
+ " follow copy history by default. Use --stop-on-copy to disable this\n"
+ " behavior, which can be useful for determining branchpoints.\n"),
+ {'r', 'q', 'v', 'g', 'c', opt_targets, opt_stop_on_copy,
+ 'l', opt_with_all_revprops, opt_with_no_revprops, opt_with_revprop,
+ 'x',},
+ {{opt_with_revprop, N_("retrieve revision property ARG")},
+ {'c', N_("the change made in revision ARG")}} },
+
+ { NULL, NULL, {0}, NULL, {0} }
+};
+
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_client", svn_client_version },
+ { "svn_wc", svn_wc_version },
+ { "svn_ra", svn_ra_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+/* A flag to see if we've been cancelled by the client or not. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+/* A signal handler to support cancellation. */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+/* Our cancellation callback. */
+svn_error_t *
+svn_cl__check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/*** Main. ***/
+
+/* Report and clear the error ERR, and return EXIT_FAILURE. */
+#define EXIT_ERROR(err) \
+ svn_cmdline_handle_exit_error(err, NULL, "svn: ")
+
+/* A redefinition of the public SVN_INT_ERR macro, that suppresses the
+ * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR. */
+#undef SVN_INT_ERR
+#define SVN_INT_ERR(expr) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ return EXIT_ERROR(svn_err__temp); \
+ } while (0)
+
+static int
+sub_main(int argc, const char *argv[], apr_pool_t *pool)
+{
+ svn_error_t *err;
+ int opt_id;
+ apr_getopt_t *os;
+ svn_cl__opt_state_t opt_state = { 0, { 0 } };
+ svn_client_ctx_t *ctx;
+ apr_array_header_t *received_opts;
+ int i;
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ svn_cl__cmd_baton_t command_baton;
+ svn_auth_baton_t *ab;
+ svn_config_t *cfg_config;
+ svn_boolean_t descend = TRUE;
+ svn_boolean_t use_notifier = TRUE;
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ /* Check library versions */
+ SVN_INT_ERR(check_lib_versions());
+
+#if defined(WIN32) || defined(__CYGWIN__)
+ /* Set the working copy administrative directory name. */
+ if (getenv("SVN_ASP_DOT_NET_HACK"))
+ {
+ SVN_INT_ERR(svn_wc_set_adm_dir("_svn", pool));
+ }
+#endif
+
+ /* Initialize the RA library. */
+ SVN_INT_ERR(svn_ra_initialize(pool));
+
+ /* Begin processing arguments. */
+ opt_state.start_revision.kind = svn_opt_revision_unspecified;
+ opt_state.end_revision.kind = svn_opt_revision_unspecified;
+ opt_state.revision_ranges =
+ apr_array_make(pool, 0, sizeof(svn_opt_revision_range_t *));
+ opt_state.depth = svn_depth_unknown;
+
+ /* No args? Show usage. */
+ if (argc <= 1)
+ {
+ SVN_INT_ERR(svn_cl__help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+
+ /* Else, parse options. */
+ SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
+
+ os->interleave = 1;
+ while (1)
+ {
+ const char *opt_arg;
+ const char *utf8_opt_arg;
+
+ /* Parse the next option. */
+ apr_status_t apr_err = apr_getopt_long(os, svn_cl__options, &opt_id,
+ &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ SVN_INT_ERR(svn_cl__help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id) {
+ case 'l':
+ {
+ err = svn_cstring_atoi(&opt_state.limit, opt_arg);
+ if (err)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Non-numeric limit argument given"));
+ return EXIT_ERROR(err);
+ }
+ if (opt_state.limit <= 0)
+ {
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Argument to --limit must be positive"));
+ return EXIT_ERROR(err);
+ }
+ }
+ break;
+ case 'c':
+ {
+ apr_array_header_t *change_revs =
+ svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, pool);
+
+ for (i = 0; i < change_revs->nelts; i++)
+ {
+ char *end;
+ svn_revnum_t changeno, changeno_end;
+ const char *change_str =
+ APR_ARRAY_IDX(change_revs, i, const char *);
+ const char *s = change_str;
+ svn_boolean_t is_negative;
+
+ /* Check for a leading minus to allow "-c -r42".
+ * The is_negative flag is used to handle "-c -42" and "-c -r42".
+ * The "-c r-42" case is handled by strtol() returning a
+ * negative number. */
+ is_negative = (*s == '-');
+ if (is_negative)
+ s++;
+
+ /* Allow any number of 'r's to prefix a revision number. */
+ while (*s == 'r')
+ s++;
+ changeno = changeno_end = strtol(s, &end, 10);
+ if (end != s && *end == '-')
+ {
+ if (changeno < 0 || is_negative)
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Negative number in range (%s)"
+ " not supported with -c"),
+ change_str);
+ return EXIT_ERROR(err);
+ }
+ s = end + 1;
+ while (*s == 'r')
+ s++;
+ changeno_end = strtol(s, &end, 10);
+ }
+ if (end == change_str || *end != '\0')
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Non-numeric change argument (%s) "
+ "given to -c"), change_str);
+ return EXIT_ERROR(err);
+ }
+
+ if (changeno == 0)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("There is no change 0"));
+ return EXIT_ERROR(err);
+ }
+
+ if (is_negative)
+ changeno = -changeno;
+
+ /* Figure out the range:
+ -c N -> -r N-1:N
+ -c -N -> -r N:N-1
+ -c M-N -> -r M-1:N for M < N
+ -c M-N -> -r M:N-1 for M > N
+ -c -M-N -> error (too confusing/no valid use case)
+ */
+ if (changeno > 0)
+ {
+ if (changeno <= changeno_end)
+ changeno--;
+ else
+ changeno_end--;
+ }
+ else
+ {
+ changeno = -changeno;
+ changeno_end = changeno - 1;
+ }
+
+ opt_state.used_change_arg = TRUE;
+ APR_ARRAY_PUSH(opt_state.revision_ranges,
+ svn_opt_revision_range_t *)
+ = svn_opt__revision_range_from_revnums(changeno, changeno_end,
+ pool);
+ }
+ }
+ break;
+ case 'r':
+ opt_state.used_revision_arg = TRUE;
+ if (svn_opt_parse_revision_to_range(opt_state.revision_ranges,
+ opt_arg, pool) != 0)
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ err = svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error in revision argument '%s'"),
+ utf8_opt_arg);
+ return EXIT_ERROR(err);
+ }
+ break;
+ case 'v':
+ opt_state.verbose = TRUE;
+ break;
+ case 'h':
+ case '?':
+ opt_state.help = TRUE;
+ break;
+ case 'q':
+ opt_state.quiet = TRUE;
+ break;
+ case opt_targets:
+ {
+ svn_stringbuf_t *buffer, *buffer_utf8;
+
+ /* We need to convert to UTF-8 now, even before we divide
+ the targets into an array, because otherwise we wouldn't
+ know what delimiter to use for svn_cstring_split(). */
+
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool));
+ SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
+ opt_state.targets = svn_cstring_split(buffer_utf8->data, "\n\r",
+ TRUE, pool);
+ }
+ break;
+ case 'N':
+ descend = FALSE;
+ break;
+ case opt_depth:
+ err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
+ if (err)
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Error converting depth "
+ "from locale to UTF-8")));
+ opt_state.depth = svn_depth_from_word(utf8_opt_arg);
+ if (opt_state.depth == svn_depth_unknown
+ || opt_state.depth == svn_depth_exclude)
+ {
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid depth; try "
+ "'empty', 'files', 'immediates', "
+ "or 'infinity'"),
+ utf8_opt_arg));
+ }
+ break;
+ case opt_version:
+ opt_state.version = TRUE;
+ break;
+ case opt_auth_username:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_username,
+ opt_arg, pool));
+ break;
+ case opt_auth_password:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password,
+ opt_arg, pool));
+ break;
+ case opt_stop_on_copy:
+ opt_state.stop_on_copy = TRUE;
+ break;
+ case opt_strict:
+ opt_state.strict = TRUE;
+ break;
+ case opt_no_auth_cache:
+ opt_state.no_auth_cache = TRUE;
+ break;
+ case opt_non_interactive:
+ opt_state.non_interactive = TRUE;
+ break;
+ case opt_trust_server_cert:
+ opt_state.trust_server_cert = TRUE;
+ break;
+ case 'x':
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.extensions,
+ opt_arg, pool));
+ break;
+ case opt_config_dir:
+ {
+ const char *path_utf8;
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool));
+ opt_state.config_dir = svn_dirent_internal_style(path_utf8, pool);
+ }
+ break;
+ case opt_config_options:
+ if (!opt_state.config_options)
+ opt_state.config_options =
+ apr_array_make(pool, 1,
+ sizeof(svn_cmdline__config_argument_t*));
+
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_cmdline__parse_config_option(opt_state.config_options,
+ opt_arg, pool));
+ break;
+ case opt_with_all_revprops:
+ /* If --with-all-revprops is specified along with one or more
+ * --with-revprops options, --with-all-revprops takes precedence. */
+ opt_state.all_revprops = TRUE;
+ break;
+ case opt_with_no_revprops:
+ opt_state.no_revprops = TRUE;
+ break;
+ case opt_with_revprop:
+ SVN_INT_ERR(svn_opt_parse_revprop(&opt_state.revprop_table,
+ opt_arg, pool));
+ break;
+ case 'g':
+ opt_state.use_merge_history = TRUE;
+ break;
+ default:
+ /* Hmmm. Perhaps this would be a good place to squirrel away
+ opts that commands like svn diff might need. Hmmm indeed. */
+ break;
+ }
+ }
+
+ /* ### This really belongs in libsvn_client. The trouble is,
+ there's no one place there to run it from, no
+ svn_client_init(). We'd have to add it to all the public
+ functions that a client might call. It's unmaintainable to do
+ initialization from within libsvn_client itself, but it seems
+ burdensome to demand that all clients call svn_client_init()
+ before calling any other libsvn_client function... On the other
+ hand, the alternative is effectively to demand that they call
+ svn_config_ensure() instead, so maybe we should have a generic
+ init function anyway. Thoughts? */
+ SVN_INT_ERR(svn_config_ensure(opt_state.config_dir, pool));
+
+ /* If the user asked for help, then the rest of the arguments are
+ the names of subcommands to get help on (if any), or else they're
+ just typos/mistakes. Whatever the case, the subcommand to
+ actually run is svn_cl__help(). */
+ if (opt_state.help)
+ subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, "help");
+
+ /* If we're not running the `help' subcommand, then look for a
+ subcommand in the first argument. */
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_state.version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", svn_cl__help, {0}, "",
+ {opt_version, /* must accept its own option */
+ 'q', /* brief output */
+ 'v', /* verbose output */
+ opt_config_dir /* all commands accept this */
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Subcommand argument required\n")));
+ SVN_INT_ERR(svn_cl__help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table,
+ first_arg);
+ if (subcommand == NULL)
+ {
+ const char *first_arg_utf8;
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
+ first_arg, pool));
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Unknown subcommand: '%s'\n"),
+ first_arg_utf8));
+ SVN_INT_ERR(svn_cl__help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id,
+ svn_cl__global_options))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, svn_cl__options,
+ subcommand, pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ SVN_INT_ERR(svn_cl__help(NULL, NULL, pool));
+ else
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svn-bench help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Only merge and log support multiple revisions/revision ranges. */
+ if (subcommand->cmd_func != svn_cl__null_log)
+ {
+ if (opt_state.revision_ranges->nelts > 1)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Multiple revision arguments "
+ "encountered; can't specify -c twice, "
+ "or both -c and -r"));
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* Disallow simultaneous use of both --with-all-revprops and
+ --with-no-revprops. */
+ if (opt_state.all_revprops && opt_state.no_revprops)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--with-all-revprops and --with-no-revprops "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Disallow simultaneous use of both --with-revprop and
+ --with-no-revprops. */
+ if (opt_state.revprop_table && opt_state.no_revprops)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--with-revprop and --with-no-revprops "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* --trust-server-cert can only be used with --non-interactive */
+ if (opt_state.trust_server_cert && !opt_state.non_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--trust-server-cert requires "
+ "--non-interactive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Ensure that 'revision_ranges' has at least one item, and make
+ 'start_revision' and 'end_revision' match that item. */
+ if (opt_state.revision_ranges->nelts == 0)
+ {
+ svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
+ range->start.kind = svn_opt_revision_unspecified;
+ range->end.kind = svn_opt_revision_unspecified;
+ APR_ARRAY_PUSH(opt_state.revision_ranges,
+ svn_opt_revision_range_t *) = range;
+ }
+ opt_state.start_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
+ svn_opt_revision_range_t *)->start;
+ opt_state.end_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
+ svn_opt_revision_range_t *)->end;
+
+ /* Create a client context object. */
+ command_baton.opt_state = &opt_state;
+ SVN_INT_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ command_baton.ctx = ctx;
+
+ /* Only a few commands can accept a revision range; the rest can take at
+ most one revision number. */
+ if (subcommand->cmd_func != svn_cl__null_log)
+ {
+ if (opt_state.end_revision.kind != svn_opt_revision_unspecified)
+ {
+ err = svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL);
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* -N has a different meaning depending on the command */
+ if (!descend)
+ opt_state.depth = svn_depth_files;
+
+ err = svn_config_get_config(&(ctx->config),
+ opt_state.config_dir, pool);
+ if (err)
+ {
+ /* Fallback to default config if the config directory isn't readable
+ or is not a directory. */
+ if (APR_STATUS_IS_EACCES(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ }
+ else
+ return EXIT_ERROR(err);
+ }
+
+ cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING);
+
+ /* Update the options in the config */
+ if (opt_state.config_options)
+ {
+ svn_error_clear(
+ svn_cmdline__apply_config_options(ctx->config,
+ opt_state.config_options,
+ "svn: ", "--config-option"));
+ }
+
+ /* Set up the notifier.
+
+ In general, we use it any time we aren't in --quiet mode. 'svn
+ status' is unique, though, in that we don't want it in --quiet mode
+ unless we're also in --verbose mode. When in --xml mode,
+ though, we never want it. */
+ if (opt_state.quiet)
+ use_notifier = FALSE;
+ if (use_notifier)
+ {
+ SVN_INT_ERR(svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2,
+ pool));
+ }
+
+ /* Set up our cancellation support. */
+ ctx->cancel_func = svn_cl__check_cancel;
+ apr_signal(SIGINT, signal_handler);
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, signal_handler);
+#endif
+#ifdef SIGHUP
+ apr_signal(SIGHUP, signal_handler);
+#endif
+#ifdef SIGTERM
+ apr_signal(SIGTERM, signal_handler);
+#endif
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /* Set up Authentication stuff. */
+ SVN_INT_ERR(svn_cmdline_create_auth_baton(&ab,
+ opt_state.non_interactive,
+ opt_state.auth_username,
+ opt_state.auth_password,
+ opt_state.config_dir,
+ opt_state.no_auth_cache,
+ opt_state.trust_server_cert,
+ cfg_config,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ pool));
+
+ ctx->auth_baton = ab;
+
+ /* The new svn behavior is to postpone everything until after the operation
+ completed */
+ ctx->conflict_func = NULL;
+ ctx->conflict_baton = NULL;
+ ctx->conflict_func2 = NULL;
+ ctx->conflict_baton2 = NULL;
+
+ /* And now we finally run the subcommand. */
+ err = (*subcommand->cmd_func)(os, &command_baton, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(
+ err, apr_psprintf(pool,
+ _("Try 'svn-bench help %s' for more information"),
+ subcommand->name));
+ }
+ if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Please see the 'svn upgrade' command"));
+ }
+
+ /* Tell the user about 'svn cleanup' if any error on the stack
+ was about locked working copies. */
+ if (svn_error_find_cause(err, SVN_ERR_WC_LOCKED))
+ {
+ err = svn_error_quick_wrap(
+ err, _("Run 'svn cleanup' to remove locks "
+ "(type 'svn help cleanup' for details)"));
+ }
+
+ return EXIT_ERROR(err);
+ }
+ else
+ {
+ /* Ensure that stdout is flushed, so the user will see any write errors.
+ This makes sure that output is not silently lost. */
+ SVN_INT_ERR(svn_cmdline_fflush(stdout));
+
+ return EXIT_SUCCESS;
+ }
+}
+
+int
+main(int argc, const char *argv[])
+{
+ apr_pool_t *pool;
+ int exit_code;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svn", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ exit_code = sub_main(argc, argv, pool);
+
+ svn_pool_destroy(pool);
+ return exit_code;
+}
diff --git a/tools/client-side/svn-bench/util.c b/tools/client-side/svn-bench/util.c
new file mode 100644
index 0000000..2aedde6
--- /dev/null
+++ b/tools/client-side/svn-bench/util.c
@@ -0,0 +1,92 @@
+/*
+ * util.c: Subversion command line client utility functions. Any
+ * functions that need to be shared across subcommands should be put
+ * in here.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "svn_private_config.h"
+#include "svn_error.h"
+#include "svn_path.h"
+
+#include "cl.h"
+
+
+
+svn_error_t *
+svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_last_origpath_on_truepath_collision,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_client_args_to_target_array2(targets,
+ os,
+ known_targets,
+ ctx,
+ keep_last_origpath_on_truepath_collision,
+ pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
+ {
+ svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
+ svn_error_clear(err);
+ }
+ else
+ return svn_error_trace(err);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__check_target_is_local_path(const char *target)
+{
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a local path"), target);
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_cl__local_style_skip_ancestor(const char *parent_path,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *relpath = NULL;
+
+ if (parent_path)
+ relpath = svn_dirent_skip_ancestor(parent_path, path);
+
+ return svn_dirent_local_style(relpath ? relpath : path, pool);
+}
+
diff --git a/tools/client-side/svn-ssl-fingerprints.sh b/tools/client-side/svn-ssl-fingerprints.sh
index 6d1fd92..6fed58b 100755
--- a/tools/client-side/svn-ssl-fingerprints.sh
+++ b/tools/client-side/svn-ssl-fingerprints.sh
@@ -28,6 +28,6 @@
CONFIG_DIR=${1-$HOME/.subversion}
for i in $CONFIG_DIR/auth/svn.ssl.server/????????????????????????????????; do
grep :// $i
- grep '.\{80\}' $i | sed 's/\(.\{64\}\)/\1\n/g' | openssl base64 -d | openssl x509 -inform der -noout -fingerprint | sed 's/=/\n/'
+ grep '.\{80\}' $i | sed 's/\(.\{64\}\)/\1 /g' | xargs -n1 | openssl base64 -d | openssl x509 -inform der -noout -fingerprint | sed 's/=/ /' | xargs -n1
echo
done
diff --git a/tools/client-side/svn-viewspec.py b/tools/client-side/svn-viewspec.py
index 794460a..cdcd495 100755
--- a/tools/client-side/svn-viewspec.py
+++ b/tools/client-side/svn-viewspec.py
@@ -20,6 +20,8 @@
# ====================================================================
"""\
+__SCRIPTNAME__: checkout utility for sparse Subversion working copies
+
Usage: 1. __SCRIPTNAME__ checkout VIEWSPEC-FILE TARGET-DIR
2. __SCRIPTNAME__ examine VIEWSPEC-FILE
3. __SCRIPTNAME__ help
diff --git a/tools/client-side/svnmucc/svnmucc-test.py b/tools/client-side/svnmucc/svnmucc-test.py
deleted file mode 100755
index c09d15c..0000000
--- a/tools/client-side/svnmucc/svnmucc-test.py
+++ /dev/null
@@ -1,359 +0,0 @@
-#!/usr/bin/env python
-#
-#
-# 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.
-#
-#
-
-# Usage: svnmucc-test.py [build-dir-top [base-url]]
-
-import sys
-import os
-import re
-import shutil
-
-# calculate the absolute directory in which this test script lives
-this_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
-
-# add the Subversion Python test suite libraries to the path, and import
-sys.path.insert(0, '%s/../../../subversion/tests/cmdline' % (this_dir))
-import svntest
-
-# setup the global 'svntest.main.options' object so functions in the
-# module don't freak out.
-svntest.main._parse_options(arglist=[])
-
-# calculate the top of the build tree
-if len(sys.argv) > 1:
- build_top = os.path.abspath(sys.argv[1])
-else:
- build_top = os.path.abspath('%s/../../../' % (this_dir))
-
-# where lives svnmucc?
-svnmucc_binary = \
- os.path.abspath('%s/tools/client-side/svnmucc/svnmucc' % (build_top))
-
-# override some svntest binary locations
-svntest.main.svn_binary = \
- os.path.abspath('%s/subversion/svn/svn' % (build_top))
-svntest.main.svnlook_binary = \
- os.path.abspath('%s/subversion/svnlook/svnlook' % (build_top))
-svntest.main.svnadmin_binary = \
- os.path.abspath('%s/subversion/svnadmin/svnadmin' % (build_top))
-
-# where lives the test repository?
-repos_path = \
- os.path.abspath(('%s/tools/client-side/svnmucc/svnmucc-test-repos'
- % (build_top)))
-
-if (len(sys.argv) > 2):
- repos_url = sys.argv[2] + '/svnmucc-test-repos'
-else:
- repos_url = 'file://' + repos_path
-
-def die(msg):
- """Write MSG (formatted as a failure) to stderr, and exit with a
- non-zero errorcode."""
-
- sys.stderr.write("FAIL: " + msg + "\n")
- sys.exit(1)
-
-
-_svnmucc_re = re.compile('^(r[0-9]+) committed by svnmuccuser at (.*)$')
-_log_re = re.compile('^ ([ADRM] /[^\(]+($| \(from .*:[0-9]+\)$))')
-_err_re = re.compile('^svnmucc: (.*)$')
-
-def xrun_svnmucc(expected_errors, *varargs):
- """Run svnmucc with the list of SVNMUCC_ARGS arguments. Verify that
- its run results match the list of EXPECTED_ERRORS."""
-
- # First, run svnmucc.
- exit_code, outlines, errlines = \
- svntest.main.run_command(svnmucc_binary, 1, 0,
- '-U', repos_url,
- '-u', 'svnmuccuser',
- '-p', 'svnmuccpass',
- '--config-dir', 'dummy',
- *varargs)
- errors = []
- for line in errlines:
- match = _err_re.match(line)
- if match:
- errors.append(line.rstrip('\n\r'))
- if errors != expected_errors:
- raise svntest.main.SVNUnmatchedError(str(errors))
-
-
-def run_svnmucc(expected_path_changes, *varargs):
- """Run svnmucc with the list of SVNMUCC_ARGS arguments. Verify that
- its run results in a new commit with 'svn log -rHEAD' changed paths
- that match the list of EXPECTED_PATH_CHANGES."""
-
- # First, run svnmucc.
- exit_code, outlines, errlines = \
- svntest.main.run_command(svnmucc_binary, 1, 0,
- '-U', repos_url,
- '-u', 'svnmuccuser',
- '-p', 'svnmuccpass',
- '--config-dir', 'dummy',
- *varargs)
- if errlines:
- raise svntest.main.SVNCommitFailure(str(errlines))
- if len(outlines) != 1 or not _svnmucc_re.match(outlines[0]):
- raise svntest.main.SVNLineUnequal(str(outlines))
-
- # Now, run 'svn log -vq -rHEAD'
- changed_paths = []
- exit_code, outlines, errlines = \
- svntest.main.run_svn(None, 'log', '-vqrHEAD', repos_url)
- if errlines:
- raise svntest.Failure("Unable to verify commit with 'svn log': %s"
- % (str(errlines)))
- for line in outlines:
- match = _log_re.match(line)
- if match:
- changed_paths.append(match.group(1).rstrip('\n\r'))
-
- expected_path_changes.sort()
- changed_paths.sort()
- if changed_paths != expected_path_changes:
- raise svntest.Failure("Logged path changes differ from expectations\n"
- " expected: %s\n"
- " actual: %s" % (str(expected_path_changes),
- str(changed_paths)))
-
-
-def main():
- """Test svnmucc."""
-
- # revision 1
- run_svnmucc(['A /foo'
- ], # ---------
- 'mkdir', 'foo')
-
- # revision 2
- run_svnmucc(['A /z.c',
- ], # ---------
- 'put', '/dev/null', 'z.c')
-
- # revision 3
- run_svnmucc(['A /foo/z.c (from /z.c:2)',
- 'A /foo/bar (from /foo:2)',
- ], # ---------
- 'cp', '2', 'z.c', 'foo/z.c',
- 'cp', '2', 'foo', 'foo/bar')
-
- # revision 4
- run_svnmucc(['A /zig (from /foo:3)',
- 'D /zig/bar',
- 'D /foo',
- 'A /zig/zag (from /foo:3)',
- ], # ---------
- 'cp', '3', 'foo', 'zig',
- 'rm', 'zig/bar',
- 'mv', 'foo', 'zig/zag')
-
- # revision 5
- run_svnmucc(['D /z.c',
- 'A /zig/zag/bar/y.c (from /z.c:4)',
- 'A /zig/zag/bar/x.c (from /z.c:2)',
- ], # ---------
- 'mv', 'z.c', 'zig/zag/bar/y.c',
- 'cp', '2', 'z.c', 'zig/zag/bar/x.c')
-
- # revision 6
- run_svnmucc(['D /zig/zag/bar/y.c',
- 'A /zig/zag/bar/y y.c (from /zig/zag/bar/y.c:5)',
- 'A /zig/zag/bar/y%20y.c (from /zig/zag/bar/y.c:5)',
- ], # ---------
- 'mv', 'zig/zag/bar/y.c', 'zig/zag/bar/y%20y.c',
- 'cp', 'HEAD', 'zig/zag/bar/y.c', 'zig/zag/bar/y%2520y.c')
-
- # revision 7
- run_svnmucc(['D /zig/zag/bar/y y.c',
- 'A /zig/zag/bar/z z1.c (from /zig/zag/bar/y y.c:6)',
- 'A /zig/zag/bar/z%20z.c (from /zig/zag/bar/y%20y.c:6)',
- 'A /zig/zag/bar/z z2.c (from /zig/zag/bar/y y.c:6)',
- ], #---------
- 'mv', 'zig/zag/bar/y%20y.c', 'zig/zag/bar/z z1.c',
- 'cp', 'HEAD', 'zig/zag/bar/y%2520y.c', 'zig/zag/bar/z%2520z.c',
- 'cp', 'HEAD', 'zig/zag/bar/y y.c', 'zig/zag/bar/z z2.c')
-
- # revision 8
- run_svnmucc(['D /zig/zag',
- 'A /zig/foo (from /zig/zag:7)',
- 'D /zig/foo/bar/z%20z.c',
- 'D /zig/foo/bar/z z2.c',
- 'R /zig/foo/bar/z z1.c (from /zig/zag/bar/x.c:5)',
- ], #---------
- 'mv', 'zig/zag', 'zig/foo',
- 'rm', 'zig/foo/bar/z z1.c',
- 'rm', 'zig/foo/bar/z%20z2.c',
- 'rm', 'zig/foo/bar/z%2520z.c',
- 'cp', '5', 'zig/zag/bar/x.c', 'zig/foo/bar/z%20z1.c')
-
- # revision 9
- run_svnmucc(['R /zig/foo/bar (from /zig/z.c:8)',
- ], #---------
- 'rm', 'zig/foo/bar',
- 'cp', '8', 'zig/z.c', 'zig/foo/bar')
-
- # revision 10
- run_svnmucc(['R /zig/foo/bar (from /zig/foo/bar:8)',
- 'D /zig/foo/bar/z z1.c',
- ], #---------
- 'rm', 'zig/foo/bar',
- 'cp', '8', 'zig/foo/bar', 'zig/foo/bar',
- 'rm', 'zig/foo/bar/z%20z1.c')
-
- # revision 11
- run_svnmucc(['R /zig/foo (from /zig/foo/bar:10)',
- ], #---------
- 'rm', 'zig/foo',
- 'cp', 'head', 'zig/foo/bar', 'zig/foo')
-
- # revision 12
- run_svnmucc(['D /zig',
- 'A /foo (from /foo:3)',
- 'A /foo/foo (from /foo:3)',
- 'A /foo/foo/foo (from /foo:3)',
- 'D /foo/foo/bar',
- 'R /foo/foo/foo/bar (from /foo:3)',
- ], #---------
- 'rm', 'zig',
- 'cp', '3', 'foo', 'foo',
- 'cp', '3', 'foo', 'foo/foo',
- 'cp', '3', 'foo', 'foo/foo/foo',
- 'rm', 'foo/foo/bar',
- 'rm', 'foo/foo/foo/bar',
- 'cp', '3', 'foo', 'foo/foo/foo/bar')
-
- # revision 13
- run_svnmucc(['A /boozle (from /foo:3)',
- 'A /boozle/buz',
- 'A /boozle/buz/nuz',
- ], #---------
- 'cp', '3', 'foo', 'boozle',
- 'mkdir', 'boozle/buz',
- 'mkdir', 'boozle/buz/nuz')
-
- # revision 14
- run_svnmucc(['A /boozle/buz/svnmucc-test.py',
- 'A /boozle/guz (from /boozle/buz:13)',
- 'A /boozle/guz/svnmucc-test.py',
- ], #---------
- 'put', '/dev/null', 'boozle/buz/svnmucc-test.py',
- 'cp', '13', 'boozle/buz', 'boozle/guz',
- 'put', '/dev/null', 'boozle/guz/svnmucc-test.py')
-
- # revision 15
- run_svnmucc(['M /boozle/buz/svnmucc-test.py',
- 'R /boozle/guz/svnmucc-test.py',
- ], #---------
- 'put', sys.argv[0], 'boozle/buz/svnmucc-test.py',
- 'rm', 'boozle/guz/svnmucc-test.py',
- 'put', sys.argv[0], 'boozle/guz/svnmucc-test.py')
-
- # revision 16
- run_svnmucc(['R /foo/bar (from /foo/foo:15)'], #---------
- 'rm', 'foo/bar',
- 'cp', '15', 'foo/foo', 'foo/bar',
- 'propset', 'testprop', 'true', 'foo/bar')
-
- # revision 17
- run_svnmucc(['M /foo/bar'], #---------
- 'propdel', 'testprop', 'foo/bar')
-
- # revision 18
- run_svnmucc(['M /foo/z.c',
- 'M /foo/foo',
- ], #---------
- 'propset', 'testprop', 'true', 'foo/z.c',
- 'propset', 'testprop', 'true', 'foo/foo')
-
- # revision 19
- run_svnmucc(['M /foo/z.c',
- 'M /foo/foo',
- ], #---------
- 'propsetf', 'testprop', sys.argv[0], 'foo/z.c',
- 'propsetf', 'testprop', sys.argv[0], 'foo/foo')
-
- # Expected missing revision error
- xrun_svnmucc(["svnmucc: E200004: 'a' is not a revision"
- ], #---------
- 'cp', 'a', 'b')
-
- # Expected cannot be younger error
- xrun_svnmucc(['svnmucc: E205000: Copy source revision cannot be younger ' +
- 'than base revision',
- ], #---------
- 'cp', '42', 'a', 'b')
-
- # Expected already exists error
- xrun_svnmucc(["svnmucc: E125002: 'foo' already exists",
- ], #---------
- 'cp', '17', 'a', 'foo')
-
- # Expected copy_src already exists error
- xrun_svnmucc(["svnmucc: E125002: 'a/bar' (from 'foo/bar:17') already exists",
- ], #---------
- 'cp', '17', 'foo', 'a',
- 'cp', '17', 'foo/foo', 'a/bar')
-
- # Expected not found error
- xrun_svnmucc(["svnmucc: E125002: 'a' not found",
- ], #---------
- 'cp', '17', 'a', 'b')
-
-if __name__ == "__main__":
- try:
- # remove any previously existing repository, then create a new one
- if os.path.exists(repos_path):
- shutil.rmtree(repos_path)
- exit_code, outlines, errlines = \
- svntest.main.run_svnadmin('create', '--fs-type',
- 'fsfs', repos_path)
- if errlines:
- raise svntest.main.SVNRepositoryCreateFailure(repos_path)
- fp = open(os.path.join(repos_path, 'conf', 'svnserve.conf'), 'w')
- fp.write('[general]\nauth-access = write\npassword-db = passwd\n')
- fp.close()
- fp = open(os.path.join(repos_path, 'conf', 'passwd'), 'w')
- fp.write('[users]\nsvnmuccuser = svnmuccpass\n')
- fp.close()
- main()
- except SystemExit, e:
- raise
- except svntest.main.SVNCommitFailure, e:
- die("Error committing via svnmucc: %s" % (str(e)))
- except svntest.main.SVNLineUnequal, e:
- die("Unexpected svnmucc output line: %s" % (str(e)))
- except svntest.main.SVNRepositoryCreateFailure, e:
- die("Error creating test repository: %s" % (str(e)))
- except svntest.Failure, e:
- die("Test failed: %s" % (str(e)))
- except Exception, e:
- die("Something bad happened: %s" % (str(e)))
-
- # cleanup the repository on a successful run
- try:
- if os.path.exists(repos_path):
- shutil.rmtree(repos_path)
- except:
- pass
- print("SUCCESS!")
diff --git a/tools/client-side/svnmucc/svnmucc.c b/tools/client-side/svnmucc/svnmucc.c
deleted file mode 100644
index b33d6a9..0000000
--- a/tools/client-side/svnmucc/svnmucc.c
+++ /dev/null
@@ -1,1206 +0,0 @@
-/*
- * svnmucc.c: Subversion Multiple URL Client
- *
- * ====================================================================
- * 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.
- * ====================================================================
- *
- */
-
-/* Multiple URL Command Client
-
- Combine a list of mv, cp and rm commands on URLs into a single commit.
-
- How it works: the command line arguments are parsed into an array of
- action structures. The action structures are interpreted to build a
- tree of operation structures. The tree of operation structures is
- used to drive an RA commit editor to produce a single commit.
-
- To build this client, type 'make svnmucc' from the root of your
- Subversion source directory.
-*/
-
-#include <stdio.h>
-#include <string.h>
-
-#include <apr_lib.h>
-
-#include "svn_client.h"
-#include "svn_cmdline.h"
-#include "svn_config.h"
-#include "svn_error.h"
-#include "svn_path.h"
-#include "svn_pools.h"
-#include "svn_props.h"
-#include "svn_ra.h"
-#include "svn_string.h"
-#include "svn_subst.h"
-#include "svn_utf.h"
-#include "svn_version.h"
-#include "private/svn_cmdline_private.h"
-
-static void handle_error(svn_error_t *err, apr_pool_t *pool)
-{
- if (err)
- svn_handle_error2(err, stderr, FALSE, "svnmucc: ");
- svn_error_clear(err);
- if (pool)
- svn_pool_destroy(pool);
- exit(EXIT_FAILURE);
-}
-
-static apr_pool_t *
-init(const char *application)
-{
- apr_allocator_t *allocator;
- apr_pool_t *pool;
- svn_error_t *err;
- const svn_version_checklist_t checklist[] = {
- {"svn_client", svn_client_version},
- {"svn_subr", svn_subr_version},
- {"svn_ra", svn_ra_version},
- {NULL, NULL}
- };
-
- SVN_VERSION_DEFINE(my_version);
-
- if (svn_cmdline_init(application, stderr)
- || apr_allocator_create(&allocator))
- exit(EXIT_FAILURE);
-
- err = svn_ver_check_list(&my_version, checklist);
- if (err)
- handle_error(err, NULL);
-
- apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
- pool = svn_pool_create_ex(NULL, allocator);
- apr_allocator_owner_set(allocator, pool);
-
- return pool;
-}
-
-static svn_error_t *
-open_tmp_file(apr_file_t **fp,
- void *callback_baton,
- apr_pool_t *pool)
-{
- /* Open a unique file; use APR_DELONCLOSE. */
- return svn_io_open_unique_file3(fp, NULL, NULL, svn_io_file_del_on_close,
- pool, pool);
-}
-
-static svn_error_t *
-create_ra_callbacks(svn_ra_callbacks2_t **callbacks,
- const char *username,
- const char *password,
- const char *config_dir,
- svn_config_t *cfg_config,
- svn_boolean_t non_interactive,
- svn_boolean_t no_auth_cache,
- apr_pool_t *pool)
-{
- SVN_ERR(svn_ra_create_callbacks(callbacks, pool));
-
- SVN_ERR(svn_cmdline_create_auth_baton(&(*callbacks)->auth_baton,
- non_interactive,
- username, password, config_dir,
- no_auth_cache,
- FALSE /* trust_server_certs */,
- cfg_config, NULL, NULL, pool));
-
- (*callbacks)->open_tmp_file = open_tmp_file;
-
- return SVN_NO_ERROR;
-}
-
-
-
-static svn_error_t *
-commit_callback(const svn_commit_info_t *commit_info,
- void *baton,
- apr_pool_t *pool)
-{
- SVN_ERR(svn_cmdline_printf(pool, "r%ld committed by %s at %s\n",
- commit_info->revision,
- (commit_info->author
- ? commit_info->author : "(no author)"),
- commit_info->date));
- return SVN_NO_ERROR;
-}
-
-typedef enum action_code_t {
- ACTION_MV,
- ACTION_MKDIR,
- ACTION_CP,
- ACTION_PROPSET,
- ACTION_PROPSETF,
- ACTION_PROPDEL,
- ACTION_PUT,
- ACTION_RM
-} action_code_t;
-
-struct operation {
- enum {
- OP_OPEN,
- OP_DELETE,
- OP_ADD,
- OP_REPLACE,
- OP_PROPSET /* only for files for which no other operation is
- occuring; directories are OP_OPEN with non-empty
- props */
- } operation;
- svn_node_kind_t kind; /* to copy, mkdir, put or set revprops */
- svn_revnum_t rev; /* to copy, valid for add and replace */
- const char *url; /* to copy, valid for add and replace */
- const char *src_file; /* for put, the source file for contents */
- apr_hash_t *children; /* const char *path -> struct operation * */
- apr_hash_t *prop_mods; /* const char *prop_name ->
- const svn_string_t *prop_value */
- apr_array_header_t *prop_dels; /* const char *prop_name deletions */
- void *baton; /* as returned by the commit editor */
-};
-
-
-/* An iterator (for use via apr_table_do) which sets node properties.
- REC is a pointer to a struct driver_state. */
-static svn_error_t *
-change_props(const svn_delta_editor_t *editor,
- void *baton,
- struct operation *child,
- apr_pool_t *pool)
-{
- apr_pool_t *iterpool = svn_pool_create(pool);
-
- if (child->prop_dels)
- {
- int i;
- for (i = 0; i < child->prop_dels->nelts; i++)
- {
- const char *prop_name;
-
- svn_pool_clear(iterpool);
- prop_name = APR_ARRAY_IDX(child->prop_dels, i, const char *);
- if (child->kind == svn_node_dir)
- SVN_ERR(editor->change_dir_prop(baton, prop_name,
- NULL, iterpool));
- else
- SVN_ERR(editor->change_file_prop(baton, prop_name,
- NULL, iterpool));
- }
- }
- if (apr_hash_count(child->prop_mods))
- {
- apr_hash_index_t *hi;
- for (hi = apr_hash_first(pool, child->prop_mods);
- hi; hi = apr_hash_next(hi))
- {
- const void *key;
- void *val;
-
- svn_pool_clear(iterpool);
- apr_hash_this(hi, &key, NULL, &val);
- if (child->kind == svn_node_dir)
- SVN_ERR(editor->change_dir_prop(baton, key, val, iterpool));
- else
- SVN_ERR(editor->change_file_prop(baton, key, val, iterpool));
- }
- }
-
- svn_pool_destroy(iterpool);
- return SVN_NO_ERROR;
-}
-
-
-/* Drive EDITOR to affect the change represented by OPERATION. HEAD
- is the last-known youngest revision in the repository. */
-static svn_error_t *
-drive(struct operation *operation,
- svn_revnum_t head,
- const svn_delta_editor_t *editor,
- apr_pool_t *pool)
-{
- apr_pool_t *subpool = svn_pool_create(pool);
- apr_hash_index_t *hi;
-
- for (hi = apr_hash_first(pool, operation->children);
- hi; hi = apr_hash_next(hi))
- {
- const void *key;
- void *val;
- struct operation *child;
- void *file_baton = NULL;
-
- svn_pool_clear(subpool);
- apr_hash_this(hi, &key, NULL, &val);
- child = val;
-
- /* Deletes and replacements are simple -- delete something. */
- if (child->operation == OP_DELETE || child->operation == OP_REPLACE)
- {
- SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool));
- }
- /* Opens could be for directories or files. */
- if (child->operation == OP_OPEN || child->operation == OP_PROPSET)
- {
- if (child->kind == svn_node_dir)
- {
- SVN_ERR(editor->open_directory(key, operation->baton, head,
- subpool, &child->baton));
- }
- else
- {
- SVN_ERR(editor->open_file(key, operation->baton, head,
- subpool, &file_baton));
- }
- }
- /* Adds and replacements could also be for directories or files. */
- if (child->operation == OP_ADD || child->operation == OP_REPLACE)
- {
- if (child->kind == svn_node_dir)
- {
- SVN_ERR(editor->add_directory(key, operation->baton,
- child->url, child->rev,
- subpool, &child->baton));
- }
- else
- {
- SVN_ERR(editor->add_file(key, operation->baton, child->url,
- child->rev, subpool, &file_baton));
- }
- }
- /* If there's a source file and an open file baton, we get to
- change textual contents. */
- if ((child->src_file) && (file_baton))
- {
- svn_txdelta_window_handler_t handler;
- void *handler_baton;
- svn_stream_t *contents;
- apr_file_t *f = NULL;
-
- SVN_ERR(editor->apply_textdelta(file_baton, NULL, subpool,
- &handler, &handler_baton));
- if (strcmp(child->src_file, "-"))
- {
- SVN_ERR(svn_io_file_open(&f, child->src_file, APR_READ,
- APR_OS_DEFAULT, pool));
- }
- else
- {
- apr_status_t apr_err = apr_file_open_stdin(&f, pool);
- if (apr_err)
- return svn_error_wrap_apr(apr_err, "Can't open stdin");
- }
- contents = svn_stream_from_aprfile2(f, FALSE, pool);
- SVN_ERR(svn_txdelta_send_stream(contents, handler,
- handler_baton, NULL, pool));
- }
- /* If we opened a file, we need to apply outstanding propmods,
- then close it. */
- if (file_baton)
- {
- if (child->kind == svn_node_file)
- {
- SVN_ERR(change_props(editor, file_baton, child, subpool));
- }
- SVN_ERR(editor->close_file(file_baton, NULL, subpool));
- }
- /* If we opened, added, or replaced a directory, we need to
- recurse, apply outstanding propmods, and then close it. */
- if ((child->kind == svn_node_dir)
- && (child->operation == OP_OPEN
- || child->operation == OP_ADD
- || child->operation == OP_REPLACE))
- {
- SVN_ERR(drive(child, head, editor, subpool));
- if (child->kind == svn_node_dir)
- {
- SVN_ERR(change_props(editor, child->baton, child, subpool));
- }
- SVN_ERR(editor->close_directory(child->baton, subpool));
- }
- }
- svn_pool_destroy(subpool);
- return SVN_NO_ERROR;
-}
-
-
-/* Find the operation associated with PATH, which is a single-path
- component representing a child of the path represented by
- OPERATION. If no such child operation exists, create a new one of
- type OP_OPEN. */
-static struct operation *
-get_operation(const char *path,
- struct operation *operation,
- apr_pool_t *pool)
-{
- struct operation *child = apr_hash_get(operation->children, path,
- APR_HASH_KEY_STRING);
- if (! child)
- {
- child = apr_pcalloc(pool, sizeof(*child));
- child->children = apr_hash_make(pool);
- child->operation = OP_OPEN;
- child->rev = SVN_INVALID_REVNUM;
- child->kind = svn_node_dir;
- child->prop_mods = apr_hash_make(pool);
- child->prop_dels = apr_array_make(pool, 1, sizeof(const char *));
- apr_hash_set(operation->children, path, APR_HASH_KEY_STRING, child);
- }
- return child;
-}
-
-/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */
-static const char *
-subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool)
-{
- if (! strcmp(url, anchor))
- return "";
- else
- return svn_uri__is_child(anchor, url, pool);
-}
-
-/* Add PATH to the operations tree rooted at OPERATION, creating any
- intermediate nodes that are required. Here's what's expected for
- each action type:
-
- ACTION URL REV SRC-FILE PROPNAME
- ------------ ----- ------- -------- --------
- ACTION_MKDIR NULL invalid NULL NULL
- ACTION_CP valid valid NULL NULL
- ACTION_PUT NULL invalid valid NULL
- ACTION_RM NULL invalid NULL NULL
- ACTION_PROPSET valid invalid NULL valid
- ACTION_PROPDEL valid invalid NULL valid
-
- Node type information is obtained for any copy source (to determine
- whether to create a file or directory) and for any deleted path (to
- ensure it exists since svn_delta_editor_t->delete_entry doesn't
- return an error on non-existent nodes). */
-static svn_error_t *
-build(action_code_t action,
- const char *path,
- const char *url,
- svn_revnum_t rev,
- const char *prop_name,
- const svn_string_t *prop_value,
- const char *src_file,
- svn_revnum_t head,
- const char *anchor,
- svn_ra_session_t *session,
- struct operation *operation,
- apr_pool_t *pool)
-{
- apr_array_header_t *path_bits = svn_path_decompose(path, pool);
- const char *path_so_far = "";
- const char *copy_src = NULL;
- svn_revnum_t copy_rev = SVN_INVALID_REVNUM;
- int i;
-
- /* Look for any previous operations we've recognized for PATH. If
- any of PATH's ancestors have not yet been traversed, we'll be
- creating OP_OPEN operations for them as we walk down PATH's path
- components. */
- for (i = 0; i < path_bits->nelts; ++i)
- {
- const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *);
- path_so_far = svn_relpath_join(path_so_far, path_bit, pool);
- operation = get_operation(path_so_far, operation, pool);
-
- /* If we cross a replace- or add-with-history, remember the
- source of those things in case we need to lookup the node kind
- of one of their children. And if this isn't such a copy,
- but we've already seen one in of our parent paths, we just need
- to extend that copy source path by our current path
- component. */
- if (operation->url
- && SVN_IS_VALID_REVNUM(operation->rev)
- && (operation->operation == OP_REPLACE
- || operation->operation == OP_ADD))
- {
- copy_src = subtract_anchor(anchor, operation->url, pool);
- copy_rev = operation->rev;
- }
- else if (copy_src)
- {
- copy_src = svn_relpath_join(copy_src, path_bit, pool);
- }
- }
-
- /* Handle property changes. */
- if (prop_name)
- {
- if (operation->operation == OP_DELETE)
- return svn_error_createf(SVN_ERR_BAD_URL, NULL,
- "cannot set properties on a location being"
- " deleted ('%s')", path);
- /* If we're not adding this thing ourselves, check for existence. */
- if (! ((operation->operation == OP_ADD) ||
- (operation->operation == OP_REPLACE)))
- {
- SVN_ERR(svn_ra_check_path(session,
- copy_src ? copy_src : path,
- copy_src ? copy_rev : head,
- &operation->kind, pool));
- if (operation->kind == svn_node_none)
- return svn_error_createf(SVN_ERR_BAD_URL, NULL,
- "propset: '%s' not found", path);
- else if ((operation->kind == svn_node_file)
- && (operation->operation == OP_OPEN))
- operation->operation = OP_PROPSET;
- }
- if (! prop_value)
- APR_ARRAY_PUSH(operation->prop_dels, const char *) = prop_name;
- else
- apr_hash_set(operation->prop_mods, prop_name,
- APR_HASH_KEY_STRING, prop_value);
- if (!operation->rev)
- operation->rev = rev;
- return SVN_NO_ERROR;
- }
-
- /* We won't fuss about multiple operations on the same path in the
- following cases:
-
- - the prior operation was, in fact, a no-op (open)
- - the prior operation was a propset placeholder
- - the prior operation was a deletion
-
- Note: while the operation structure certainly supports the
- ability to do a copy of a file followed by a put of new contents
- for the file, we don't let that happen (yet).
- */
- if (operation->operation != OP_OPEN
- && operation->operation != OP_PROPSET
- && operation->operation != OP_DELETE)
- return svn_error_createf(SVN_ERR_BAD_URL, NULL,
- "unsupported multiple operations on '%s'", path);
-
- /* For deletions, we validate that there's actually something to
- delete. If this is a deletion of the child of a copied
- directory, we need to remember to look in the copy source tree to
- verify that this thing actually exists. */
- if (action == ACTION_RM)
- {
- operation->operation = OP_DELETE;
- SVN_ERR(svn_ra_check_path(session,
- copy_src ? copy_src : path,
- copy_src ? copy_rev : head,
- &operation->kind, pool));
- if (operation->kind == svn_node_none)
- {
- if (copy_src && strcmp(path, copy_src))
- return svn_error_createf(SVN_ERR_BAD_URL, NULL,
- "'%s' (from '%s:%ld') not found",
- path, copy_src, copy_rev);
- else
- return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found",
- path);
- }
- }
- /* Handle copy operations (which can be adds or replacements). */
- else if (action == ACTION_CP)
- {
- if (rev > head)
- return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
- "Copy source revision cannot be younger "
- "than base revision");
- operation->operation =
- operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
- if (operation->operation == OP_ADD)
- {
- /* There is a bug in the current version of mod_dav_svn
- which incorrectly replaces existing directories.
- Therefore we need to check if the target exists
- and raise an error here. */
- SVN_ERR(svn_ra_check_path(session,
- copy_src ? copy_src : path,
- copy_src ? copy_rev : head,
- &operation->kind, pool));
- if (operation->kind != svn_node_none)
- {
- if (copy_src && strcmp(path, copy_src))
- return svn_error_createf(SVN_ERR_BAD_URL, NULL,
- "'%s' (from '%s:%ld') already exists",
- path, copy_src, copy_rev);
- else
- return svn_error_createf(SVN_ERR_BAD_URL, NULL,
- "'%s' already exists", path);
- }
- }
- SVN_ERR(svn_ra_check_path(session, subtract_anchor(anchor, url, pool),
- rev, &operation->kind, pool));
- if (operation->kind == svn_node_none)
- return svn_error_createf(SVN_ERR_BAD_URL, NULL,
- "'%s' not found",
- subtract_anchor(anchor, url, pool));
- operation->url = url;
- operation->rev = rev;
- }
- /* Handle mkdir operations (which can be adds or replacements). */
- else if (action == ACTION_MKDIR)
- {
- operation->operation =
- operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
- operation->kind = svn_node_dir;
- }
- /* Handle put operations (which can be adds, replacements, or opens). */
- else if (action == ACTION_PUT)
- {
- if (operation->operation == OP_DELETE)
- {
- operation->operation = OP_REPLACE;
- }
- else
- {
- SVN_ERR(svn_ra_check_path(session,
- copy_src ? copy_src : path,
- copy_src ? copy_rev : head,
- &operation->kind, pool));
- if (operation->kind == svn_node_file)
- operation->operation = OP_OPEN;
- else if (operation->kind == svn_node_none)
- operation->operation = OP_ADD;
- else
- return svn_error_createf(SVN_ERR_BAD_URL, NULL,
- "'%s' is not a file", path);
- }
- operation->kind = svn_node_file;
- operation->src_file = src_file;
- }
- else
- {
- /* We shouldn't get here. */
- SVN_ERR_MALFUNCTION();
- }
-
- return SVN_NO_ERROR;
-}
-
-struct action {
- action_code_t action;
-
- /* revision (copy-from-rev of path[0] for cp; base-rev for put) */
- svn_revnum_t rev;
-
- /* action path[0] path[1]
- * ------ ------- -------
- * mv source target
- * mkdir target (null)
- * cp source target
- * put target source
- * rm target (null)
- * propset target (null)
- */
- const char *path[2];
-
- /* property name/value */
- const char *prop_name;
- const svn_string_t *prop_value;
-};
-
-static svn_error_t *
-execute(const apr_array_header_t *actions,
- const char *anchor,
- apr_hash_t *revprops,
- const char *username,
- const char *password,
- const char *config_dir,
- const apr_array_header_t *config_options,
- svn_boolean_t non_interactive,
- svn_boolean_t no_auth_cache,
- svn_revnum_t base_revision,
- apr_pool_t *pool)
-{
- svn_ra_session_t *session;
- svn_revnum_t head;
- const svn_delta_editor_t *editor;
- svn_ra_callbacks2_t *ra_callbacks;
- void *editor_baton;
- struct operation root;
- svn_error_t *err;
- apr_hash_t *config;
- svn_config_t *cfg_config;
- int i;
-
- SVN_ERR(svn_config_get_config(&config, config_dir, pool));
- SVN_ERR(svn_cmdline__apply_config_options(config, config_options,
- "svnmucc: ", "--config-option"));
- cfg_config = apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
- APR_HASH_KEY_STRING);
- SVN_ERR(create_ra_callbacks(&ra_callbacks, username, password, config_dir,
- cfg_config, non_interactive, no_auth_cache,
- pool));
- SVN_ERR(svn_ra_open4(&session, NULL, anchor, NULL, ra_callbacks,
- NULL, config, pool));
-
- SVN_ERR(svn_ra_get_latest_revnum(session, &head, pool));
- if (SVN_IS_VALID_REVNUM(base_revision))
- {
- if (base_revision > head)
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- "No such revision %ld (youngest is %ld)",
- base_revision, head);
- head = base_revision;
- }
-
- root.children = apr_hash_make(pool);
- root.operation = OP_OPEN;
- for (i = 0; i < actions->nelts; ++i)
- {
- struct action *action = APR_ARRAY_IDX(actions, i, struct action *);
- switch (action->action)
- {
- const char *path1, *path2;
- case ACTION_MV:
- path1 = subtract_anchor(anchor, action->path[0], pool);
- path2 = subtract_anchor(anchor, action->path[1], pool);
- SVN_ERR(build(ACTION_RM, path1, NULL,
- SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
- session, &root, pool));
- SVN_ERR(build(ACTION_CP, path2, action->path[0],
- head, NULL, NULL, NULL, head, anchor,
- session, &root, pool));
- break;
- case ACTION_CP:
- path2 = subtract_anchor(anchor, action->path[1], pool);
- if (action->rev == SVN_INVALID_REVNUM)
- action->rev = head;
- SVN_ERR(build(ACTION_CP, path2, action->path[0],
- action->rev, NULL, NULL, NULL, head, anchor,
- session, &root, pool));
- break;
- case ACTION_RM:
- path1 = subtract_anchor(anchor, action->path[0], pool);
- SVN_ERR(build(ACTION_RM, path1, NULL,
- SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
- session, &root, pool));
- break;
- case ACTION_MKDIR:
- path1 = subtract_anchor(anchor, action->path[0], pool);
- SVN_ERR(build(ACTION_MKDIR, path1, action->path[0],
- SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
- session, &root, pool));
- break;
- case ACTION_PUT:
- path1 = subtract_anchor(anchor, action->path[0], pool);
- SVN_ERR(build(ACTION_PUT, path1, action->path[0],
- SVN_INVALID_REVNUM, NULL, NULL, action->path[1],
- head, anchor, session, &root, pool));
- break;
- case ACTION_PROPSET:
- case ACTION_PROPDEL:
- path1 = subtract_anchor(anchor, action->path[0], pool);
- SVN_ERR(build(action->action, path1, action->path[0],
- SVN_INVALID_REVNUM,
- action->prop_name, action->prop_value,
- NULL, head, anchor, session, &root, pool));
- break;
- case ACTION_PROPSETF:
- default:
- SVN_ERR_MALFUNCTION_NO_RETURN();
- }
- }
-
- SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &editor_baton, revprops,
- commit_callback, NULL, NULL, FALSE, pool));
-
- SVN_ERR(editor->open_root(editor_baton, head, pool, &root.baton));
- err = drive(&root, head, editor, pool);
- if (!err)
- err = editor->close_edit(editor_baton, pool);
- if (err)
- svn_error_clear(editor->abort_edit(editor_baton, pool));
-
- return err;
-}
-
-static svn_error_t *
-read_propvalue_file(const svn_string_t **value_p,
- const char *filename,
- apr_pool_t *pool)
-{
- svn_stringbuf_t *value;
- apr_pool_t *scratch_pool = svn_pool_create(pool);
- apr_file_t *f;
-
- SVN_ERR(svn_io_file_open(&f, filename, APR_READ | APR_BINARY | APR_BUFFERED,
- APR_OS_DEFAULT, scratch_pool));
- SVN_ERR(svn_stringbuf_from_aprfile(&value, f, scratch_pool));
- *value_p = svn_string_create_from_buf(value, pool);
- svn_pool_destroy(scratch_pool);
- return SVN_NO_ERROR;
-}
-
-/* Perform the typical suite of manipulations for user-provided URLs
- on URL, returning the result (allocated from POOL): IRI-to-URI
- conversion, auto-escaping, and canonicalization. */
-static const char *
-sanitize_url(const char *url,
- apr_pool_t *pool)
-{
- url = svn_path_uri_from_iri(url, pool);
- url = svn_path_uri_autoescape(url, pool);
- return svn_uri_canonicalize(url, pool);
-}
-
-static void
-usage(apr_pool_t *pool, int exit_val)
-{
- FILE *stream = exit_val == EXIT_SUCCESS ? stdout : stderr;
- const char msg[] =
- "Multiple URL Command Client (for Subversion)\n"
- "\nUsage: svnmucc [OPTION]... [ACTION]...\n"
- "\nActions:\n"
- " cp REV URL1 URL2 copy URL1@REV to URL2\n"
- " mkdir URL create new directory URL\n"
- " mv URL1 URL2 move URL1 to URL2\n"
- " rm URL delete URL\n"
- " put SRC-FILE URL add or modify file URL with contents copied from\n"
- " SRC-FILE (use \"-\" to read from standard input)\n"
- " propset NAME VAL URL set property NAME on URL to value VAL\n"
- " propsetf NAME VAL URL set property NAME on URL to value from file VAL\n"
- " propdel NAME URL delete property NAME from URL\n"
- "\nOptions:\n"
- " -h, --help display this text\n"
- " -m, --message ARG use ARG as a log message\n"
- " -F, --file ARG read log message from file ARG\n"
- " -u, --username ARG commit the changes as username ARG\n"
- " -p, --password ARG use ARG as the password\n"
- " -U, --root-url ARG interpret all action URLs are relative to ARG\n"
- " -r, --revision ARG use revision ARG as baseline for changes\n"
- " --with-revprop A[=B] set revision property A in new revision to B\n"
- " if specified, else to the empty string\n"
- " -n, --non-interactive don't prompt the user about anything\n"
- " -X, --extra-args ARG append arguments from file ARG (one per line;\n"
- " use \"-\" to read from standard input)\n"
- " --config-dir ARG use ARG to override the config directory\n"
- " --config-option ARG use ARG so override a configuration option\n"
- " --no-auth-cache do not cache authentication tokens\n"
- " --version print version information\n";
- svn_error_clear(svn_cmdline_fputs(msg, stream, pool));
- apr_pool_destroy(pool);
- exit(exit_val);
-}
-
-static void
-insufficient(apr_pool_t *pool)
-{
- handle_error(svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
- "insufficient arguments"),
- pool);
-}
-
-static svn_error_t *
-display_version(apr_getopt_t *os, apr_pool_t *pool)
-{
- const char *ra_desc_start
- = "The following repository access (RA) modules are available:\n\n";
- svn_stringbuf_t *version_footer;
-
- version_footer = svn_stringbuf_create(ra_desc_start, pool);
- SVN_ERR(svn_ra_print_modules(version_footer, pool));
-
- SVN_ERR(svn_opt_print_help3(os, "svnmucc", TRUE, FALSE, version_footer->data,
- NULL, NULL, NULL, NULL, NULL, pool));
-
- return SVN_NO_ERROR;
-}
-
-int
-main(int argc, const char **argv)
-{
- apr_pool_t *pool = init("svnmucc");
- apr_array_header_t *actions = apr_array_make(pool, 1,
- sizeof(struct action *));
- const char *anchor = NULL;
- svn_error_t *err = SVN_NO_ERROR;
- apr_getopt_t *getopt;
- enum {
- config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID,
- config_inline_opt,
- no_auth_cache_opt,
- version_opt,
- with_revprop_opt
- };
- const apr_getopt_option_t options[] = {
- {"message", 'm', 1, ""},
- {"file", 'F', 1, ""},
- {"username", 'u', 1, ""},
- {"password", 'p', 1, ""},
- {"root-url", 'U', 1, ""},
- {"revision", 'r', 1, ""},
- {"with-revprop", with_revprop_opt, 1, ""},
- {"extra-args", 'X', 1, ""},
- {"help", 'h', 0, ""},
- {"non-interactive", 'n', 0, ""},
- {"config-dir", config_dir_opt, 1, ""},
- {"config-option", config_inline_opt, 1, ""},
- {"no-auth-cache", no_auth_cache_opt, 0, ""},
- {"version", version_opt, 0, ""},
- {NULL, 0, 0, NULL}
- };
- const char *message = NULL;
- const char *username = NULL, *password = NULL;
- const char *root_url = NULL, *extra_args_file = NULL;
- const char *config_dir = NULL;
- apr_array_header_t *config_options;
- svn_boolean_t non_interactive = FALSE;
- svn_boolean_t no_auth_cache = FALSE;
- svn_revnum_t base_revision = SVN_INVALID_REVNUM;
- apr_array_header_t *action_args;
- apr_hash_t *revprops = apr_hash_make(pool);
- int i;
-
- config_options = apr_array_make(pool, 0,
- sizeof(svn_cmdline__config_argument_t*));
-
- apr_getopt_init(&getopt, pool, argc, argv);
- getopt->interleave = 1;
- while (1)
- {
- int opt;
- const char *arg;
- const char *opt_arg;
-
- apr_status_t status = apr_getopt_long(getopt, options, &opt, &arg);
- if (APR_STATUS_IS_EOF(status))
- break;
- if (status != APR_SUCCESS)
- handle_error(svn_error_wrap_apr(status, "getopt failure"), pool);
- switch(opt)
- {
- case 'm':
- err = svn_utf_cstring_to_utf8(&message, arg, pool);
- if (err)
- handle_error(err, pool);
- break;
- case 'F':
- {
- const char *arg_utf8;
- svn_stringbuf_t *contents;
- err = svn_utf_cstring_to_utf8(&arg_utf8, arg, pool);
- if (! err)
- err = svn_stringbuf_from_file2(&contents, arg, pool);
- if (! err)
- err = svn_utf_cstring_to_utf8(&message, contents->data, pool);
- if (err)
- handle_error(err, pool);
- }
- break;
- case 'u':
- username = apr_pstrdup(pool, arg);
- break;
- case 'p':
- password = apr_pstrdup(pool, arg);
- break;
- case 'U':
- err = svn_utf_cstring_to_utf8(&root_url, arg, pool);
- if (err)
- handle_error(err, pool);
- if (! svn_path_is_url(root_url))
- handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
- "'%s' is not a URL\n", root_url),
- pool);
- root_url = sanitize_url(root_url, pool);
- break;
- case 'r':
- {
- char *digits_end = NULL;
- base_revision = strtol(arg, &digits_end, 10);
- if ((! SVN_IS_VALID_REVNUM(base_revision))
- || (! digits_end)
- || *digits_end)
- handle_error(svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR,
- NULL, "Invalid revision number"),
- pool);
- }
- break;
- case with_revprop_opt:
- err = svn_opt_parse_revprop(&revprops, arg, pool);
- if (err != SVN_NO_ERROR)
- handle_error(err, pool);
- break;
- case 'X':
- extra_args_file = apr_pstrdup(pool, arg);
- break;
- case 'n':
- non_interactive = TRUE;
- break;
- case config_dir_opt:
- err = svn_utf_cstring_to_utf8(&config_dir, arg, pool);
- if (err)
- handle_error(err, pool);
- break;
- case config_inline_opt:
- err = svn_utf_cstring_to_utf8(&opt_arg, arg, pool);
- if (err)
- handle_error(err, pool);
-
- err = svn_cmdline__parse_config_option(config_options, opt_arg,
- pool);
- if (err)
- handle_error(err, pool);
- break;
- case no_auth_cache_opt:
- no_auth_cache = TRUE;
- break;
- case version_opt:
- SVN_INT_ERR(display_version(getopt, pool));
- exit(EXIT_SUCCESS);
- break;
- case 'h':
- usage(pool, EXIT_SUCCESS);
- break;
- }
- }
-
- /* Copy the rest of our command-line arguments to an array,
- UTF-8-ing them along the way. */
- action_args = apr_array_make(pool, getopt->argc, sizeof(const char *));
- while (getopt->ind < getopt->argc)
- {
- const char *arg = getopt->argv[getopt->ind++];
- if ((err = svn_utf_cstring_to_utf8(&(APR_ARRAY_PUSH(action_args,
- const char *)),
- arg, pool)))
- handle_error(err, pool);
- }
-
- /* If there are extra arguments in a supplementary file, tack those
- on, too (again, in UTF8 form). */
- if (extra_args_file)
- {
- const char *extra_args_file_utf8;
- svn_stringbuf_t *contents, *contents_utf8;
-
- err = svn_utf_cstring_to_utf8(&extra_args_file_utf8,
- extra_args_file, pool);
- if (! err)
- err = svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool);
- if (! err)
- err = svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool);
- if (err)
- handle_error(err, pool);
- svn_cstring_split_append(action_args, contents_utf8->data, "\n\r",
- FALSE, pool);
- }
-
- /* Now, we iterate over the combined set of arguments -- our actions. */
- for (i = 0; i < action_args->nelts; )
- {
- int j, num_url_args;
- const char *action_string = APR_ARRAY_IDX(action_args, i, const char *);
- struct action *action = apr_palloc(pool, sizeof(*action));
-
- /* First, parse the action. */
- if (! strcmp(action_string, "mv"))
- action->action = ACTION_MV;
- else if (! strcmp(action_string, "cp"))
- action->action = ACTION_CP;
- else if (! strcmp(action_string, "mkdir"))
- action->action = ACTION_MKDIR;
- else if (! strcmp(action_string, "rm"))
- action->action = ACTION_RM;
- else if (! strcmp(action_string, "put"))
- action->action = ACTION_PUT;
- else if (! strcmp(action_string, "propset"))
- action->action = ACTION_PROPSET;
- else if (! strcmp(action_string, "propsetf"))
- action->action = ACTION_PROPSETF;
- else if (! strcmp(action_string, "propdel"))
- action->action = ACTION_PROPDEL;
- else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h")
- || ! strcmp(action_string, "help"))
- usage(pool, EXIT_SUCCESS);
- else
- handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
- "'%s' is not an action\n",
- action_string), pool);
- if (++i == action_args->nelts)
- insufficient(pool);
-
- /* For copies, there should be a revision number next. */
- if (action->action == ACTION_CP)
- {
- const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *);
- if (strcmp(rev_str, "head") == 0)
- action->rev = SVN_INVALID_REVNUM;
- else if (strcmp(rev_str, "HEAD") == 0)
- action->rev = SVN_INVALID_REVNUM;
- else
- {
- char *end;
-
- while (*rev_str == 'r')
- ++rev_str;
-
- action->rev = strtol(rev_str, &end, 0);
- if (*end)
- handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
- "'%s' is not a revision\n",
- rev_str), pool);
- }
- if (++i == action_args->nelts)
- insufficient(pool);
- }
- else
- {
- action->rev = SVN_INVALID_REVNUM;
- }
-
- /* For puts, there should be a local file next. */
- if (action->action == ACTION_PUT)
- {
- action->path[1] =
- svn_dirent_canonicalize(APR_ARRAY_IDX(action_args, i,
- const char *), pool);
- if (++i == action_args->nelts)
- insufficient(pool);
- }
-
- /* For propset, propsetf, and propdel, a property name (and
- maybe a property value or file which contains one) comes next. */
- if ((action->action == ACTION_PROPSET)
- || (action->action == ACTION_PROPSETF)
- || (action->action == ACTION_PROPDEL))
- {
- action->prop_name = APR_ARRAY_IDX(action_args, i, const char *);
- if (++i == action_args->nelts)
- insufficient(pool);
-
- if (action->action == ACTION_PROPDEL)
- {
- action->prop_value = NULL;
- }
- else if (action->action == ACTION_PROPSET)
- {
- action->prop_value =
- svn_string_create(APR_ARRAY_IDX(action_args, i,
- const char *), pool);
- if (++i == action_args->nelts)
- insufficient(pool);
- }
- else
- {
- const char *propval_file =
- svn_dirent_canonicalize(APR_ARRAY_IDX(action_args, i,
- const char *), pool);
-
- if (++i == action_args->nelts)
- insufficient(pool);
-
- err = read_propvalue_file(&(action->prop_value),
- propval_file, pool);
- if (err)
- handle_error(err, pool);
-
- action->action = ACTION_PROPSET;
- }
-
- if (action->prop_value
- && svn_prop_needs_translation(action->prop_name))
- {
- svn_string_t *translated_value;
- err = svn_subst_translate_string2(&translated_value, NULL,
- NULL, action->prop_value, NULL,
- FALSE, pool, pool);
- if (err)
- handle_error(
- svn_error_quick_wrap(err,
- "Error normalizing property value"),
- pool);
- action->prop_value = translated_value;
- }
- }
-
- /* How many URLs does this action expect? */
- if (action->action == ACTION_RM
- || action->action == ACTION_MKDIR
- || action->action == ACTION_PUT
- || action->action == ACTION_PROPSET
- || action->action == ACTION_PROPSETF /* shouldn't see this one */
- || action->action == ACTION_PROPDEL)
- num_url_args = 1;
- else
- num_url_args = 2;
-
- /* Parse the required number of URLs. */
- for (j = 0; j < num_url_args; ++j)
- {
- const char *url = APR_ARRAY_IDX(action_args, i, const char *);
-
- /* If there's a ROOT_URL, we expect URL to be a path
- relative to ROOT_URL (and we build a full url from the
- combination of the two). Otherwise, it should be a full
- url. */
- if (! svn_path_is_url(url))
- {
- if (! root_url)
- handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
- "'%s' is not a URL, and "
- "--root-url (-U) not provided\n",
- url), pool);
- /* ### These relpaths are already URI-encoded. */
- url = apr_pstrcat(pool, root_url, "/",
- svn_relpath_canonicalize(url, pool),
- (char *)NULL);
- }
- url = sanitize_url(url, pool);
- action->path[j] = url;
-
- /* The cp source could be the anchor, but the other URLs should be
- children of the anchor. */
- if (! (action->action == ACTION_CP && j == 0))
- url = svn_uri_dirname(url, pool);
- if (! anchor)
- anchor = url;
- else
- anchor = svn_uri_get_longest_ancestor(anchor, url, pool);
-
- if ((++i == action_args->nelts) && (j >= num_url_args))
- insufficient(pool);
- }
- APR_ARRAY_PUSH(actions, struct action *) = action;
- }
-
- if (! actions->nelts)
- usage(pool, EXIT_FAILURE);
-
- if (message == NULL)
- {
- if (apr_hash_get(revprops, SVN_PROP_REVISION_LOG,
- APR_HASH_KEY_STRING) == NULL)
- /* None of -F, -m, or --with-revprop=svn:log specified; default. */
- apr_hash_set(revprops, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
- svn_string_create("committed using svnmucc", pool));
- }
- else
- {
- /* -F or -m specified; use that even if --with-revprop=svn:log. */
- apr_hash_set(revprops, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
- svn_string_create(message, pool));
- }
-
- if ((err = execute(actions, anchor, revprops, username, password,
- config_dir, config_options, non_interactive,
- no_auth_cache, base_revision, pool)))
- handle_error(err, pool);
-
- svn_pool_destroy(pool);
- return EXIT_SUCCESS;
-}