1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2008 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 DescriptionPropertySetter(SVNPropertySetter
):
69 """Set the svn:description property based on cvs_rev.cvs_file.description."""
71 propname
= 'cvs:description'
73 def set_properties(self
, s_item
):
74 if self
.propname
in s_item
.svn_props
:
77 if s_item
.cvs_rev
.cvs_file
.description
:
78 s_item
.svn_props
[self
.propname
] = s_item
.cvs_rev
.cvs_file
.description
81 class CVSBinaryFileEOLStyleSetter(SVNPropertySetter
):
82 """Set the eol-style to None for files with CVS mode '-kb'."""
84 propname
= 'svn:eol-style'
86 def set_properties(self
, s_item
):
87 if self
.propname
in s_item
.svn_props
:
90 if s_item
.cvs_rev
.cvs_file
.mode
== 'b':
91 s_item
.svn_props
[self
.propname
] = None
94 class MimeMapper(SVNPropertySetter
):
95 """A class that provides mappings from file names to MIME types."""
97 propname
= 'svn:mime-type'
99 def __init__(self
, mime_types_file
):
102 for line
in file(mime_types_file
):
103 if line
.startswith("#"):
106 # format of a line is something like
108 extensions
= line
.split()
109 if len(extensions
) < 2:
111 type = extensions
.pop(0)
112 for ext
in extensions
:
113 if ext
in self
.mappings
and self
.mappings
[ext
] != type:
115 "%s: ambiguous MIME mapping for *.%s (%s or %s)\n"
116 % (warning_prefix
, ext
, self
.mappings
[ext
], type)
118 self
.mappings
[ext
] = type
120 def set_properties(self
, s_item
):
121 if self
.propname
in s_item
.svn_props
:
124 basename
, extension
= os
.path
.splitext(s_item
.cvs_rev
.cvs_file
.basename
)
126 # Extension includes the dot, so strip it (will leave extension
127 # empty if filename ends with a dot, which is ok):
128 extension
= extension
[1:]
130 # If there is no extension (or the file ends with a period), use
131 # the base name for mapping. This allows us to set mappings for
132 # files such as README or Makefile:
136 mime_type
= self
.mappings
.get(extension
, None)
137 if mime_type
is not None:
138 s_item
.svn_props
[self
.propname
] = mime_type
141 class AutoPropsPropertySetter(SVNPropertySetter
):
142 """Set arbitrary svn properties based on an auto-props configuration.
144 This class supports case-sensitive or case-insensitive pattern
145 matching. The command-line default is case-insensitive behavior,
146 consistent with Subversion (see
147 http://subversion.tigris.org/issues/show_bug.cgi?id=2036).
149 As a special extension to Subversion's auto-props handling, if a
150 property name is preceded by a '!' then that property is forced to
153 If a property specified in auto-props has already been set to a
154 different value, print a warning and leave the old property value
157 Python's treatment of whitespaces in the ConfigParser module is
158 buggy and inconsistent. Usually spaces are preserved, but if there
159 is at least one semicolon in the value, and the *first* semicolon is
160 preceded by a space, then that is treated as the start of a comment
161 and the rest of the line is silently discarded."""
163 property_name_pattern
= r
'(?P<name>[^\!\=\s]+)'
164 property_unset_re
= re
.compile(
165 r
'^\!\s*' + property_name_pattern
+ r
'$'
167 property_set_re
= re
.compile(
168 r
'^' + property_name_pattern
+ r
'\s*\=\s*(?P<value>.*)$'
170 property_novalue_re
= re
.compile(
171 r
'^' + property_name_pattern
+ r
'$'
174 quoted_re
= re
.compile(
177 comment_re = re.compile(r'\s
;')
180 """Describes the properties to be set for files matching a pattern."""
182 def __init__(self, pattern, propdict):
183 # A glob-like pattern:
184 self.pattern = pattern
185 # A dictionary of properties that should be set:
186 self.propdict = propdict
188 def match(self, basename):
189 """Does the file with the specified basename match pattern?"""
191 return fnmatch.fnmatch(basename, self.pattern)
193 def __init__(self, configfilename, ignore_case=True):
194 config = ConfigParser.ConfigParser()
196 self.transform_case = self.squash_case
198 config.optionxform = self.preserve_case
199 self.transform_case = self.preserve_case
201 configtext = open(configfilename).read()
202 if self.comment_re.search(configtext):
204 '%s: Please be aware that a space followed by a
\n'
205 'semicolon
is sometimes treated
as a comment
in configuration
\n'
206 'files
. This pattern was seen
in\n'
208 'Please make sure that you have
not inadvertently commented
\n'
209 'out part of an important line
.'
210 % (warning_prefix, configfilename,)
213 config.readfp(StringIO(configtext), configfilename)
215 sections = config.sections()
217 for section in sections:
218 if self.transform_case(section) == 'auto
-props
':
219 patterns = config.options(section)
221 for pattern in patterns:
222 value = config.get(section, pattern)
224 self._add_pattern(pattern, value)
226 def squash_case(self, s):
229 def preserve_case(self, s):
232 def _add_pattern(self, pattern, props):
234 if self.quoted_re.match(pattern):
236 '%s: Quoting
is not supported
in auto
-props
; please verify rule
\n'
237 'for %r. (Using pattern including quotation marks
.)\n'
238 % (warning_prefix, pattern,)
240 for prop in props.split(';'):
242 m = self.property_unset_re.match(prop)
244 name = m.group('name
')
246 'auto
-props
: For
%r, leaving
%r unset
.' % (pattern, name,)
248 propdict[name] = None
251 m = self.property_set_re.match(prop)
253 name = m.group('name
')
254 value = m.group('value
')
255 if self.quoted_re.match(value):
257 '%s: Quoting
is not supported
in auto
-props
; please verify
\n'
258 'rule
%r for pattern
%r. (Using value
\n'
259 'including quotation marks
.)\n'
260 % (warning_prefix, prop, pattern,)
263 'auto
-props
: For
%r, setting
%r to
%r.' % (pattern, name, value,)
265 propdict[name] = value
268 m = self.property_novalue_re.match(prop)
270 name = m.group('name
')
272 'auto
-props
: For
%r, setting
%r to the empty string
'
279 '%s: in auto
-props line
for %r, value
%r cannot be
parsed (ignored
)'
280 % (warning_prefix, pattern, prop,)
283 self.patterns.append(self.Pattern(self.transform_case(pattern), propdict))
285 def get_propdict(self, cvs_file):
286 basename = self.transform_case(cvs_file.basename)
288 for pattern in self.patterns:
289 if pattern.match(basename):
290 for (key,value) in pattern.propdict.items():
292 if propdict[key] != value:
294 "Contradictory values set for property '%s' for file %s."
297 propdict[key] = value
301 def set_properties(self, s_item):
302 propdict = self.get_propdict(s_item.cvs_rev.cvs_file)
303 for (k,v) in propdict.items():
304 if k in s_item.svn_props:
305 if s_item.svn_props[k] != v:
307 "Property '%s' already set to %r for file %s; "
308 "auto-props value (%r) ignored."
309 % (k, s_item.svn_props[k], s_item.cvs_rev.cvs_path, v,))
311 s_item.svn_props[k] = v
314 class CVSBinaryFileDefaultMimeTypeSetter(SVNPropertySetter):
315 """If the file is binary and its svn:mime-type property is not yet
316 set, set it to 'application
/octet
-stream
'."""
318 propname = 'svn
:mime
-type'
320 def set_properties(self, s_item):
321 if self.propname in s_item.svn_props:
324 if s_item.cvs_rev.cvs_file.mode == 'b
':
325 s_item.svn_props[self.propname] = 'application
/octet
-stream
'
328 class EOLStyleFromMimeTypeSetter(SVNPropertySetter):
329 """Set svn:eol-style based on svn:mime-type.
331 If svn:mime-type is known but svn:eol-style is not, then set
332 svn:eol-style based on svn:mime-type as follows: if svn:mime-type
333 starts with 'text
/', then set svn:eol-style to native; otherwise,
334 force it to remain unset. See also issue #39."""
336 propname = 'svn
:eol
-style
'
338 def set_properties(self, s_item):
339 if self.propname in s_item.svn_props:
342 if s_item.svn_props.get('svn
:mime
-type', None) is not None:
343 if s_item.svn_props['svn
:mime
-type'].startswith("text/"):
344 s_item.svn_props[self.propname] = 'native
'
346 s_item.svn_props[self.propname] = None
349 class DefaultEOLStyleSetter(SVNPropertySetter):
350 """Set the eol-style if one has not already been set."""
352 propname = 'svn
:eol
-style
'
354 def __init__(self, value):
355 """Initialize with the specified default VALUE."""
359 def set_properties(self, s_item):
360 if self.propname in s_item.svn_props:
363 s_item.svn_props[self.propname] = self.value
366 class SVNBinaryFileKeywordsPropertySetter(SVNPropertySetter):
367 """Turn off svn:keywords for files with binary svn:eol-style."""
369 propname = 'svn
:keywords
'
371 def set_properties(self, s_item):
372 if self.propname in s_item.svn_props:
375 if not s_item.svn_props.get('svn
:eol
-style
'):
376 s_item.svn_props[self.propname] = None
379 class KeywordsPropertySetter(SVNPropertySetter):
380 """If the svn:keywords property is not yet set, set it based on the
381 file's mode
. See issue
#2."""
383 propname
= 'svn:keywords'
385 def __init__(self
, value
):
386 """Use VALUE for the value of the svn:keywords property if it is
391 def set_properties(self
, s_item
):
392 if self
.propname
in s_item
.svn_props
:
395 if s_item
.cvs_rev
.cvs_file
.mode
in [None, 'kv', 'kvl']:
396 s_item
.svn_props
[self
.propname
] = self
.value