diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2015-03-18 13:33:26 +0000 |
---|---|---|
committer | <> | 2015-07-08 14:41:01 +0000 |
commit | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (patch) | |
tree | 98bae10dde41c746c51ae97ec4f879e330415aa7 /tools/client-side | |
parent | 239dfafe71711b2f4c43d7b90a1228d7bdc5195e (diff) | |
download | subversion-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_completion | 68 | ||||
-rwxr-xr-x | tools/client-side/detach.py | 271 | ||||
-rwxr-xr-x | tools/client-side/mergeinfo-sanitizer.py | 319 | ||||
-rw-r--r-- | tools/client-side/svn-bench/cl.h | 198 | ||||
-rw-r--r-- | tools/client-side/svn-bench/client_errors.h | 97 | ||||
-rw-r--r-- | tools/client-side/svn-bench/help-cmd.c | 94 | ||||
-rw-r--r-- | tools/client-side/svn-bench/notify.c | 1045 | ||||
-rw-r--r-- | tools/client-side/svn-bench/null-export-cmd.c | 346 | ||||
-rw-r--r-- | tools/client-side/svn-bench/null-list-cmd.c | 169 | ||||
-rw-r--r-- | tools/client-side/svn-bench/null-log-cmd.c | 243 | ||||
-rw-r--r-- | tools/client-side/svn-bench/svn-bench.c | 954 | ||||
-rw-r--r-- | tools/client-side/svn-bench/util.c | 92 | ||||
-rwxr-xr-x | tools/client-side/svn-ssl-fingerprints.sh | 2 | ||||
-rwxr-xr-x | tools/client-side/svn-viewspec.py | 2 | ||||
-rwxr-xr-x | tools/client-side/svnmucc/svnmucc-test.py | 359 | ||||
-rw-r--r-- | tools/client-side/svnmucc/svnmucc.c | 1206 |
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; -} |