fix timezones in darcs-fast-export, take 2
[bzr-fastimport/rorcz.git] / helpers.py
blob34d4688949f982e166abac4168c1476f3e7bbbdd
1 # Copyright (C) 2008 Canonical Ltd
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 2 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, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 """Miscellaneous useful stuff."""
20 def single_plural(n, single, plural):
21 """Return a single or plural form of a noun based on number."""
22 if n == 1:
23 return single
24 else:
25 return plural
28 def defines_to_dict(defines):
29 """Convert a list of definition strings to a dictionary."""
30 if defines is None:
31 return None
32 result = {}
33 for define in defines:
34 kv = define.split('=', 1)
35 if len(kv) == 1:
36 result[define.strip()] = 1
37 else:
38 result[kv[0].strip()] = kv[1].strip()
39 return result
42 def invert_dict(d):
43 """Invert a dictionary with keys matching each value turned into a list."""
44 # Based on recipe from ASPN
45 result = {}
46 for k, v in d.iteritems():
47 keys = result.setdefault(v, [])
48 keys.append(k)
49 return result
52 def invert_dictset(d):
53 """Invert a dictionary with keys matching a set of values, turned into lists."""
54 # Based on recipe from ASPN
55 result = {}
56 for k, c in d.iteritems():
57 for v in c:
58 keys = result.setdefault(v, [])
59 keys.append(k)
60 return result
63 def _common_path_and_rest(l1, l2, common=[]):
64 # From http://code.activestate.com/recipes/208993/
65 if len(l1) < 1: return (common, l1, l2)
66 if len(l2) < 1: return (common, l1, l2)
67 if l1[0] != l2[0]: return (common, l1, l2)
68 return _common_path_and_rest(l1[1:], l2[1:], common+[l1[0]])
71 def common_path(path1, path2):
72 """Find the common bit of 2 paths."""
73 return ''.join(_common_path_and_rest(path1, path2)[0])
76 def common_directory(paths):
77 """Find the deepest common directory of a list of paths.
79 :return: if no paths are provided, None is returned;
80 if there is no common directory, '' is returned;
81 otherwise the common directory with a trailing / is returned.
82 """
83 from bzrlib import osutils
84 def get_dir_with_slash(path):
85 if path == '' or path.endswith('/'):
86 return path
87 else:
88 dirname, basename = osutils.split(path)
89 if dirname == '':
90 return dirname
91 else:
92 return dirname + '/'
94 if not paths:
95 return None
96 elif len(paths) == 1:
97 return get_dir_with_slash(paths[0])
98 else:
99 common = common_path(paths[0], paths[1])
100 for path in paths[2:]:
101 common = common_path(common, path)
102 return get_dir_with_slash(common)
105 def escape_commit_message(message):
106 """Replace xml-incompatible control characters."""
107 # This really ought to be provided by bzrlib.
108 # Code copied from bzrlib.commit.
110 # Python strings can include characters that can't be
111 # represented in well-formed XML; escape characters that
112 # aren't listed in the XML specification
113 # (http://www.w3.org/TR/REC-xml/#NT-Char).
114 import re
115 message, _ = re.subn(
116 u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
117 lambda match: match.group(0).encode('unicode_escape'),
118 message)
119 return message
122 def binary_stream(stream):
123 """Ensure a stream is binary on Windows.
125 :return: the stream
127 try:
128 import os
129 if os.name == 'nt':
130 fileno = getattr(stream, 'fileno', None)
131 if fileno:
132 no = fileno()
133 if no >= 0: # -1 means we're working as subprocess
134 import msvcrt
135 msvcrt.setmode(no, os.O_BINARY)
136 except ImportError:
137 pass
138 return stream
141 def best_format_for_objects_in_a_repository(repo):
142 """Find the high-level format for branches and trees given a repository.
144 When creating branches and working trees within a repository, Bazaar
145 defaults to using the default format which may not be the best choice.
146 This routine does a reverse lookup of the high-level format registry
147 to find the high-level format that a shared repository was most likely
148 created via.
150 :return: the BzrDirFormat or None if no matches were found.
152 # Based on code from bzrlib/info.py ...
153 from bzrlib import bzrdir
154 repo_format = repo._format
155 candidates = []
156 non_aliases = set(bzrdir.format_registry.keys())
157 non_aliases.difference_update(bzrdir.format_registry.aliases())
158 for key in non_aliases:
159 format = bzrdir.format_registry.make_bzrdir(key)
160 # LocalGitBzrDirFormat has no repository_format
161 if hasattr(format, "repository_format"):
162 if format.repository_format == repo_format:
163 candidates.append((key, format))
164 if len(candidates):
165 # Assume the first one. Is there any reason not to do that?
166 name, format = candidates[0]
167 return format
168 else:
169 return None
172 def open_destination_directory(location, format=None, verbose=True):
173 """Open a destination directory and return the BzrDir.
175 If destination has a control directory, it will be returned.
176 Otherwise, the destination should be empty or non-existent and
177 a shared repository will be created there.
179 :param location: the destination directory
180 :param format: the format to use or None for the default
181 :param verbose: display the format used if a repository is created.
182 :return: BzrDir for the destination
184 import os
185 from bzrlib import bzrdir, errors, trace, transport
186 try:
187 control, relpath = bzrdir.BzrDir.open_containing(location)
188 # XXX: Check the relpath is None here?
189 return control
190 except errors.NotBranchError:
191 pass
193 # If the directory exists, check it is empty. Otherwise create it.
194 if os.path.exists(location):
195 contents = os.listdir(location)
196 if contents:
197 errors.BzrCommandError("Destination must have a .bzr directory, "
198 " not yet exist or be empty - files found in %s" % (location,))
199 else:
200 try:
201 os.mkdir(location)
202 except IOError, ex:
203 errors.BzrCommandError("Unable to create %s: %s" %
204 (location, ex))
206 # Create a repository for the nominated format.
207 trace.note("Creating destination repository ...")
208 if format is None:
209 format = bzrdir.format_registry.make_bzrdir('default')
210 to_transport = transport.get_transport(location)
211 to_transport.ensure_base()
212 control = format.initialize_on_transport(to_transport)
213 repo = control.create_repository(shared=True)
214 if verbose:
215 from bzrlib.info import show_bzrdir_info
216 show_bzrdir_info(repo.bzrdir, verbose=0)
217 return control