Update my email address.
[jelmer/subvertpy.git] / subvertpy / properties.py
index 4fa009b0809fb38454132f7d906b6302f2d04fa4..37f310c3cd52d11057b843e708b19b24acae0029 100644 (file)
@@ -1,19 +1,24 @@
-# 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):
@@ -21,6 +26,11 @@ 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:]:
@@ -30,7 +40,11 @@ def is_valid_property_name(prop):
 
 
 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)
@@ -38,12 +52,16 @@ def time_to_cstring(timestamp):
 
 
 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):
@@ -99,11 +117,15 @@ 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)
@@ -125,6 +147,11 @@ def parse_mergeinfo_property(text):
 
 
 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:
@@ -134,13 +161,19 @@ def generate_mergeinfo_property(merges):
         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
@@ -149,6 +182,13 @@ def range_includes_revnum(ranges, revnum):
 
 
 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:
@@ -175,6 +215,13 @@ def range_add_revnum(ranges, revnum, inheritable=True):
 
 
 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]
@@ -185,6 +232,13 @@ def mergeinfo_includes_revision(merges, path, revnum):
 
 
 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
@@ -213,10 +267,20 @@ PROP_ENTRY_UUID = 'svn:entry:uuid'
 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