+++ /dev/null
-#!/usr/bin/env python
-#
-# change-svn-wc-format.py: Change the format of a Subversion working copy.
-#
-# ====================================================================
-# Licensed to the Subversion Corporation (SVN Corp.) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The SVN Corp. 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 sys
-import os
-import getopt
-try:
- my_getopt = getopt.gnu_getopt
-except AttributeError:
- my_getopt = getopt.getopt
-
-### The entries file parser in subversion/tests/cmdline/svntest/entry.py
-### handles the XML-based WC entries file format used by Subversion
-### 1.3 and lower. It could be rolled into this script.
-
-LATEST_FORMATS = { "1.4" : 8,
- "1.5" : 9,
- "1.6" : 10,
- # Do NOT add format 11 here. See comment in must_retain_fields
- # for why.
- }
-
-def usage_and_exit(error_msg=None):
- """Write usage information and exit. If ERROR_MSG is provide, that
- error message is printed first (to stderr), the usage info goes to
- stderr, and the script exits with a non-zero status. Otherwise,
- usage info goes to stdout and the script exits with a zero status."""
- progname = os.path.basename(sys.argv[0])
-
- stream = error_msg and sys.stderr or sys.stdout
- if error_msg:
- stream.write("ERROR: %s\n\n" % error_msg)
- stream.write("""\
-usage: %s WC_PATH SVN_VERSION [--verbose] [--force] [--skip-unknown-format]
- %s --help
-
-Change the format of a Subversion working copy to that of SVN_VERSION.
-
- --skip-unknown-format : skip directories with unknown working copy
- format and continue the update
-
-""" % (progname, progname))
- stream.flush()
- sys.exit(error_msg and 1 or 0)
-
-def get_adm_dir():
- """Return the name of Subversion's administrative directory,
- adjusted for the SVN_ASP_DOT_NET_HACK environment variable. See
- <http://svn.collab.net/repos/svn/trunk/notes/asp-dot-net-hack.txt>
- for details."""
- return "SVN_ASP_DOT_NET_HACK" in os.environ and "_svn" or ".svn"
-
-class WCFormatConverter:
- "Performs WC format conversions."
- root_path = None
- error_on_unrecognized = True
- force = False
- verbosity = 0
-
- def write_dir_format(self, format_nbr, dirname, paths):
- """Attempt to write the WC format FORMAT_NBR to the entries file
- for DIRNAME. Throws LossyConversionException when not in --force
- mode, and unconvertable WC data is encountered."""
-
- # Avoid iterating in unversioned directories.
- if not (get_adm_dir() in paths):
- del paths[:]
- return
-
- # Process the entries file for this versioned directory.
- if self.verbosity:
- print("Processing directory '%s'" % dirname)
- entries = Entries(os.path.join(dirname, get_adm_dir(), "entries"))
- entries_parsed = True
- if self.verbosity:
- print("Parsing file '%s'" % entries.path)
- try:
- entries.parse(self.verbosity)
- except UnrecognizedWCFormatException, e:
- if self.error_on_unrecognized:
- raise
- sys.stderr.write("%s, skipping\n" % e)
- sys.stderr.flush()
- entries_parsed = False
-
- if entries_parsed:
- format = Format(os.path.join(dirname, get_adm_dir(), "format"))
- if self.verbosity:
- print("Updating file '%s'" % format.path)
- format.write_format(format_nbr, self.verbosity)
- else:
- if self.verbosity:
- print("Skipping file '%s'" % format.path)
-
- if self.verbosity:
- print("Checking whether WC format can be converted")
- try:
- entries.assert_valid_format(format_nbr, self.verbosity)
- except LossyConversionException, e:
- # In --force mode, ignore complaints about lossy conversion.
- if self.force:
- print("WARNING: WC format conversion will be lossy. Dropping "\
- "field(s) %s " % ", ".join(e.lossy_fields))
- else:
- raise
-
- if self.verbosity:
- print("Writing WC format")
- entries.write_format(format_nbr)
-
- def change_wc_format(self, format_nbr):
- """Walk all paths in a WC tree, and change their format to
- FORMAT_NBR. Throw LossyConversionException or NotImplementedError
- if the WC format should not be converted, or is unrecognized."""
- for dirpath, dirs, files in os.walk(self.root_path):
- self.write_dir_format(format_nbr, dirpath, dirs + files)
-
-class Entries:
- """Represents a .svn/entries file.
-
- 'The entries file' section in subversion/libsvn_wc/README is a
- useful reference."""
-
- # The name and index of each field composing an entry's record.
- entry_fields = (
- "name",
- "kind",
- "revision",
- "url",
- "repos",
- "schedule",
- "text-time",
- "checksum",
- "committed-date",
- "committed-rev",
- "last-author",
- "has-props",
- "has-prop-mods",
- "cachable-props",
- "present-props",
- "conflict-old",
- "conflict-new",
- "conflict-wrk",
- "prop-reject-file",
- "copied",
- "copyfrom-url",
- "copyfrom-rev",
- "deleted",
- "absent",
- "incomplete",
- "uuid",
- "lock-token",
- "lock-owner",
- "lock-comment",
- "lock-creation-date",
- "changelist",
- "keep-local",
- "working-size",
- "depth",
- "tree-conflicts",
- "file-external",
- )
-
- # The format number.
- format_nbr = -1
-
- # How many bytes the format number takes in the file. (The format number
- # may have leading zeroes after using this script to convert format 10 to
- # format 9 -- which would write the format number as '09'.)
- format_nbr_bytes = -1
-
- def __init__(self, path):
- self.path = path
- self.entries = []
-
- def parse(self, verbosity=0):
- """Parse the entries file. Throw NotImplementedError if the WC
- format is unrecognized."""
-
- input = open(self.path, "r")
-
- # Read WC format number from INPUT. Validate that it
- # is a supported format for conversion.
- format_line = input.readline()
- try:
- self.format_nbr = int(format_line)
- self.format_nbr_bytes = len(format_line.rstrip()) # remove '\n'
- except ValueError:
- self.format_nbr = -1
- self.format_nbr_bytes = -1
- if not self.format_nbr in LATEST_FORMATS.values():
- raise UnrecognizedWCFormatException(self.format_nbr, self.path)
-
- # Parse file into individual entries, to later inspect for
- # non-convertable data.
- entry = None
- while True:
- entry = self.parse_entry(input, verbosity)
- if entry is None:
- break
- self.entries.append(entry)
-
- input.close()
-
- def assert_valid_format(self, format_nbr, verbosity=0):
- if verbosity >= 2:
- print("Validating format for entries file '%s'" % self.path)
- for entry in self.entries:
- if verbosity >= 3:
- print("Validating format for entry '%s'" % entry.get_name())
- try:
- entry.assert_valid_format(format_nbr)
- except LossyConversionException:
- if verbosity >= 3:
- sys.stderr.write("Offending entry:\n%s\n" % entry)
- sys.stderr.flush()
- raise
-
- def parse_entry(self, input, verbosity=0):
- "Read an individual entry from INPUT stream."
- entry = None
-
- while True:
- line = input.readline()
- if line in ("", "\x0c\n"):
- # EOF or end of entry terminator encountered.
- break
-
- if entry is None:
- entry = Entry()
-
- # Retain the field value, ditching its field terminator ("\x0a").
- entry.fields.append(line[:-1])
-
- if entry is not None and verbosity >= 3:
- sys.stdout.write(str(entry))
- print("-" * 76)
- return entry
-
- def write_format(self, format_nbr):
- # Overwrite all bytes of the format number (which are the first bytes in
- # the file). Overwrite format '10' by format '09', which will be converted
- # to '9' by Subversion when it rewrites the file. (Subversion 1.4 and later
- # ignore leading zeroes in the format number.)
- assert len(str(format_nbr)) <= self.format_nbr_bytes
- format_string = '%0' + str(self.format_nbr_bytes) + 'd'
-
- os.chmod(self.path, 0600)
- output = open(self.path, "r+", 0)
- output.write(format_string % format_nbr)
- output.close()
- os.chmod(self.path, 0400)
-
-class Entry:
- "Describes an entry in a WC."
-
- # Maps format numbers to indices of fields within an entry's record that must
- # be retained when downgrading to that format.
- must_retain_fields = {
- # Not in 1.4: changelist, keep-local, depth, tree-conflicts, file-externals
- 8 : (30, 31, 33, 34, 35),
- # Not in 1.5: tree-conflicts, file-externals
- 9 : (34, 35),
- 10 : (),
- # Downgrading from format 11 (1.7-dev) to format 10 is not possible,
- # because 11 does not use has-props and cachable-props (but 10 does).
- # Naively downgrading in that situation causes properties to disappear
- # from the wc.
- #
- # Downgrading from the 1.7 SQLite-based format to format 10 is not
- # implemented.
- }
-
- def __init__(self):
- self.fields = []
-
- def assert_valid_format(self, format_nbr):
- "Assure that conversion will be non-lossy by examining fields."
-
- # Check whether lossy conversion is being attempted.
- lossy_fields = []
- for field_index in self.must_retain_fields[format_nbr]:
- if len(self.fields) - 1 >= field_index and self.fields[field_index]:
- lossy_fields.append(Entries.entry_fields[field_index])
- if lossy_fields:
- raise LossyConversionException(lossy_fields,
- "Lossy WC format conversion requested for entry '%s'\n"
- "Data for the following field(s) is unsupported by older versions "
- "of\nSubversion, and is likely to be subsequently discarded, and/or "
- "have\nunexpected side-effects: %s\n\n"
- "WC format conversion was cancelled, use the --force option to "
- "override\nthe default behavior."
- % (self.get_name(), ", ".join(lossy_fields)))
-
- def get_name(self):
- "Return the name of this entry."
- return len(self.fields) > 0 and self.fields[0] or ""
-
- def __str__(self):
- "Return all fields from this entry as a multi-line string."
- rep = ""
- for i in range(0, len(self.fields)):
- rep += "[%s] %s\n" % (Entries.entry_fields[i], self.fields[i])
- return rep
-
-class Format:
- """Represents a .svn/format file."""
-
- def __init__(self, path):
- self.path = path
-
- def write_format(self, format_nbr, verbosity=0):
- format_string = '%d\n'
- if os.path.exists(self.path):
- if verbosity >= 1:
- print("%s will be updated." % self.path)
- os.chmod(self.path,0600)
- else:
- if verbosity >= 1:
- print("%s does not exist, creating it." % self.path)
- format = open(self.path, "w")
- format.write(format_string % format_nbr)
- format.close()
- os.chmod(self.path, 0400)
-
-class LocalException(Exception):
- """Root of local exception class hierarchy."""
- pass
-
-class LossyConversionException(LocalException):
- "Exception thrown when a lossy WC format conversion is requested."
- def __init__(self, lossy_fields, str):
- self.lossy_fields = lossy_fields
- self.str = str
- def __str__(self):
- return self.str
-
-class UnrecognizedWCFormatException(LocalException):
- def __init__(self, format, path):
- self.format = format
- self.path = path
- def __str__(self):
- return ("Unrecognized WC format %d in '%s'; "
- "only formats 8, 9, and 10 can be supported") % (self.format, self.path)
-
-
-def main():
- try:
- opts, args = my_getopt(sys.argv[1:], "vh?",
- ["debug", "force", "skip-unknown-format",
- "verbose", "help"])
- except:
- usage_and_exit("Unable to process arguments/options")
-
- converter = WCFormatConverter()
-
- # Process arguments.
- if len(args) == 2:
- converter.root_path = args[0]
- svn_version = args[1]
- else:
- usage_and_exit()
-
- # Process options.
- debug = False
- for opt, value in opts:
- if opt in ("--help", "-h", "-?"):
- usage_and_exit()
- elif opt == "--force":
- converter.force = True
- elif opt == "--skip-unknown-format":
- converter.error_on_unrecognized = False
- elif opt in ("--verbose", "-v"):
- converter.verbosity += 1
- elif opt == "--debug":
- debug = True
- else:
- usage_and_exit("Unknown option '%s'" % opt)
-
- try:
- new_format_nbr = LATEST_FORMATS[svn_version]
- except KeyError:
- usage_and_exit("Unsupported version number '%s'; "
- "only 1.4, 1.5, and 1.6 can be supported" % svn_version)
-
- try:
- converter.change_wc_format(new_format_nbr)
- except LocalException, e:
- if debug:
- raise
- sys.stderr.write("%s\n" % e)
- sys.stderr.flush()
- sys.exit(1)
-
- print("Converted WC at '%s' into format %d for Subversion %s" % \
- (converter.root_path, new_format_nbr, svn_version))
-
-if __name__ == "__main__":
- main()