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