1 # Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 from bzrlib import urlutils
20 class InvalidExternalsDescription(Exception):
21 _fmt = """Unable to parse externals description."""
24 def is_valid_property_name(prop):
25 if not prop[0].isalnum() and not prop[0] in ":_":
28 if not c.isalnum() and not c in "-:._":
33 def time_to_cstring(timestamp):
35 tm_usec = timestamp % 1000000
36 (tm_year, tm_mon, tm_mday, tm_hour, tm_min,
37 tm_sec, tm_wday, tm_yday, tm_isdst) = time.gmtime(timestamp / 1000000)
38 return "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ" % (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_usec)
41 def time_from_cstring(text):
43 (basestr, usecstr) = text.split(".", 1)
44 assert usecstr[-1] == "Z"
45 tm_usec = int(usecstr[:-1])
46 tm = time.strptime(basestr, "%Y-%m-%dT%H:%M:%S")
47 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)
50 def parse_externals_description(base_url, val):
51 """Parse an svn:externals property value.
53 :param base_url: URL on which the property is set. Used for
56 :returns: dictionary with local names as keys, (revnum, url)
57 as value. revnum is the revision number and is
58 set to None if not applicable.
63 for l in val.splitlines():
64 if l == "" or l[0] == "#":
66 pts = l.rsplit(None, 3)
68 if pts[0] == "-r": # -r X URL DIR
72 elif pts[1] == "-r": # DIR -r X URL
77 raise InvalidExternalsDescription()
79 if pts[1].startswith("-r"): # DIR -rX URL
80 revno = int(pts[1][2:])
83 elif pts[0].startswith("-r"): # -rX URL DIR
84 revno = int(pts[0][2:])
88 raise InvalidExternalsDescription()
90 if not is_url(pts[0]):
98 raise InvalidExternalsDescription()
99 if relurl.startswith("//"):
100 raise NotImplementedError("Relative to the scheme externals not yet supported")
101 if relurl.startswith("^/"):
102 raise NotImplementedError("Relative to the repository root externals not yet supported")
103 ret[path] = (revno, "%s/%s" % (base_url, relurl))
107 def parse_mergeinfo_property(text):
109 for l in text.splitlines():
110 (path, ranges) = l.rsplit(":", 1)
111 assert path.startswith("/")
113 for range in ranges.split(","):
120 (start, end) = range.split("-", 1)
121 ret[path].append((int(start), int(end), inheritable))
123 ret[path].append((int(range), int(range), inheritable))
128 def generate_mergeinfo_property(merges):
129 def formatrange((start, end, inheritable)):
134 return "%d%s" % (start, suffix)
136 return "%d-%d%s" % (start, end, suffix)
138 for (path, ranges) in merges.items():
139 assert path.startswith("/")
140 text += "%s:%s\n" % (path, ",".join(map(formatrange, ranges)))
144 def range_includes_revnum(ranges, revnum):
145 i = bisect.bisect(ranges, (revnum, revnum, True))
148 (start, end, inheritable) = ranges[i-1]
149 return (start <= revnum <= end)
152 def range_add_revnum(ranges, revnum, inheritable=True):
153 # TODO: Deal with inheritable
154 item = (revnum, revnum, inheritable)
158 i = bisect.bisect(ranges, item)
160 (start, end, inh) = ranges[i-1]
161 if (start <= revnum <= end):
165 # Extend previous range
166 ranges[i-1] = (start, end+1, inh)
169 (start, end, inh) = ranges[i]
170 if start-1 == revnum:
172 ranges[i] = (start-1, end, inh)
174 ranges.insert(i, item)
178 def mergeinfo_includes_revision(merges, path, revnum):
179 assert path.startswith("/")
181 ranges = merges[path]
185 return range_includes_revnum(ranges, revnum)
188 def mergeinfo_add_revision(mergeinfo, path, revnum):
189 assert path.startswith("/")
190 mergeinfo[path] = range_add_revnum(mergeinfo.get(path, []), revnum)
194 PROP_EXECUTABLE = 'svn:executable'
195 PROP_EXECUTABLE_VALUE = '*'
196 PROP_EXTERNALS = 'svn:externals'
197 PROP_IGNORE = 'svn:ignore'
198 PROP_KEYWORDS = 'svn:keywords'
199 PROP_MIME_TYPE = 'svn:mime-type'
200 PROP_MERGEINFO = 'svn:mergeinfo'
201 PROP_NEEDS_LOCK = 'svn:needs-lock'
202 PROP_NEEDS_LOCK_VALUE = '*'
204 PROP_SPECIAL = 'svn:special'
205 PROP_SPECIAL_VALUE = '*'
206 PROP_WC_PREFIX = 'svn:wc:'
207 PROP_ENTRY_PREFIX = 'svn:entry'
208 PROP_ENTRY_COMMITTED_DATE = 'svn:entry:committed-date'
209 PROP_ENTRY_COMMITTED_REV = 'svn:entry:committed-rev'
210 PROP_ENTRY_LAST_AUTHOR = 'svn:entry:last-author'
211 PROP_ENTRY_LOCK_TOKEN = 'svn:entry:lock-token'
212 PROP_ENTRY_UUID = 'svn:entry:uuid'
214 PROP_REVISION_LOG = "svn:log"
215 PROP_REVISION_AUTHOR = "svn:author"
216 PROP_REVISION_DATE = "svn:date"
218 def diff(current, previous):
220 for key, val in current.items():
221 if previous.get(key) != val: