1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2007 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://cvs2svn.tigris.org/.
15 # ====================================================================
17 """This module contains classes to set Subversion properties on files."""
24 from cStringIO
import StringIO
26 from cvs2svn_lib
.common
import warning_prefix
27 from cvs2svn_lib
.log
import Log
30 class SVNPropertySetter
:
31 """Abstract class for objects that can set properties on a SVNCommitItem."""
33 def set_properties(self
, s_item
):
34 """Set any properties that can be determined for S_ITEM.
36 S_ITEM is an instance of SVNCommitItem. This method should modify
37 S_ITEM.svn_props in place."""
39 raise NotImplementedError
42 class CVSRevisionNumberSetter(SVNPropertySetter
):
43 """Set the cvs2svn:cvs-rev property to the CVS revision number."""
45 propname
= 'cvs2svn:cvs-rev'
47 def set_properties(self
, s_item
):
48 if self
.propname
in s_item
.svn_props
:
51 s_item
.svn_props
[self
.propname
] = s_item
.cvs_rev
.rev
52 s_item
.svn_props_changed
= True
55 class ExecutablePropertySetter(SVNPropertySetter
):
56 """Set the svn:executable property based on cvs_rev.cvs_file.executable."""
58 propname
= 'svn:executable'
60 def set_properties(self
, s_item
):
61 if self
.propname
in s_item
.svn_props
:
64 if s_item
.cvs_rev
.cvs_file
.executable
:
65 s_item
.svn_props
[self
.propname
] = '*'
68 class CVSBinaryFileEOLStyleSetter(SVNPropertySetter
):
69 """Set the eol-style to None for files with CVS mode '-kb'."""
71 propname
= 'svn:eol-style'
73 def set_properties(self
, s_item
):
74 if self
.propname
in s_item
.svn_props
:
77 if s_item
.cvs_rev
.cvs_file
.mode
== 'b':
78 s_item
.svn_props
[self
.propname
] = None
81 class MimeMapper(SVNPropertySetter
):
82 """A class that provides mappings from file names to MIME types."""
84 propname
= 'svn:mime-type'
86 def __init__(self
, mime_types_file
):
89 for line
in file(mime_types_file
):
90 if line
.startswith("#"):
93 # format of a line is something like
95 extensions
= line
.split()
96 if len(extensions
) < 2:
98 type = extensions
.pop(0)
99 for ext
in extensions
:
100 if ext
in self
.mappings
and self
.mappings
[ext
] != type:
102 "%s: ambiguous MIME mapping for *.%s (%s or %s)\n"
103 % (warning_prefix
, ext
, self
.mappings
[ext
], type)
105 self
.mappings
[ext
] = type
107 def set_properties(self
, s_item
):
108 if self
.propname
in s_item
.svn_props
:
111 basename
, extension
= os
.path
.splitext(s_item
.cvs_rev
.cvs_file
.basename
)
113 # Extension includes the dot, so strip it (will leave extension
114 # empty if filename ends with a dot, which is ok):
115 extension
= extension
[1:]
117 # If there is no extension (or the file ends with a period), use
118 # the base name for mapping. This allows us to set mappings for
119 # files such as README or Makefile:
123 mime_type
= self
.mappings
.get(extension
, None)
124 if mime_type
is not None:
125 s_item
.svn_props
[self
.propname
] = mime_type
128 class AutoPropsPropertySetter(SVNPropertySetter
):
129 """Set arbitrary svn properties based on an auto-props configuration.
131 This class supports case-sensitive or case-insensitive pattern
132 matching. The command-line default is case-insensitive behavior,
133 consistent with Subversion (see
134 http://subversion.tigris.org/issues/show_bug.cgi?id=2036).
136 As a special extension to Subversion's auto-props handling, if a
137 property name is preceded by a '!' then that property is forced to
140 If a property specified in auto-props has already been set to a
141 different value, print a warning and leave the old property value
144 Python's treatment of whitespaces in the ConfigParser module is
145 buggy and inconsistent. Usually spaces are preserved, but if there
146 is at least one semicolon in the value, and the *first* semicolon is
147 preceded by a space, then that is treated as the start of a comment
148 and the rest of the line is silently discarded."""
150 property_name_pattern
= r
'(?P<name>[^\!\=\s]+)'
151 property_unset_re
= re
.compile(
152 r
'^\!\s*' + property_name_pattern
+ r
'$'
154 property_set_re
= re
.compile(
155 r
'^' + property_name_pattern
+ r
'\s*\=\s*(?P<value>.*)$'
157 property_novalue_re
= re
.compile(
158 r
'^' + property_name_pattern
+ r
'$'
161 quoted_re
= re
.compile(
164 comment_re = re.compile(r'\s
;')
167 """Describes the properties to be set for files matching a pattern."""
169 def __init__(self, pattern, propdict):
170 # A glob-like pattern:
171 self.pattern = pattern
172 # A dictionary of properties that should be set:
173 self.propdict = propdict
175 def match(self, basename):
176 """Does the file with the specified basename match pattern?"""
178 return fnmatch.fnmatch(basename, self.pattern)
180 def __init__(self, configfilename, ignore_case=True):
181 config = ConfigParser.ConfigParser()
183 self.transform_case = self.squash_case
185 config.optionxform = self.preserve_case
186 self.transform_case = self.preserve_case
188 configtext = open(configfilename).read()
189 if self.comment_re.search(configtext):
191 '%s: Please be aware that a space followed by a
\n'
192 'semicolon
is sometimes treated
as a comment
in configuration
\n'
193 'files
. This pattern was seen
in\n'
195 'Please make sure that you have
not inadvertently commented
\n'
196 'out part of an important line
.'
197 % (warning_prefix, configfilename,)
200 config.readfp(StringIO(configtext), configfilename)
202 sections = config.sections()
204 for section in sections:
205 if self.transform_case(section) == 'auto
-props
':
206 patterns = config.options(section)
208 for pattern in patterns:
209 value = config.get(section, pattern)
211 self._add_pattern(pattern, value)
213 def squash_case(self, s):
216 def preserve_case(self, s):
219 def _add_pattern(self, pattern, props):
221 if self.quoted_re.match(pattern):
223 '%s: Quoting
is not supported
in auto
-props
; please verify rule
\n'
224 'for %r. (Using pattern including quotation marks
.)\n'
225 % (warning_prefix, pattern,)
227 for prop in props.split(';'):
229 m = self.property_unset_re.match(prop)
231 name = m.group('name
')
233 'auto
-props
: For
%r, leaving
%r unset
.' % (pattern, name,)
235 propdict[name] = None
238 m = self.property_set_re.match(prop)
240 name = m.group('name
')
241 value = m.group('value
')
242 if self.quoted_re.match(value):
244 '%s: Quoting
is not supported
in auto
-props
; please verify
\n'
245 'rule
%r for pattern
%r. (Using value
\n'
246 'including quotation marks
.)\n'
247 % (warning_prefix, prop, pattern,)
250 'auto
-props
: For
%r, setting
%r to
%r.' % (pattern, name, value,)
252 propdict[name] = value
255 m = self.property_novalue_re.match(prop)
257 name = m.group('name
')
259 'auto
-props
: For
%r, setting
%r to the empty string
'
266 '%s: in auto
-props line
for %r, value
%r cannot be
parsed (ignored
)'
267 % (warning_prefix, pattern, prop,)
270 self.patterns.append(self.Pattern(self.transform_case(pattern), propdict))
272 def get_propdict(self, cvs_file):
273 basename = self.transform_case(cvs_file.basename)
275 for pattern in self.patterns:
276 if pattern.match(basename):
277 for (key,value) in pattern.propdict.items():
279 if propdict[key] != value:
281 "Contradictory values set for property '%s' for file %s."
284 propdict[key] = value
288 def set_properties(self, s_item):
289 propdict = self.get_propdict(s_item.cvs_rev.cvs_file)
290 for (k,v) in propdict.items():
291 if k in s_item.svn_props:
292 if s_item.svn_props[k] != v:
294 "Property '%s' already set to %r for file %s; "
295 "auto-props value (%r) ignored."
296 % (k, s_item.svn_props[k], s_item.cvs_rev.cvs_path, v,))
298 s_item.svn_props[k] = v
301 class CVSBinaryFileDefaultMimeTypeSetter(SVNPropertySetter):
302 """If the file is binary and its svn:mime-type property is not yet
303 set, set it to 'application
/octet
-stream
'."""
305 propname = 'svn
:mime
-type'
307 def set_properties(self, s_item):
308 if self.propname in s_item.svn_props:
311 if s_item.cvs_rev.cvs_file.mode == 'b
':
312 s_item.svn_props[self.propname] = 'application
/octet
-stream
'
315 class EOLStyleFromMimeTypeSetter(SVNPropertySetter):
316 """Set svn:eol-style based on svn:mime-type.
318 If svn:mime-type is known but svn:eol-style is not, then set
319 svn:eol-style based on svn:mime-type as follows: if svn:mime-type
320 starts with 'text
/', then set svn:eol-style to native; otherwise,
321 force it to remain unset. See also issue #39."""
323 propname = 'svn
:eol
-style
'
325 def set_properties(self, s_item):
326 if self.propname in s_item.svn_props:
329 if s_item.svn_props.get('svn
:mime
-type', None) is not None:
330 if s_item.svn_props['svn
:mime
-type'].startswith("text/"):
331 s_item.svn_props[self.propname] = 'native
'
333 s_item.svn_props[self.propname] = None
336 class DefaultEOLStyleSetter(SVNPropertySetter):
337 """Set the eol-style if one has not already been set."""
339 propname = 'svn
:eol
-style
'
341 def __init__(self, value):
342 """Initialize with the specified default VALUE."""
346 def set_properties(self, s_item):
347 if self.propname in s_item.svn_props:
350 s_item.svn_props[self.propname] = self.value
353 class SVNBinaryFileKeywordsPropertySetter(SVNPropertySetter):
354 """Turn off svn:keywords for files with binary svn:eol-style."""
356 propname = 'svn
:keywords
'
358 def set_properties(self, s_item):
359 if self.propname in s_item.svn_props:
362 if not s_item.svn_props.get('svn
:eol
-style
'):
363 s_item.svn_props[self.propname] = None
366 class KeywordsPropertySetter(SVNPropertySetter):
367 """If the svn:keywords property is not yet set, set it based on the
368 file's mode
. See issue
#2."""
370 propname
= 'svn:keywords'
372 def __init__(self
, value
):
373 """Use VALUE for the value of the svn:keywords property if it is
378 def set_properties(self
, s_item
):
379 if self
.propname
in s_item
.svn_props
:
382 if s_item
.cvs_rev
.cvs_file
.mode
in [None, 'kv', 'kvl']:
383 s_item
.svn_props
[self
.propname
] = self
.value