##// END OF EJS Templates
convert: Use clone's behaviour for the default destionation name....
Thomas Arendsen Hein -
r4883:72ac66e8 default
parent child Browse files
Show More
@@ -1,318 +1,320 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 destination isn't given, a new Mercurial repo named <src>-hg will
251 If no destination directory name is specified, it defaults to the
252 be created. If <mapfile> isn't given, it will be put in a default
252 basename of the source with '-hg' appended. If the destination
253 location (<dest>/.hg/shamap by default)
253 repository doesn't exist, it will be created.
254
254
255 The <mapfile> is a simple text file that maps each source commit ID to
255 If <mapfile> isn't given, it will be put in a default location
256 the destination ID for that revision, like so:
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
258 that revision, like so:
257 <source ID> <destination ID>
259 <source ID> <destination ID>
258
260
259 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
260 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
261 be run repeatedly to copy new commits.
263 be run repeatedly to copy new commits.
262
264
263 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
264 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
265 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
266 mapping and the line format is:
268 mapping and the line format is:
267 srcauthor=whatever string you want
269 srcauthor=whatever string you want
268 '''
270 '''
269
271
270 srcc = converter(ui, src)
272 srcc = converter(ui, src)
271 if not hasattr(srcc, "getcommit"):
273 if not hasattr(srcc, "getcommit"):
272 raise util.Abort("%s: can't read from this repo type" % src)
274 raise util.Abort("%s: can't read from this repo type" % src)
273
275
274 if not dest:
276 if not dest:
275 dest = src + "-hg"
277 dest = hg.defaultdest(src) + "-hg"
276 ui.status("assuming destination %s\n" % dest)
278 ui.status("assuming destination %s\n" % dest)
277
279
278 # Try to be smart and initalize things when required
280 # Try to be smart and initalize things when required
279 if os.path.isdir(dest):
281 if os.path.isdir(dest):
280 if len(os.listdir(dest)) > 0:
282 if len(os.listdir(dest)) > 0:
281 try:
283 try:
282 hg.repository(ui, dest)
284 hg.repository(ui, dest)
283 ui.status("destination %s is a Mercurial repository\n" % dest)
285 ui.status("destination %s is a Mercurial repository\n" % dest)
284 except hg.RepoError:
286 except hg.RepoError:
285 raise util.Abort(
287 raise util.Abort(
286 "destination directory %s is not empty.\n"
288 "destination directory %s is not empty.\n"
287 "Please specify an empty directory to be initialized\n"
289 "Please specify an empty directory to be initialized\n"
288 "or an already initialized mercurial repository"
290 "or an already initialized mercurial repository"
289 % dest)
291 % dest)
290 else:
292 else:
291 ui.status("initializing destination %s repository\n" % dest)
293 ui.status("initializing destination %s repository\n" % dest)
292 hg.repository(ui, dest, create=True)
294 hg.repository(ui, dest, create=True)
293 elif os.path.exists(dest):
295 elif os.path.exists(dest):
294 raise util.Abort("destination %s exists and is not a directory" % dest)
296 raise util.Abort("destination %s exists and is not a directory" % dest)
295 else:
297 else:
296 ui.status("initializing destination %s repository\n" % dest)
298 ui.status("initializing destination %s repository\n" % dest)
297 hg.repository(ui, dest, create=True)
299 hg.repository(ui, dest, create=True)
298
300
299 destc = converter(ui, dest)
301 destc = converter(ui, dest)
300 if not hasattr(destc, "putcommit"):
302 if not hasattr(destc, "putcommit"):
301 raise util.Abort("%s: can't write to this repo type" % src)
303 raise util.Abort("%s: can't write to this repo type" % src)
302
304
303 if not mapfile:
305 if not mapfile:
304 try:
306 try:
305 mapfile = destc.mapfile()
307 mapfile = destc.mapfile()
306 except:
308 except:
307 mapfile = os.path.join(destc, "map")
309 mapfile = os.path.join(destc, "map")
308
310
309 c = convert(ui, srcc, destc, mapfile, opts)
311 c = convert(ui, srcc, destc, mapfile, opts)
310 c.convert()
312 c.convert()
311
313
312 cmdtable = {
314 cmdtable = {
313 "convert":
315 "convert":
314 (_convert,
316 (_convert,
315 [('A', 'authors', '', 'username mapping filename'),
317 [('A', 'authors', '', 'username mapping filename'),
316 ('', 'datesort', None, 'try to sort changesets by date')],
318 ('', 'datesort', None, 'try to sort changesets by date')],
317 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
319 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
318 }
320 }
General Comments 0
You need to be logged in to leave comments. Login now