* cvs2svn: Use gnu_getopt when available (Python >= 2.3) for more flexible
[cvs2svn.git] / cvs2svn_lib / property_setters.py
blob2c3d54d3d242bc6794f9441379b659fda932b24a
1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2006 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."""
20 import sys
21 import os
22 import fnmatch
23 import fileinput
24 import ConfigParser
26 from boolean import *
27 from common import warning_prefix
28 from log import Log
31 class SVNPropertySetter:
32 """Abstract class for objects that can set properties on a SVNCommitItem."""
34 def set_properties(self, s_item):
35 """Set any properties that can be determined for S_ITEM."""
37 raise NotImplementedError
40 class CVSRevisionNumberSetter(SVNPropertySetter):
41 """Set the cvs2svn:cvs-rev property to the CVS revision number."""
43 def set_properties(self, s_item):
44 s_item.svn_props['cvs2svn:cvs-rev'] = s_item.c_rev.rev
45 s_item.svn_props_changed = True
48 class ExecutablePropertySetter(SVNPropertySetter):
49 """Set the svn:executable property based on c_rev.cvs_file.executable."""
51 def set_properties(self, s_item):
52 if s_item.c_rev.cvs_file.executable:
53 s_item.svn_props['svn:executable'] = '*'
56 class BinaryFileEOLStyleSetter(SVNPropertySetter):
57 """Set the eol-style for binary files to None."""
59 def set_properties(self, s_item):
60 if s_item.c_rev.cvs_file.mode == 'b':
61 s_item.svn_props['svn:eol-style'] = None
64 class MimeMapper(SVNPropertySetter):
65 """A class that provides mappings from file names to MIME types."""
67 def __init__(self, mime_types_file):
68 self.mappings = { }
70 for line in fileinput.input(mime_types_file):
71 if line.startswith("#"):
72 continue
74 # format of a line is something like
75 # text/plain c h cpp
76 extensions = line.split()
77 if len(extensions) < 2:
78 continue
79 type = extensions.pop(0)
80 for ext in extensions:
81 if self.mappings.has_key(ext) and self.mappings[ext] != type:
82 sys.stderr.write("%s: ambiguous MIME mapping for *.%s (%s or %s)\n"
83 % (warning_prefix, ext, self.mappings[ext], type))
84 self.mappings[ext] = type
86 def set_properties(self, s_item):
87 basename, extension = os.path.splitext(
88 os.path.basename(s_item.c_rev.cvs_path)
91 # Extension includes the dot, so strip it (will leave extension
92 # empty if filename ends with a dot, which is ok):
93 extension = extension[1:]
95 # If there is no extension (or the file ends with a period), use
96 # the base name for mapping. This allows us to set mappings for
97 # files such as README or Makefile:
98 if not extension:
99 extension = basename
101 mime_type = self.mappings.get(extension, None)
102 if mime_type is not None:
103 s_item.svn_props['svn:mime-type'] = mime_type
106 class AutoPropsPropertySetter(SVNPropertySetter):
107 """Set arbitrary svn properties based on an auto-props configuration.
109 This class supports case-sensitive or case-insensitive pattern
110 matching. The 'correct' behavior is not quite clear, because
111 subversion itself does an inconsistent job of handling case in
112 auto-props patterns; see
113 http://subversion.tigris.org/issues/show_bug.cgi?id=2036.
115 If a property specified in auto-props has already been set to a
116 different value, print a warning and leave the old property value
117 unchanged."""
119 class Pattern:
120 """Describes the properties to be set for files matching a pattern."""
122 def __init__(self, pattern, propdict):
123 # A glob-like pattern:
124 self.pattern = pattern
125 # A dictionary of properties that should be set:
126 self.propdict = propdict
128 def match(self, basename):
129 """Does the file with the specified basename match pattern?"""
131 return fnmatch.fnmatch(basename, self.pattern)
133 def __init__(self, configfilename, ignore_case):
134 config = ConfigParser.ConfigParser()
135 if ignore_case:
136 self.transform_case = self.squash_case
137 else:
138 config.optionxform = self.preserve_case
139 self.transform_case = self.preserve_case
141 config.readfp(file(configfilename))
142 self.patterns = []
143 for section in config.sections():
144 if self.transform_case(section) == 'auto-props':
145 for pattern in config.options(section):
146 value = config.get(section, pattern)
147 if value:
148 self._add_pattern(pattern, value)
150 def squash_case(self, s):
151 return s.lower()
153 def preserve_case(self, s):
154 return s
156 def _add_pattern(self, pattern, value):
157 props = value.split(';')
158 propdict = {}
159 for prop in props:
160 s = prop.split('=', 1)
161 if len(s) == 1:
162 propdict[s[0]] = None
163 else:
164 propdict[s[0]] = s[1]
165 self.patterns.append(
166 self.Pattern(self.transform_case(pattern), propdict))
168 def get_propdict(self, path):
169 basename = self.transform_case(os.path.basename(path))
170 propdict = {}
171 for pattern in self.patterns:
172 if pattern.match(basename):
173 for (key,value) in pattern.propdict.items():
174 if propdict.has_key(key):
175 if propdict[key] != value:
176 Log().warn(
177 "Contradictory values set for property '%s' for file %s."
178 % (key, path,))
179 else:
180 propdict[key] = value
182 return propdict
184 def set_properties(self, s_item):
185 propdict = self.get_propdict(s_item.c_rev.cvs_path)
186 for (k,v) in propdict.items():
187 if s_item.svn_props.has_key(k):
188 if s_item.svn_props[k] != v:
189 Log().warn(
190 "Property '%s' already set to %r for file %s; "
191 "auto-props value (%r) ignored."
192 % (k, s_item.svn_props[k], s_item.c_rev.cvs_path, v,))
193 else:
194 s_item.svn_props[k] = v
197 class BinaryFileDefaultMimeTypeSetter(SVNPropertySetter):
198 """If the file is binary and its svn:mime-type property is not yet
199 set, set it to 'application/octet-stream'."""
201 def set_properties(self, s_item):
202 if not s_item.svn_props.has_key('svn:mime-type') \
203 and s_item.c_rev.cvs_file.mode == 'b':
204 s_item.svn_props['svn:mime-type'] = 'application/octet-stream'
207 class EOLStyleFromMimeTypeSetter(SVNPropertySetter):
208 """Set svn:eol-style based on svn:mime-type.
210 If svn:mime-type is known but svn:eol-style is not, then set
211 svn:eol-style based on svn:mime-type as follows: if svn:mime-type
212 starts with 'text/', then set svn:eol-style to native; otherwise,
213 force it to remain unset. See also issue #39."""
215 def set_properties(self, s_item):
216 if not s_item.svn_props.has_key('svn:eol-style') \
217 and s_item.svn_props.get('svn:mime-type', None) is not None:
218 if s_item.svn_props['svn:mime-type'].startswith("text/"):
219 s_item.svn_props['svn:eol-style'] = 'native'
220 else:
221 s_item.svn_props['svn:eol-style'] = None
224 class DefaultEOLStyleSetter(SVNPropertySetter):
225 """Set the eol-style if one has not already been set."""
227 def __init__(self, value):
228 """Initialize with the specified default VALUE."""
230 self.value = value
232 def set_properties(self, s_item):
233 if not s_item.svn_props.has_key('svn:eol-style'):
234 s_item.svn_props['svn:eol-style'] = self.value
237 class KeywordsPropertySetter(SVNPropertySetter):
238 """If the svn:keywords property is not yet set, set it based on the
239 file's mode. See issue #2."""
241 def __init__(self, value):
242 """Use VALUE for the value of the svn:keywords property if it is
243 to be set."""
245 self.value = value
247 def set_properties(self, s_item):
248 if not s_item.svn_props.has_key('svn:keywords') \
249 and s_item.c_rev.cvs_file.mode in [None, 'kv', 'kvl']:
250 s_item.svn_props['svn:keywords'] = self.value