-# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
+# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@jelmer.uk>
# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# GNU Lesser General Public License for more details.
-# You should have received a copy of the GNU General Public License
+# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import bisect, urllib
+"""Handling of Subversion properties."""
+
+__author__ = "Jelmer Vernooij <jelmer@jelmer.uk>"
+__docformat__ = "restructuredText"
+
+import bisect, calendar, time, urlparse
class InvalidExternalsDescription(Exception):
def is_valid_property_name(prop):
+ """Check the validity of a property name.
+
+ :param prop: Property name
+ :return: Whether prop is a valid property name
+ """
if not prop[0].isalnum() and not prop[0] in ":_":
return False
for c in prop[1:]:
def time_to_cstring(timestamp):
- import time
+ """Determine string representation of a time.
+
+ :param timestamp: Number of microseconds since the start of 1970
+ :return: string with date
+ """
tm_usec = timestamp % 1000000
(tm_year, tm_mon, tm_mday, tm_hour, tm_min,
tm_sec, tm_wday, tm_yday, tm_isdst) = time.gmtime(timestamp / 1000000)
def time_from_cstring(text):
- import time
+ """Parse a time from a cstring.
+
+ :param text: Parse text
+ :return: number of microseconds since the start of 1970
+ """
(basestr, usecstr) = text.split(".", 1)
assert usecstr[-1] == "Z"
tm_usec = int(usecstr[:-1])
tm = time.strptime(basestr, "%Y-%m-%dT%H:%M:%S")
- return (long(time.mktime((tm[0], tm[1], tm[2], tm[3], tm[4], tm[5], tm[6], tm[7], -1)) - time.timezone) * 1000000 + tm_usec)
+ return (long(calendar.timegm(tm)) * 1000000 + tm_usec)
def parse_externals_description(base_url, val):
raise NotImplementedError("Relative to the scheme externals not yet supported")
if relurl.startswith("^/"):
raise NotImplementedError("Relative to the repository root externals not yet supported")
- ret[path] = (revno, urllib.basejoin(base_url, relurl))
+ ret[path] = (revno, urlparse.urljoin(base_url+"/", relurl))
return ret
def parse_mergeinfo_property(text):
+ """Parse a mergeinfo property.
+
+ :param text: Property contents
+ """
ret = {}
for l in text.splitlines():
(path, ranges) = l.rsplit(":", 1)
def generate_mergeinfo_property(merges):
+ """Generate the contents of the svn:mergeinfo property
+
+ :param merges: dictionary mapping paths to lists of ranges
+ :return: Property contents
+ """
def formatrange((start, end, inheritable)):
suffix = ""
if not inheritable:
else:
return "%d-%d%s" % (start, end, suffix)
text = ""
- for (path, ranges) in merges.items():
+ for (path, ranges) in merges.iteritems():
assert path.startswith("/")
text += "%s:%s\n" % (path, ",".join(map(formatrange, ranges)))
return text
def range_includes_revnum(ranges, revnum):
+ """Check if the specified range contains the mentioned revision number.
+
+ :param ranges: list of ranges
+ :param revnum: revision number
+ :return: Whether or not the revision number is included
+ """
i = bisect.bisect(ranges, (revnum, revnum, True))
if i == 0:
return False
def range_add_revnum(ranges, revnum, inheritable=True):
+ """Add revision number to a list of ranges
+
+ :param ranges: List of ranges
+ :param revnum: Revision number to add
+ :param inheritable: TODO
+ :return: New list of ranges
+ """
# TODO: Deal with inheritable
item = (revnum, revnum, inheritable)
if len(ranges) == 0:
def mergeinfo_includes_revision(merges, path, revnum):
+ """Check if the specified mergeinfo contains a path in revnum.
+
+ :param merges: Dictionary with merges
+ :param path: Merged path
+ :param revnum: Revision number
+ :return: Whether the revision is included
+ """
assert path.startswith("/")
try:
ranges = merges[path]
def mergeinfo_add_revision(mergeinfo, path, revnum):
+ """Add a revision to a mergeinfo dictionary
+
+ :param mergeinfo: Merginfo dictionary
+ :param path: Merged path to add
+ :param revnum: Merged revision to add
+ :return: Updated dictionary
+ """
assert path.startswith("/")
mergeinfo[path] = range_add_revnum(mergeinfo.get(path, []), revnum)
return mergeinfo
PROP_REVISION_LOG = "svn:log"
PROP_REVISION_AUTHOR = "svn:author"
PROP_REVISION_DATE = "svn:date"
+PROP_REVISION_ORIGINAL_DATE = "svn:original-date"
def diff(current, previous):
+ """Find the differences between two property dictionaries.
+
+ :param current: Dictionary with current (new) properties
+ :param previous: Dictionary with previous (old) properties
+ :return: Dictionary that contains an entry for
+ each property that was changed. Value is a tuple
+ with the old and the new property value.
+ """
ret = {}
- for key, val in current.items():
- if previous.get(key) != val:
- ret[key] = val
+ for key, newval in current.iteritems():
+ oldval = previous.get(key)
+ if oldval != newval:
+ ret[key] = (oldval, newval)
return ret