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