##// END OF EJS Templates
convert: manually set encoding to UTF-8...
Alexis S. L. Carvalho -
r4895:fa6c9381 default
parent child Browse files
Show More
@@ -1,320 +1,322 b''
1 1 # convert.py Foreign SCM converter
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from common import NoRepo
9 9 from cvs import convert_cvs
10 10 from git import convert_git
11 11 from hg import convert_mercurial
12 12
13 13 import os
14 14 from mercurial import hg, ui, util, commands
15 15
16 16 commands.norepo += " convert"
17 17
18 18 converters = [convert_cvs, convert_git, convert_mercurial]
19 19
20 20 def converter(ui, path):
21 21 if not os.path.isdir(path):
22 22 raise util.Abort("%s: not a directory" % path)
23 23 for c in converters:
24 24 try:
25 25 return c(ui, path)
26 26 except NoRepo:
27 27 pass
28 28 raise util.Abort("%s: unknown repository type" % path)
29 29
30 30 class convert(object):
31 31 def __init__(self, ui, source, dest, mapfile, opts):
32 32
33 33 self.source = source
34 34 self.dest = dest
35 35 self.ui = ui
36 36 self.opts = opts
37 37 self.commitcache = {}
38 38 self.mapfile = mapfile
39 39 self.mapfilefd = None
40 40 self.authors = {}
41 41 self.authorfile = None
42 42
43 43 self.map = {}
44 44 try:
45 45 origmapfile = open(self.mapfile, 'r')
46 46 for l in origmapfile:
47 47 sv, dv = l[:-1].split()
48 48 self.map[sv] = dv
49 49 origmapfile.close()
50 50 except IOError:
51 51 pass
52 52
53 53 # Read first the dst author map if any
54 54 authorfile = self.dest.authorfile()
55 55 if authorfile and os.path.exists(authorfile):
56 56 self.readauthormap(authorfile)
57 57 # Extend/Override with new author map if necessary
58 58 if opts.get('authors'):
59 59 self.readauthormap(opts.get('authors'))
60 60 self.authorfile = self.dest.authorfile()
61 61
62 62 def walktree(self, heads):
63 63 '''Return a mapping that identifies the uncommitted parents of every
64 64 uncommitted changeset.'''
65 65 visit = heads
66 66 known = {}
67 67 parents = {}
68 68 while visit:
69 69 n = visit.pop(0)
70 70 if n in known or n in self.map: continue
71 71 known[n] = 1
72 72 self.commitcache[n] = self.source.getcommit(n)
73 73 cp = self.commitcache[n].parents
74 74 parents[n] = []
75 75 for p in cp:
76 76 parents[n].append(p)
77 77 visit.append(p)
78 78
79 79 return parents
80 80
81 81 def toposort(self, parents):
82 82 '''Return an ordering such that every uncommitted changeset is
83 83 preceeded by all its uncommitted ancestors.'''
84 84 visit = parents.keys()
85 85 seen = {}
86 86 children = {}
87 87
88 88 while visit:
89 89 n = visit.pop(0)
90 90 if n in seen: continue
91 91 seen[n] = 1
92 92 # Ensure that nodes without parents are present in the 'children'
93 93 # mapping.
94 94 children.setdefault(n, [])
95 95 for p in parents[n]:
96 96 if not p in self.map:
97 97 visit.append(p)
98 98 children.setdefault(p, []).append(n)
99 99
100 100 s = []
101 101 removed = {}
102 102 visit = children.keys()
103 103 while visit:
104 104 n = visit.pop(0)
105 105 if n in removed: continue
106 106 dep = 0
107 107 if n in parents:
108 108 for p in parents[n]:
109 109 if p in self.map: continue
110 110 if p not in removed:
111 111 # we're still dependent
112 112 visit.append(n)
113 113 dep = 1
114 114 break
115 115
116 116 if not dep:
117 117 # all n's parents are in the list
118 118 removed[n] = 1
119 119 if n not in self.map:
120 120 s.append(n)
121 121 if n in children:
122 122 for c in children[n]:
123 123 visit.insert(0, c)
124 124
125 125 if self.opts.get('datesort'):
126 126 depth = {}
127 127 for n in s:
128 128 depth[n] = 0
129 129 pl = [p for p in self.commitcache[n].parents
130 130 if p not in self.map]
131 131 if pl:
132 132 depth[n] = max([depth[p] for p in pl]) + 1
133 133
134 134 s = [(depth[n], self.commitcache[n].date, n) for n in s]
135 135 s.sort()
136 136 s = [e[2] for e in s]
137 137
138 138 return s
139 139
140 140 def mapentry(self, src, dst):
141 141 if self.mapfilefd is None:
142 142 try:
143 143 self.mapfilefd = open(self.mapfile, "a")
144 144 except IOError, (errno, strerror):
145 145 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.mapfile, errno, strerror))
146 146 self.map[src] = dst
147 147 self.mapfilefd.write("%s %s\n" % (src, dst))
148 148 self.mapfilefd.flush()
149 149
150 150 def writeauthormap(self):
151 151 authorfile = self.authorfile
152 152 if authorfile:
153 153 self.ui.status('Writing author map file %s\n' % authorfile)
154 154 ofile = open(authorfile, 'w+')
155 155 for author in self.authors:
156 156 ofile.write("%s=%s\n" % (author, self.authors[author]))
157 157 ofile.close()
158 158
159 159 def readauthormap(self, authorfile):
160 160 afile = open(authorfile, 'r')
161 161 for line in afile:
162 162 try:
163 163 srcauthor = line.split('=')[0].strip()
164 164 dstauthor = line.split('=')[1].strip()
165 165 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
166 166 self.ui.status(
167 167 'Overriding mapping for author %s, was %s, will be %s\n'
168 168 % (srcauthor, self.authors[srcauthor], dstauthor))
169 169 else:
170 170 self.ui.debug('Mapping author %s to %s\n'
171 171 % (srcauthor, dstauthor))
172 172 self.authors[srcauthor] = dstauthor
173 173 except IndexError:
174 174 self.ui.warn(
175 175 'Ignoring bad line in author file map %s: %s\n'
176 176 % (authorfile, line))
177 177 afile.close()
178 178
179 179 def copy(self, rev):
180 180 c = self.commitcache[rev]
181 181 files = self.source.getchanges(rev)
182 182
183 183 for f, v in files:
184 184 try:
185 185 data = self.source.getfile(f, v)
186 186 except IOError, inst:
187 187 self.dest.delfile(f)
188 188 else:
189 189 e = self.source.getmode(f, v)
190 190 self.dest.putfile(f, e, data)
191 191
192 192 r = [self.map[v] for v in c.parents]
193 193 f = [f for f, v in files]
194 194 newnode = self.dest.putcommit(f, r, c)
195 195 self.mapentry(rev, newnode)
196 196
197 197 def convert(self):
198 198 try:
199 199 self.ui.status("scanning source...\n")
200 200 heads = self.source.getheads()
201 201 parents = self.walktree(heads)
202 202 self.ui.status("sorting...\n")
203 203 t = self.toposort(parents)
204 204 num = len(t)
205 205 c = None
206 206
207 207 self.ui.status("converting...\n")
208 208 for c in t:
209 209 num -= 1
210 210 desc = self.commitcache[c].desc
211 211 if "\n" in desc:
212 212 desc = desc.splitlines()[0]
213 213 author = self.commitcache[c].author
214 214 author = self.authors.get(author, author)
215 215 self.commitcache[c].author = author
216 216 self.ui.status("%d %s\n" % (num, desc))
217 217 self.copy(c)
218 218
219 219 tags = self.source.gettags()
220 220 ctags = {}
221 221 for k in tags:
222 222 v = tags[k]
223 223 if v in self.map:
224 224 ctags[k] = self.map[v]
225 225
226 226 if c and ctags:
227 227 nrev = self.dest.puttags(ctags)
228 228 # write another hash correspondence to override the previous
229 229 # one so we don't end up with extra tag heads
230 230 if nrev:
231 231 self.mapentry(c, nrev)
232 232
233 233 self.writeauthormap()
234 234 finally:
235 235 self.cleanup()
236 236
237 237 def cleanup(self):
238 238 if self.mapfilefd:
239 239 self.mapfilefd.close()
240 240
241 241 def _convert(ui, src, dest=None, mapfile=None, **opts):
242 242 '''Convert a foreign SCM repository to a Mercurial one.
243 243
244 244 Accepted source formats:
245 245 - GIT
246 246 - CVS
247 247
248 248 Accepted destination formats:
249 249 - Mercurial
250 250
251 251 If no destination directory name is specified, it defaults to the
252 252 basename of the source with '-hg' appended. If the destination
253 253 repository doesn't exist, it will be created.
254 254
255 255 If <mapfile> isn't given, it will be put in a default location
256 256 (<dest>/.hg/shamap by default). The <mapfile> is a simple text
257 257 file that maps each source commit ID to the destination ID for
258 258 that revision, like so:
259 259 <source ID> <destination ID>
260 260
261 261 If the file doesn't exist, it's automatically created. It's updated
262 262 on each commit copied, so convert-repo can be interrupted and can
263 263 be run repeatedly to copy new commits.
264 264
265 265 The [username mapping] file is a simple text file that maps each source
266 266 commit author to a destination commit author. It is handy for source SCMs
267 267 that use unix logins to identify authors (eg: CVS). One line per author
268 268 mapping and the line format is:
269 269 srcauthor=whatever string you want
270 270 '''
271 271
272 util._encoding = 'UTF-8'
273
272 274 srcc = converter(ui, src)
273 275 if not hasattr(srcc, "getcommit"):
274 276 raise util.Abort("%s: can't read from this repo type" % src)
275 277
276 278 if not dest:
277 279 dest = hg.defaultdest(src) + "-hg"
278 280 ui.status("assuming destination %s\n" % dest)
279 281
280 282 # Try to be smart and initalize things when required
281 283 if os.path.isdir(dest):
282 284 if len(os.listdir(dest)) > 0:
283 285 try:
284 286 hg.repository(ui, dest)
285 287 ui.status("destination %s is a Mercurial repository\n" % dest)
286 288 except hg.RepoError:
287 289 raise util.Abort(
288 290 "destination directory %s is not empty.\n"
289 291 "Please specify an empty directory to be initialized\n"
290 292 "or an already initialized mercurial repository"
291 293 % dest)
292 294 else:
293 295 ui.status("initializing destination %s repository\n" % dest)
294 296 hg.repository(ui, dest, create=True)
295 297 elif os.path.exists(dest):
296 298 raise util.Abort("destination %s exists and is not a directory" % dest)
297 299 else:
298 300 ui.status("initializing destination %s repository\n" % dest)
299 301 hg.repository(ui, dest, create=True)
300 302
301 303 destc = converter(ui, dest)
302 304 if not hasattr(destc, "putcommit"):
303 305 raise util.Abort("%s: can't write to this repo type" % src)
304 306
305 307 if not mapfile:
306 308 try:
307 309 mapfile = destc.mapfile()
308 310 except:
309 311 mapfile = os.path.join(destc, "map")
310 312
311 313 c = convert(ui, srcc, destc, mapfile, opts)
312 314 c.convert()
313 315
314 316 cmdtable = {
315 317 "convert":
316 318 (_convert,
317 319 [('A', 'authors', '', 'username mapping filename'),
318 320 ('', 'datesort', None, 'try to sort changesets by date')],
319 321 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
320 322 }
General Comments 0
You need to be logged in to leave comments. Login now