1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
#!/usr/bin/env python
# ====================================================================
#
# incremental-update.py
#
# This script performs updates of a single working copy tree piece by
# piece, starting with deep subdirectores, and working its way up
# toward the root of the working copy. Why? Because for working
# copies that have significantly mixed revisions, the size and
# complexity of the report that Subversion has to transmit to the
# server can be prohibitive, even triggering server-configured limits
# for such things. But doing an incremental update, you lessen the
# chance of hitting such a limit.
#
# ====================================================================
# Copyright (c) 2007 CollabNet. All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
# This software consists of voluntary contributions made by many
# individuals. For exact contribution history, see the revision
# history and logs, available at http://subversion.tigris.org/.
# ====================================================================
# --------------------------------------------------------------------
# Configuration (oooh... so complex...)
#
SVN_BINARY='svn'
#
# --------------------------------------------------------------------
import sys
import os
import re
def print_error(err):
sys.stderr.write("ERROR: %s\n\n" % (err))
def usage_and_exit(err=None):
if err:
stream = sys.stderr
print_error(err)
else:
stream = sys.stdout
stream.write("""Usage: %s [OPTIONS] WC-DIR
Update WC-DIR in an incremental fashion, starting will smaller
subtrees of it, and working up toward WC-DIR itself. SVN_UP_ARGS are
command-line parameters passed straight through to the Subversion
command-line client (svn) as parameters to its update command.
WARNING: Speed of operation is explicitly *NOT* of interest to this
script. Use it only when a typical 'svn update' isn't working for you
due to the complexity of your working copy's mixed-revision state.
Options:
--username USER Specify the username used to connect to the repository
--password PASS Specify the PASSWORD used to connect to the repository
""" % (os.path.basename(sys.argv[0])))
sys.exit(err and 1 or 0)
def get_head_revision(path, args):
"""Return the current HEAD revision for the repository associated
with PATH. ARGS are extra arguments to provide to the svn
client."""
lines = os.popen('%s status --show-updates --non-recursive %s %s'
% (SVN_BINARY, args, path)).readlines()
if lines and lines[-1].startswith('Status against revision:'):
return int(lines[-1][24:].strip())
raise Exception, "Unable to fetch HEAD revision number."
def compare_paths(path1, path2):
"""This is a sort() helper function for two paths."""
path1_len = len (path1);
path2_len = len (path2);
min_len = min(path1_len, path2_len)
i = 0
# Are the paths exactly the same?
if path1 == path2:
return 0
# Skip past common prefix
while (i < min_len) and (path1[i] == path2[i]):
i = i + 1
# Children of paths are greater than their parents, but less than
# greater siblings of their parents
char1 = '\0'
char2 = '\0'
if (i < path1_len):
char1 = path1[i]
if (i < path2_len):
char2 = path2[i]
if (char1 == '/') and (i == path2_len):
return 1
if (char2 == '/') and (i == path1_len):
return -1
if (i < path1_len) and (char1 == '/'):
return -1
if (i < path2_len) and (char2 == '/'):
return 1
# Common prefix was skipped above, next character is compared to
# determine order
return cmp(char1, char2)
def harvest_dirs(path):
"""Return a list of versioned directories under working copy
directory PATH, inclusive."""
# 'svn status' output line matcher, taken from the Subversion test suite
rm = re.compile('^([!MACDRUG_ ][MACDRUG_ ])([L ])([+ ])([S ])([KOBT ]) ' \
'([* ]) [^0-9-]*(\d+|-|\?) +(\d|-|\?)+ +(\S+) +(.+)')
dirs = []
fp = os.popen('%s status --verbose %s' % (SVN_BINARY, path))
while 1:
line = fp.readline()
if not line:
break
line = line.rstrip()
if line.startswith('Performing'):
break
match = rm.search(line)
if match:
stpath = match.group(10)
try:
if os.path.isdir(stpath):
dirs.append(stpath)
except:
pass
return dirs
def main():
argc = len(sys.argv)
if argc < 2:
usage_and_exit("No working copy directory specified")
if '--help' in sys.argv:
usage_and_exit(None)
path = sys.argv[-1]
args = ' '.join(sys.argv[1:-1] + ['--non-interactive'])
print "Fetch HEAD revision...",
head_revision = get_head_revision(path, args)
print "done."
print "Updating to revision %d" % (head_revision)
print "Harvesting the list of subdirectories...",
dirs = harvest_dirs(path)
print "done."
dirs.sort(compare_paths)
dirs.reverse()
print "Update the tree, one subdirectory at a time. This could take " \
"a while."
num_dirs = len(dirs)
width = len(str(num_dirs))
format_string = '[%%%dd/%%%dd] Updating %%s' % (width, width)
current = 0
for dir in dirs:
current = current + 1
print format_string % (current, num_dirs, dir)
os.system('%s update --quiet --revision %d %s %s'
% (SVN_BINARY, head_revision, args, dir))
if __name__ == "__main__":
try:
main()
except SystemExit:
raise
except Exception, e:
print_error(str(e))
sys.exit(1)
|