Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1173)

Side by Side Diff: change-svn-wc-format.py

Issue 109035: - Add change-svn-wc-format.py utility from http://svn.collab.net/repos/svn/tr... (Closed) Base URL: http://src.chromium.org/svn/trunk/tools/depot_tools/
Patch Set: '' Created 11 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « bootstrap/win/win_tools.bat ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 #
3 # change-svn-wc-format.py: Change the format of a Subversion working copy.
4 #
5 # ====================================================================
6 # Copyright (c) 2007-2009 CollabNet. All rights reserved.
7 #
8 # This software is licensed as described in the file COPYING, which
9 # you should have received as part of this distribution. The terms
10 # are also available at http://subversion.tigris.org/license-1.html.
11 # If newer versions of this license are posted there, you may use a
12 # newer version instead, at your option.
13 #
14 # This software consists of voluntary contributions made by many
15 # individuals. For exact contribution history, see the revision
16 # history and logs, available at http://subversion.tigris.org/.
17 # ====================================================================
18
19 import sys
20 import os
21 import getopt
22 try:
23 my_getopt = getopt.gnu_getopt
24 except AttributeError:
25 my_getopt = getopt.getopt
26
27 ### The entries file parser in subversion/tests/cmdline/svntest/entry.py
28 ### handles the XML-based WC entries file format used by Subversion
29 ### 1.3 and lower. It could be rolled into this script.
30
31 LATEST_FORMATS = { "1.4" : 8,
32 "1.5" : 9,
33 "1.6" : 10,
34 # Do NOT add format 11 here. See comment in must_retain_fiel ds
35 # for why.
36 }
37
38 def usage_and_exit(error_msg=None):
39 """Write usage information and exit. If ERROR_MSG is provide, that
40 error message is printed first (to stderr), the usage info goes to
41 stderr, and the script exits with a non-zero status. Otherwise,
42 usage info goes to stdout and the script exits with a zero status."""
43 progname = os.path.basename(sys.argv[0])
44
45 stream = error_msg and sys.stderr or sys.stdout
46 if error_msg:
47 stream.write("ERROR: %s\n\n" % error_msg)
48 stream.write("""\
49 usage: %s WC_PATH SVN_VERSION [--verbose] [--force] [--skip-unknown-format]
50 %s --help
51
52 Change the format of a Subversion working copy to that of SVN_VERSION.
53
54 --skip-unknown-format : skip directories with unknown working copy
55 format and continue the update
56
57 """ % (progname, progname))
58 stream.flush()
59 sys.exit(error_msg and 1 or 0)
60
61 def get_adm_dir():
62 """Return the name of Subversion's administrative directory,
63 adjusted for the SVN_ASP_DOT_NET_HACK environment variable. See
64 <http://svn.collab.net/repos/svn/trunk/notes/asp-dot-net-hack.txt>
65 for details."""
66 return "SVN_ASP_DOT_NET_HACK" in os.environ and "_svn" or ".svn"
67
68 class WCFormatConverter:
69 "Performs WC format conversions."
70 root_path = None
71 error_on_unrecognized = True
72 force = False
73 verbosity = 0
74
75 def write_dir_format(self, format_nbr, dirname, paths):
76 """Attempt to write the WC format FORMAT_NBR to the entries file
77 for DIRNAME. Throws LossyConversionException when not in --force
78 mode, and unconvertable WC data is encountered."""
79
80 # Avoid iterating in unversioned directories.
81 if not (get_adm_dir() in paths):
82 del paths[:]
83 return
84
85 # Process the entries file for this versioned directory.
86 if self.verbosity:
87 print("Processing directory '%s'" % dirname)
88 entries = Entries(os.path.join(dirname, get_adm_dir(), "entries"))
89 entries_parsed = True
90 if self.verbosity:
91 print("Parsing file '%s'" % entries.path)
92 try:
93 entries.parse(self.verbosity)
94 except UnrecognizedWCFormatException, e:
95 if self.error_on_unrecognized:
96 raise
97 sys.stderr.write("%s, skipping\n" % e)
98 sys.stderr.flush()
99 entries_parsed = False
100
101 if entries_parsed:
102 format = Format(os.path.join(dirname, get_adm_dir(), "format"))
103 if self.verbosity:
104 print("Updating file '%s'" % format.path)
105 format.write_format(format_nbr, self.verbosity)
106 else:
107 if self.verbosity:
108 print("Skipping file '%s'" % format.path)
109
110 if self.verbosity:
111 print("Checking whether WC format can be converted")
112 try:
113 entries.assert_valid_format(format_nbr, self.verbosity)
114 except LossyConversionException, e:
115 # In --force mode, ignore complaints about lossy conversion.
116 if self.force:
117 print("WARNING: WC format conversion will be lossy. Dropping "\
118 "field(s) %s " % ", ".join(e.lossy_fields))
119 else:
120 raise
121
122 if self.verbosity:
123 print("Writing WC format")
124 entries.write_format(format_nbr)
125
126 def change_wc_format(self, format_nbr):
127 """Walk all paths in a WC tree, and change their format to
128 FORMAT_NBR. Throw LossyConversionException or NotImplementedError
129 if the WC format should not be converted, or is unrecognized."""
130 for dirpath, dirs, files in os.walk(self.root_path):
131 self.write_dir_format(format_nbr, dirpath, dirs + files)
132
133 class Entries:
134 """Represents a .svn/entries file.
135
136 'The entries file' section in subversion/libsvn_wc/README is a
137 useful reference."""
138
139 # The name and index of each field composing an entry's record.
140 entry_fields = (
141 "name",
142 "kind",
143 "revision",
144 "url",
145 "repos",
146 "schedule",
147 "text-time",
148 "checksum",
149 "committed-date",
150 "committed-rev",
151 "last-author",
152 "has-props",
153 "has-prop-mods",
154 "cachable-props",
155 "present-props",
156 "conflict-old",
157 "conflict-new",
158 "conflict-wrk",
159 "prop-reject-file",
160 "copied",
161 "copyfrom-url",
162 "copyfrom-rev",
163 "deleted",
164 "absent",
165 "incomplete",
166 "uuid",
167 "lock-token",
168 "lock-owner",
169 "lock-comment",
170 "lock-creation-date",
171 "changelist",
172 "keep-local",
173 "working-size",
174 "depth",
175 "tree-conflicts",
176 "file-external",
177 )
178
179 # The format number.
180 format_nbr = -1
181
182 # How many bytes the format number takes in the file. (The format number
183 # may have leading zeroes after using this script to convert format 10 to
184 # format 9 -- which would write the format number as '09'.)
185 format_nbr_bytes = -1
186
187 def __init__(self, path):
188 self.path = path
189 self.entries = []
190
191 def parse(self, verbosity=0):
192 """Parse the entries file. Throw NotImplementedError if the WC
193 format is unrecognized."""
194
195 input = open(self.path, "r")
196
197 # Read WC format number from INPUT. Validate that it
198 # is a supported format for conversion.
199 format_line = input.readline()
200 try:
201 self.format_nbr = int(format_line)
202 self.format_nbr_bytes = len(format_line.rstrip()) # remove '\n'
203 except ValueError:
204 self.format_nbr = -1
205 self.format_nbr_bytes = -1
206 if not self.format_nbr in LATEST_FORMATS.values():
207 raise UnrecognizedWCFormatException(self.format_nbr, self.path)
208
209 # Parse file into individual entries, to later inspect for
210 # non-convertable data.
211 entry = None
212 while True:
213 entry = self.parse_entry(input, verbosity)
214 if entry is None:
215 break
216 self.entries.append(entry)
217
218 input.close()
219
220 def assert_valid_format(self, format_nbr, verbosity=0):
221 if verbosity >= 2:
222 print("Validating format for entries file '%s'" % self.path)
223 for entry in self.entries:
224 if verbosity >= 3:
225 print("Validating format for entry '%s'" % entry.get_name())
226 try:
227 entry.assert_valid_format(format_nbr)
228 except LossyConversionException:
229 if verbosity >= 3:
230 sys.stderr.write("Offending entry:\n%s\n" % entry)
231 sys.stderr.flush()
232 raise
233
234 def parse_entry(self, input, verbosity=0):
235 "Read an individual entry from INPUT stream."
236 entry = None
237
238 while True:
239 line = input.readline()
240 if line in ("", "\x0c\n"):
241 # EOF or end of entry terminator encountered.
242 break
243
244 if entry is None:
245 entry = Entry()
246
247 # Retain the field value, ditching its field terminator ("\x0a").
248 entry.fields.append(line[:-1])
249
250 if entry is not None and verbosity >= 3:
251 sys.stdout.write(str(entry))
252 print("-" * 76)
253 return entry
254
255 def write_format(self, format_nbr):
256 # Overwrite all bytes of the format number (which are the first bytes in
257 # the file). Overwrite format '10' by format '09', which will be converted
258 # to '9' by Subversion when it rewrites the file. (Subversion 1.4 and later
259 # ignore leading zeroes in the format number.)
260 assert len(str(format_nbr)) <= self.format_nbr_bytes
261 format_string = '%0' + str(self.format_nbr_bytes) + 'd'
262
263 os.chmod(self.path, 0600)
264 output = open(self.path, "r+", 0)
265 output.write(format_string % format_nbr)
266 output.close()
267 os.chmod(self.path, 0400)
268
269 class Entry:
270 "Describes an entry in a WC."
271
272 # Maps format numbers to indices of fields within an entry's record that must
273 # be retained when downgrading to that format.
274 must_retain_fields = {
275 # Not in 1.4: changelist, keep-local, depth, tree-conflicts, file-external s
276 8 : (30, 31, 33, 34, 35),
277 # Not in 1.5: tree-conflicts, file-externals
278 9 : (34, 35),
279 10 : (),
280 # Downgrading from format 11 (1.7-dev) to format 10 is not possible,
281 # because 11 does not use has-props and cachable-props (but 10 does).
282 # Naively downgrading in that situation causes properties to disappear
283 # from the wc.
284 #
285 # Downgrading from the 1.7 SQLite-based format to format 10 is not
286 # implemented.
287 }
288
289 def __init__(self):
290 self.fields = []
291
292 def assert_valid_format(self, format_nbr):
293 "Assure that conversion will be non-lossy by examining fields."
294
295 # Check whether lossy conversion is being attempted.
296 lossy_fields = []
297 for field_index in self.must_retain_fields[format_nbr]:
298 if len(self.fields) - 1 >= field_index and self.fields[field_index]:
299 lossy_fields.append(Entries.entry_fields[field_index])
300 if lossy_fields:
301 raise LossyConversionException(lossy_fields,
302 "Lossy WC format conversion requested for entry '%s'\n"
303 "Data for the following field(s) is unsupported by older versions "
304 "of\nSubversion, and is likely to be subsequently discarded, and/or "
305 "have\nunexpected side-effects: %s\n\n"
306 "WC format conversion was cancelled, use the --force option to "
307 "override\nthe default behavior."
308 % (self.get_name(), ", ".join(lossy_fields)))
309
310 def get_name(self):
311 "Return the name of this entry."
312 return len(self.fields) > 0 and self.fields[0] or ""
313
314 def __str__(self):
315 "Return all fields from this entry as a multi-line string."
316 rep = ""
317 for i in range(0, len(self.fields)):
318 rep += "[%s] %s\n" % (Entries.entry_fields[i], self.fields[i])
319 return rep
320
321 class Format:
322 """Represents a .svn/format file."""
323
324 def __init__(self, path):
325 self.path = path
326
327 def write_format(self, format_nbr, verbosity=0):
328 format_string = '%d\n'
329 if os.path.exists(self.path):
330 if verbosity >= 1:
331 print("%s will be updated." % self.path)
332 os.chmod(self.path,0600)
333 else:
334 if verbosity >= 1:
335 print("%s does not exist, creating it." % self.path)
336 format = open(self.path, "w")
337 format.write(format_string % format_nbr)
338 format.close()
339 os.chmod(self.path, 0400)
340
341 class LocalException(Exception):
342 """Root of local exception class hierarchy."""
343 pass
344
345 class LossyConversionException(LocalException):
346 "Exception thrown when a lossy WC format conversion is requested."
347 def __init__(self, lossy_fields, str):
348 self.lossy_fields = lossy_fields
349 self.str = str
350 def __str__(self):
351 return self.str
352
353 class UnrecognizedWCFormatException(LocalException):
354 def __init__(self, format, path):
355 self.format = format
356 self.path = path
357 def __str__(self):
358 return ("Unrecognized WC format %d in '%s'; "
359 "only formats 8, 9, and 10 can be supported") % (self.format, self.p ath)
360
361
362 def main():
363 try:
364 opts, args = my_getopt(sys.argv[1:], "vh?",
365 ["debug", "force", "skip-unknown-format",
366 "verbose", "help"])
367 except:
368 usage_and_exit("Unable to process arguments/options")
369
370 converter = WCFormatConverter()
371
372 # Process arguments.
373 if len(args) == 2:
374 converter.root_path = args[0]
375 svn_version = args[1]
376 else:
377 usage_and_exit()
378
379 # Process options.
380 debug = False
381 for opt, value in opts:
382 if opt in ("--help", "-h", "-?"):
383 usage_and_exit()
384 elif opt == "--force":
385 converter.force = True
386 elif opt == "--skip-unknown-format":
387 converter.error_on_unrecognized = False
388 elif opt in ("--verbose", "-v"):
389 converter.verbosity += 1
390 elif opt == "--debug":
391 debug = True
392 else:
393 usage_and_exit("Unknown option '%s'" % opt)
394
395 try:
396 new_format_nbr = LATEST_FORMATS[svn_version]
397 except KeyError:
398 usage_and_exit("Unsupported version number '%s'; "
399 "only 1.4, 1.5, and 1.6 can be supported" % svn_version)
400
401 try:
402 converter.change_wc_format(new_format_nbr)
403 except LocalException, e:
404 if debug:
405 raise
406 sys.stderr.write("%s\n" % e)
407 sys.stderr.flush()
408 sys.exit(1)
409
410 print("Converted WC at '%s' into format %d for Subversion %s" % \
411 (converter.root_path, new_format_nbr, svn_version))
412
413 if __name__ == "__main__":
414 main()
OLDNEW
« no previous file with comments | « bootstrap/win/win_tools.bat ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698