##// END OF EJS Templates
convert: some tidyups, doc improvements, and test fixes...
Bryan O'Sullivan -
r5556:61fdf255 default
parent child Browse files
Show More
@@ -1,383 +1,401 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, SKIPREV, converter_source, converter_sink, mapfile
8 from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from darcs import darcs_source
10 from darcs import darcs_source
11 from git import convert_git
11 from git import convert_git
12 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
14 import filemap
14 import filemap
15
15
16 import os, shutil
16 import os, shutil
17 from mercurial import hg, ui, util, commands
17 from mercurial import hg, ui, util, commands
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19
19
20 commands.norepo += " convert debugsvnlog"
20 commands.norepo += " convert debugsvnlog"
21
21
22 source_converters = [
22 source_converters = [
23 ('cvs', convert_cvs),
23 ('cvs', convert_cvs),
24 ('git', convert_git),
24 ('git', convert_git),
25 ('svn', svn_source),
25 ('svn', svn_source),
26 ('hg', mercurial_source),
26 ('hg', mercurial_source),
27 ('darcs', darcs_source),
27 ('darcs', darcs_source),
28 ]
28 ]
29
29
30 sink_converters = [
30 sink_converters = [
31 ('hg', mercurial_sink),
31 ('hg', mercurial_sink),
32 ('svn', svn_sink),
32 ('svn', svn_sink),
33 ]
33 ]
34
34
35 def convertsource(ui, path, type, rev):
35 def convertsource(ui, path, type, rev):
36 exceptions = []
36 exceptions = []
37 for name, source in source_converters:
37 for name, source in source_converters:
38 try:
38 try:
39 if not type or name == type:
39 if not type or name == type:
40 return source(ui, path, rev)
40 return source(ui, path, rev)
41 except NoRepo, inst:
41 except NoRepo, inst:
42 exceptions.append(inst)
42 exceptions.append(inst)
43 if not ui.quiet:
43 if not ui.quiet:
44 for inst in exceptions:
44 for inst in exceptions:
45 ui.write(_("%s\n") % inst)
45 ui.write(_("%s\n") % inst)
46 raise util.Abort('%s: unknown repository type' % path)
46 raise util.Abort('%s: unknown repository type' % path)
47
47
48 def convertsink(ui, path, type):
48 def convertsink(ui, path, type):
49 for name, sink in sink_converters:
49 for name, sink in sink_converters:
50 try:
50 try:
51 if not type or name == type:
51 if not type or name == type:
52 return sink(ui, path)
52 return sink(ui, path)
53 except NoRepo, inst:
53 except NoRepo, inst:
54 ui.note(_("convert: %s\n") % inst)
54 ui.note(_("convert: %s\n") % inst)
55 raise util.Abort('%s: unknown repository type' % path)
55 raise util.Abort('%s: unknown repository type' % path)
56
56
57 class converter(object):
57 class converter(object):
58 def __init__(self, ui, source, dest, revmapfile, opts):
58 def __init__(self, ui, source, dest, revmapfile, opts):
59
59
60 self.source = source
60 self.source = source
61 self.dest = dest
61 self.dest = dest
62 self.ui = ui
62 self.ui = ui
63 self.opts = opts
63 self.opts = opts
64 self.commitcache = {}
64 self.commitcache = {}
65 self.authors = {}
65 self.authors = {}
66 self.authorfile = None
66 self.authorfile = None
67
67
68 self.map = mapfile(ui, revmapfile)
68 self.map = mapfile(ui, revmapfile)
69
69
70 # Read first the dst author map if any
70 # Read first the dst author map if any
71 authorfile = self.dest.authorfile()
71 authorfile = self.dest.authorfile()
72 if authorfile and os.path.exists(authorfile):
72 if authorfile and os.path.exists(authorfile):
73 self.readauthormap(authorfile)
73 self.readauthormap(authorfile)
74 # Extend/Override with new author map if necessary
74 # Extend/Override with new author map if necessary
75 if opts.get('authors'):
75 if opts.get('authors'):
76 self.readauthormap(opts.get('authors'))
76 self.readauthormap(opts.get('authors'))
77 self.authorfile = self.dest.authorfile()
77 self.authorfile = self.dest.authorfile()
78
78
79 def walktree(self, heads):
79 def walktree(self, heads):
80 '''Return a mapping that identifies the uncommitted parents of every
80 '''Return a mapping that identifies the uncommitted parents of every
81 uncommitted changeset.'''
81 uncommitted changeset.'''
82 visit = heads
82 visit = heads
83 known = {}
83 known = {}
84 parents = {}
84 parents = {}
85 while visit:
85 while visit:
86 n = visit.pop(0)
86 n = visit.pop(0)
87 if n in known or n in self.map: continue
87 if n in known or n in self.map: continue
88 known[n] = 1
88 known[n] = 1
89 commit = self.cachecommit(n)
89 commit = self.cachecommit(n)
90 parents[n] = []
90 parents[n] = []
91 for p in commit.parents:
91 for p in commit.parents:
92 parents[n].append(p)
92 parents[n].append(p)
93 visit.append(p)
93 visit.append(p)
94
94
95 return parents
95 return parents
96
96
97 def toposort(self, parents):
97 def toposort(self, parents):
98 '''Return an ordering such that every uncommitted changeset is
98 '''Return an ordering such that every uncommitted changeset is
99 preceeded by all its uncommitted ancestors.'''
99 preceeded by all its uncommitted ancestors.'''
100 visit = parents.keys()
100 visit = parents.keys()
101 seen = {}
101 seen = {}
102 children = {}
102 children = {}
103
103
104 while visit:
104 while visit:
105 n = visit.pop(0)
105 n = visit.pop(0)
106 if n in seen: continue
106 if n in seen: continue
107 seen[n] = 1
107 seen[n] = 1
108 # Ensure that nodes without parents are present in the 'children'
108 # Ensure that nodes without parents are present in the 'children'
109 # mapping.
109 # mapping.
110 children.setdefault(n, [])
110 children.setdefault(n, [])
111 for p in parents[n]:
111 for p in parents[n]:
112 if not p in self.map:
112 if not p in self.map:
113 visit.append(p)
113 visit.append(p)
114 children.setdefault(p, []).append(n)
114 children.setdefault(p, []).append(n)
115
115
116 s = []
116 s = []
117 removed = {}
117 removed = {}
118 visit = children.keys()
118 visit = children.keys()
119 while visit:
119 while visit:
120 n = visit.pop(0)
120 n = visit.pop(0)
121 if n in removed: continue
121 if n in removed: continue
122 dep = 0
122 dep = 0
123 if n in parents:
123 if n in parents:
124 for p in parents[n]:
124 for p in parents[n]:
125 if p in self.map: continue
125 if p in self.map: continue
126 if p not in removed:
126 if p not in removed:
127 # we're still dependent
127 # we're still dependent
128 visit.append(n)
128 visit.append(n)
129 dep = 1
129 dep = 1
130 break
130 break
131
131
132 if not dep:
132 if not dep:
133 # all n's parents are in the list
133 # all n's parents are in the list
134 removed[n] = 1
134 removed[n] = 1
135 if n not in self.map:
135 if n not in self.map:
136 s.append(n)
136 s.append(n)
137 if n in children:
137 if n in children:
138 for c in children[n]:
138 for c in children[n]:
139 visit.insert(0, c)
139 visit.insert(0, c)
140
140
141 if self.opts.get('datesort'):
141 if self.opts.get('datesort'):
142 depth = {}
142 depth = {}
143 for n in s:
143 for n in s:
144 depth[n] = 0
144 depth[n] = 0
145 pl = [p for p in self.commitcache[n].parents
145 pl = [p for p in self.commitcache[n].parents
146 if p not in self.map]
146 if p not in self.map]
147 if pl:
147 if pl:
148 depth[n] = max([depth[p] for p in pl]) + 1
148 depth[n] = max([depth[p] for p in pl]) + 1
149
149
150 s = [(depth[n], self.commitcache[n].date, n) for n in s]
150 s = [(depth[n], self.commitcache[n].date, n) for n in s]
151 s.sort()
151 s.sort()
152 s = [e[2] for e in s]
152 s = [e[2] for e in s]
153
153
154 return s
154 return s
155
155
156 def writeauthormap(self):
156 def writeauthormap(self):
157 authorfile = self.authorfile
157 authorfile = self.authorfile
158 if authorfile:
158 if authorfile:
159 self.ui.status('Writing author map file %s\n' % authorfile)
159 self.ui.status('Writing author map file %s\n' % authorfile)
160 ofile = open(authorfile, 'w+')
160 ofile = open(authorfile, 'w+')
161 for author in self.authors:
161 for author in self.authors:
162 ofile.write("%s=%s\n" % (author, self.authors[author]))
162 ofile.write("%s=%s\n" % (author, self.authors[author]))
163 ofile.close()
163 ofile.close()
164
164
165 def readauthormap(self, authorfile):
165 def readauthormap(self, authorfile):
166 afile = open(authorfile, 'r')
166 afile = open(authorfile, 'r')
167 for line in afile:
167 for line in afile:
168 try:
168 try:
169 srcauthor = line.split('=')[0].strip()
169 srcauthor = line.split('=')[0].strip()
170 dstauthor = line.split('=')[1].strip()
170 dstauthor = line.split('=')[1].strip()
171 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
171 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
172 self.ui.status(
172 self.ui.status(
173 'Overriding mapping for author %s, was %s, will be %s\n'
173 'Overriding mapping for author %s, was %s, will be %s\n'
174 % (srcauthor, self.authors[srcauthor], dstauthor))
174 % (srcauthor, self.authors[srcauthor], dstauthor))
175 else:
175 else:
176 self.ui.debug('Mapping author %s to %s\n'
176 self.ui.debug('Mapping author %s to %s\n'
177 % (srcauthor, dstauthor))
177 % (srcauthor, dstauthor))
178 self.authors[srcauthor] = dstauthor
178 self.authors[srcauthor] = dstauthor
179 except IndexError:
179 except IndexError:
180 self.ui.warn(
180 self.ui.warn(
181 'Ignoring bad line in author file map %s: %s\n'
181 'Ignoring bad line in author file map %s: %s\n'
182 % (authorfile, line))
182 % (authorfile, line))
183 afile.close()
183 afile.close()
184
184
185 def cachecommit(self, rev):
185 def cachecommit(self, rev):
186 commit = self.source.getcommit(rev)
186 commit = self.source.getcommit(rev)
187 commit.author = self.authors.get(commit.author, commit.author)
187 commit.author = self.authors.get(commit.author, commit.author)
188 self.commitcache[rev] = commit
188 self.commitcache[rev] = commit
189 return commit
189 return commit
190
190
191 def copy(self, rev):
191 def copy(self, rev):
192 commit = self.commitcache[rev]
192 commit = self.commitcache[rev]
193 do_copies = hasattr(self.dest, 'copyfile')
193 do_copies = hasattr(self.dest, 'copyfile')
194 filenames = []
194 filenames = []
195
195
196 changes = self.source.getchanges(rev)
196 changes = self.source.getchanges(rev)
197 if isinstance(changes, basestring):
197 if isinstance(changes, basestring):
198 if changes == SKIPREV:
198 if changes == SKIPREV:
199 dest = SKIPREV
199 dest = SKIPREV
200 else:
200 else:
201 dest = self.map[changes]
201 dest = self.map[changes]
202 self.map[rev] = dest
202 self.map[rev] = dest
203 return
203 return
204 files, copies = changes
204 files, copies = changes
205 parents = [self.map[r] for r in commit.parents]
205 parents = [self.map[r] for r in commit.parents]
206 if commit.parents:
206 if commit.parents:
207 prev = commit.parents[0]
207 prev = commit.parents[0]
208 if prev not in self.commitcache:
208 if prev not in self.commitcache:
209 self.cachecommit(prev)
209 self.cachecommit(prev)
210 pbranch = self.commitcache[prev].branch
210 pbranch = self.commitcache[prev].branch
211 else:
211 else:
212 pbranch = None
212 pbranch = None
213 self.dest.setbranch(commit.branch, pbranch, parents)
213 self.dest.setbranch(commit.branch, pbranch, parents)
214 for f, v in files:
214 for f, v in files:
215 filenames.append(f)
215 filenames.append(f)
216 try:
216 try:
217 data = self.source.getfile(f, v)
217 data = self.source.getfile(f, v)
218 except IOError, inst:
218 except IOError, inst:
219 self.dest.delfile(f)
219 self.dest.delfile(f)
220 else:
220 else:
221 e = self.source.getmode(f, v)
221 e = self.source.getmode(f, v)
222 self.dest.putfile(f, e, data)
222 self.dest.putfile(f, e, data)
223 if do_copies:
223 if do_copies:
224 if f in copies:
224 if f in copies:
225 copyf = copies[f]
225 copyf = copies[f]
226 # Merely marks that a copy happened.
226 # Merely marks that a copy happened.
227 self.dest.copyfile(copyf, f)
227 self.dest.copyfile(copyf, f)
228
228
229 newnode = self.dest.putcommit(filenames, parents, commit)
229 newnode = self.dest.putcommit(filenames, parents, commit)
230 self.source.converted(rev, newnode)
230 self.source.converted(rev, newnode)
231 self.map[rev] = newnode
231 self.map[rev] = newnode
232
232
233 def convert(self):
233 def convert(self):
234 try:
234 try:
235 self.source.before()
235 self.source.before()
236 self.dest.before()
236 self.dest.before()
237 self.source.setrevmap(self.map)
237 self.source.setrevmap(self.map)
238 self.ui.status("scanning source...\n")
238 self.ui.status("scanning source...\n")
239 heads = self.source.getheads()
239 heads = self.source.getheads()
240 parents = self.walktree(heads)
240 parents = self.walktree(heads)
241 self.ui.status("sorting...\n")
241 self.ui.status("sorting...\n")
242 t = self.toposort(parents)
242 t = self.toposort(parents)
243 num = len(t)
243 num = len(t)
244 c = None
244 c = None
245
245
246 self.ui.status("converting...\n")
246 self.ui.status("converting...\n")
247 for c in t:
247 for c in t:
248 num -= 1
248 num -= 1
249 desc = self.commitcache[c].desc
249 desc = self.commitcache[c].desc
250 if "\n" in desc:
250 if "\n" in desc:
251 desc = desc.splitlines()[0]
251 desc = desc.splitlines()[0]
252 self.ui.status("%d %s\n" % (num, desc))
252 self.ui.status("%d %s\n" % (num, desc))
253 self.copy(c)
253 self.copy(c)
254
254
255 tags = self.source.gettags()
255 tags = self.source.gettags()
256 ctags = {}
256 ctags = {}
257 for k in tags:
257 for k in tags:
258 v = tags[k]
258 v = tags[k]
259 if self.map.get(v, SKIPREV) != SKIPREV:
259 if self.map.get(v, SKIPREV) != SKIPREV:
260 ctags[k] = self.map[v]
260 ctags[k] = self.map[v]
261
261
262 if c and ctags:
262 if c and ctags:
263 nrev = self.dest.puttags(ctags)
263 nrev = self.dest.puttags(ctags)
264 # write another hash correspondence to override the previous
264 # write another hash correspondence to override the previous
265 # one so we don't end up with extra tag heads
265 # one so we don't end up with extra tag heads
266 if nrev:
266 if nrev:
267 self.map[c] = nrev
267 self.map[c] = nrev
268
268
269 self.writeauthormap()
269 self.writeauthormap()
270 finally:
270 finally:
271 self.cleanup()
271 self.cleanup()
272
272
273 def cleanup(self):
273 def cleanup(self):
274 try:
274 try:
275 self.dest.after()
275 self.dest.after()
276 finally:
276 finally:
277 self.source.after()
277 self.source.after()
278 self.map.close()
278 self.map.close()
279
279
280 def convert(ui, src, dest=None, revmapfile=None, **opts):
280 def convert(ui, src, dest=None, revmapfile=None, **opts):
281 """Convert a foreign SCM repository to a Mercurial one.
281 """Convert a foreign SCM repository to a Mercurial one.
282
282
283 Accepted source formats:
283 Accepted source formats:
284 - Mercurial
284 - Mercurial
285 - CVS
285 - CVS
286 - Darcs
286 - Darcs
287 - git
287 - git
288 - Subversion
288 - Subversion
289
289
290 Accepted destination formats:
290 Accepted destination formats:
291 - Mercurial
291 - Mercurial
292 - Subversion (history on branches is not preserved)
292 - Subversion (history on branches is not preserved)
293
293
294 If no revision is given, all revisions will be converted. Otherwise,
294 If no revision is given, all revisions will be converted. Otherwise,
295 convert will only import up to the named revision (given in a format
295 convert will only import up to the named revision (given in a format
296 understood by the source).
296 understood by the source).
297
297
298 If no destination directory name is specified, it defaults to the
298 If no destination directory name is specified, it defaults to the
299 basename of the source with '-hg' appended. If the destination
299 basename of the source with '-hg' appended. If the destination
300 repository doesn't exist, it will be created.
300 repository doesn't exist, it will be created.
301
301
302 If <MAPFILE> isn't given, it will be put in a default location
302 If <MAPFILE> isn't given, it will be put in a default location
303 (<dest>/.hg/shamap by default). The <MAPFILE> is a simple text
303 (<dest>/.hg/shamap by default). The <MAPFILE> is a simple text
304 file that maps each source commit ID to the destination ID for
304 file that maps each source commit ID to the destination ID for
305 that revision, like so:
305 that revision, like so:
306 <source ID> <destination ID>
306 <source ID> <destination ID>
307
307
308 If the file doesn't exist, it's automatically created. It's updated
308 If the file doesn't exist, it's automatically created. It's updated
309 on each commit copied, so convert-repo can be interrupted and can
309 on each commit copied, so convert-repo can be interrupted and can
310 be run repeatedly to copy new commits.
310 be run repeatedly to copy new commits.
311
311
312 The [username mapping] file is a simple text file that maps each source
312 The [username mapping] file is a simple text file that maps each source
313 commit author to a destination commit author. It is handy for source SCMs
313 commit author to a destination commit author. It is handy for source SCMs
314 that use unix logins to identify authors (eg: CVS). One line per author
314 that use unix logins to identify authors (eg: CVS). One line per author
315 mapping and the line format is:
315 mapping and the line format is:
316 srcauthor=whatever string you want
316 srcauthor=whatever string you want
317
317
318 The filemap is a file that allows filtering and remapping of files
318 The filemap is a file that allows filtering and remapping of files
319 and directories. Comment lines start with '#'. Each line can
319 and directories. Comment lines start with '#'. Each line can
320 contain one of the following directives:
320 contain one of the following directives:
321
321
322 include path/to/file
322 include path/to/file
323
323
324 exclude path/to/file
324 exclude path/to/file
325
325
326 rename from/file to/file
326 rename from/file to/file
327
327
328 The 'include' directive causes a file, or all files under a
328 The 'include' directive causes a file, or all files under a
329 directory, to be included in the destination repository, and the
329 directory, to be included in the destination repository, and the
330 exclusion of all other files and dirs not explicitely included.
330 exclusion of all other files and dirs not explicitely included.
331 The 'exclude' directive causes files or directories to be omitted.
331 The 'exclude' directive causes files or directories to be omitted.
332 The 'rename' directive renames a file or directory. To rename from a
332 The 'rename' directive renames a file or directory. To rename from a
333 subdirectory into the root of the repository, use '.' as the path to
333 subdirectory into the root of the repository, use '.' as the path to
334 rename to.
334 rename to.
335
336 Back end options:
337
338 --config convert.hg.clonebranches=False (boolean)
339 hg target: XXX not documented
340 --config convert.hg.saverev=True (boolean)
341 hg source: allow target to preserve source revision ID
342 --config convert.hg.tagsbranch=default (branch name)
343 hg target: XXX not documented
344 --config convert.hg.usebranchnames=True (boolean)
345 hg target: preserve branch names
346
347 --config convert.svn.branches=branches (directory name)
348 svn source: specify the directory containing branches
349 --config convert.svn.tags=tags (directory name)
350 svn source: specify the directory containing tags
351 --config convert.svn.trunk=trunk (directory name)
352 svn source: specify the name of the trunk branch
335 """
353 """
336
354
337 util._encoding = 'UTF-8'
355 util._encoding = 'UTF-8'
338
356
339 if not dest:
357 if not dest:
340 dest = hg.defaultdest(src) + "-hg"
358 dest = hg.defaultdest(src) + "-hg"
341 ui.status("assuming destination %s\n" % dest)
359 ui.status("assuming destination %s\n" % dest)
342
360
343 destc = convertsink(ui, dest, opts.get('dest_type'))
361 destc = convertsink(ui, dest, opts.get('dest_type'))
344
362
345 try:
363 try:
346 srcc = convertsource(ui, src, opts.get('source_type'),
364 srcc = convertsource(ui, src, opts.get('source_type'),
347 opts.get('rev'))
365 opts.get('rev'))
348 except Exception:
366 except Exception:
349 for path in destc.created:
367 for path in destc.created:
350 shutil.rmtree(path, True)
368 shutil.rmtree(path, True)
351 raise
369 raise
352
370
353 fmap = opts.get('filemap')
371 fmap = opts.get('filemap')
354 if fmap:
372 if fmap:
355 srcc = filemap.filemap_source(ui, srcc, fmap)
373 srcc = filemap.filemap_source(ui, srcc, fmap)
356 destc.setfilemapmode(True)
374 destc.setfilemapmode(True)
357
375
358 if not revmapfile:
376 if not revmapfile:
359 try:
377 try:
360 revmapfile = destc.revmapfile()
378 revmapfile = destc.revmapfile()
361 except:
379 except:
362 revmapfile = os.path.join(destc, "map")
380 revmapfile = os.path.join(destc, "map")
363
381
364 c = converter(ui, srcc, destc, revmapfile, opts)
382 c = converter(ui, srcc, destc, revmapfile, opts)
365 c.convert()
383 c.convert()
366
384
367
385
368 cmdtable = {
386 cmdtable = {
369 "convert":
387 "convert":
370 (convert,
388 (convert,
371 [('A', 'authors', '', 'username mapping filename'),
389 [('A', 'authors', '', 'username mapping filename'),
372 ('d', 'dest-type', '', 'destination repository type'),
390 ('d', 'dest-type', '', 'destination repository type'),
373 ('', 'filemap', '', 'remap file names using contents of file'),
391 ('', 'filemap', '', 'remap file names using contents of file'),
374 ('r', 'rev', '', 'import up to target revision REV'),
392 ('r', 'rev', '', 'import up to target revision REV'),
375 ('s', 'source-type', '', 'source repository type'),
393 ('s', 'source-type', '', 'source repository type'),
376 ('', 'datesort', None, 'try to sort changesets by date')],
394 ('', 'datesort', None, 'try to sort changesets by date')],
377 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
395 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
378 "debugsvnlog":
396 "debugsvnlog":
379 (debugsvnlog,
397 (debugsvnlog,
380 [],
398 [],
381 'hg debugsvnlog'),
399 'hg debugsvnlog'),
382 }
400 }
383
401
@@ -1,297 +1,297 b''
1 # common code for the convert extension
1 # common code for the convert extension
2 import base64, errno
2 import base64, errno
3 import cPickle as pickle
3 import cPickle as pickle
4 from mercurial import util
4 from mercurial import util
5 from mercurial.i18n import _
5 from mercurial.i18n import _
6
6
7 def encodeargs(args):
7 def encodeargs(args):
8 def encodearg(s):
8 def encodearg(s):
9 lines = base64.encodestring(s)
9 lines = base64.encodestring(s)
10 lines = [l.splitlines()[0] for l in lines]
10 lines = [l.splitlines()[0] for l in lines]
11 return ''.join(lines)
11 return ''.join(lines)
12
12
13 s = pickle.dumps(args)
13 s = pickle.dumps(args)
14 return encodearg(s)
14 return encodearg(s)
15
15
16 def decodeargs(s):
16 def decodeargs(s):
17 s = base64.decodestring(s)
17 s = base64.decodestring(s)
18 return pickle.loads(s)
18 return pickle.loads(s)
19
19
20 def checktool(exe, name=None):
20 def checktool(exe, name=None):
21 name = name or exe
21 name = name or exe
22 if not util.find_exe(exe):
22 if not util.find_exe(exe):
23 raise util.Abort('cannot find required "%s" tool' % name)
23 raise util.Abort('cannot find required "%s" tool' % name)
24
24
25 class NoRepo(Exception): pass
25 class NoRepo(Exception): pass
26
26
27 SKIPREV = 'SKIP'
27 SKIPREV = 'SKIP'
28
28
29 class commit(object):
29 class commit(object):
30 def __init__(self, author, date, desc, parents, branch=None, rev=None,
30 def __init__(self, author, date, desc, parents, branch=None, rev=None,
31 extra={}):
31 extra={}):
32 self.author = author
32 self.author = author
33 self.date = date
33 self.date = date
34 self.desc = desc
34 self.desc = desc
35 self.parents = parents
35 self.parents = parents
36 self.branch = branch
36 self.branch = branch
37 self.rev = rev
37 self.rev = rev
38 self.extra = extra
38 self.extra = extra
39
39
40 class converter_source(object):
40 class converter_source(object):
41 """Conversion source interface"""
41 """Conversion source interface"""
42
42
43 def __init__(self, ui, path, rev=None):
43 def __init__(self, ui, path=None, rev=None):
44 """Initialize conversion source (or raise NoRepo("message")
44 """Initialize conversion source (or raise NoRepo("message")
45 exception if path is not a valid repository)"""
45 exception if path is not a valid repository)"""
46 self.ui = ui
46 self.ui = ui
47 self.path = path
47 self.path = path
48 self.rev = rev
48 self.rev = rev
49
49
50 self.encoding = 'utf-8'
50 self.encoding = 'utf-8'
51
51
52 def before(self):
52 def before(self):
53 pass
53 pass
54
54
55 def after(self):
55 def after(self):
56 pass
56 pass
57
57
58 def setrevmap(self, revmap):
58 def setrevmap(self, revmap):
59 """set the map of already-converted revisions"""
59 """set the map of already-converted revisions"""
60 pass
60 pass
61
61
62 def getheads(self):
62 def getheads(self):
63 """Return a list of this repository's heads"""
63 """Return a list of this repository's heads"""
64 raise NotImplementedError()
64 raise NotImplementedError()
65
65
66 def getfile(self, name, rev):
66 def getfile(self, name, rev):
67 """Return file contents as a string"""
67 """Return file contents as a string"""
68 raise NotImplementedError()
68 raise NotImplementedError()
69
69
70 def getmode(self, name, rev):
70 def getmode(self, name, rev):
71 """Return file mode, eg. '', 'x', or 'l'"""
71 """Return file mode, eg. '', 'x', or 'l'"""
72 raise NotImplementedError()
72 raise NotImplementedError()
73
73
74 def getchanges(self, version):
74 def getchanges(self, version):
75 """Returns a tuple of (files, copies)
75 """Returns a tuple of (files, copies)
76 Files is a sorted list of (filename, id) tuples for all files changed
76 Files is a sorted list of (filename, id) tuples for all files changed
77 in version, where id is the source revision id of the file.
77 in version, where id is the source revision id of the file.
78
78
79 copies is a dictionary of dest: source
79 copies is a dictionary of dest: source
80 """
80 """
81 raise NotImplementedError()
81 raise NotImplementedError()
82
82
83 def getcommit(self, version):
83 def getcommit(self, version):
84 """Return the commit object for version"""
84 """Return the commit object for version"""
85 raise NotImplementedError()
85 raise NotImplementedError()
86
86
87 def gettags(self):
87 def gettags(self):
88 """Return the tags as a dictionary of name: revision"""
88 """Return the tags as a dictionary of name: revision"""
89 raise NotImplementedError()
89 raise NotImplementedError()
90
90
91 def recode(self, s, encoding=None):
91 def recode(self, s, encoding=None):
92 if not encoding:
92 if not encoding:
93 encoding = self.encoding or 'utf-8'
93 encoding = self.encoding or 'utf-8'
94
94
95 if isinstance(s, unicode):
95 if isinstance(s, unicode):
96 return s.encode("utf-8")
96 return s.encode("utf-8")
97 try:
97 try:
98 return s.decode(encoding).encode("utf-8")
98 return s.decode(encoding).encode("utf-8")
99 except:
99 except:
100 try:
100 try:
101 return s.decode("latin-1").encode("utf-8")
101 return s.decode("latin-1").encode("utf-8")
102 except:
102 except:
103 return s.decode(encoding, "replace").encode("utf-8")
103 return s.decode(encoding, "replace").encode("utf-8")
104
104
105 def getchangedfiles(self, rev, i):
105 def getchangedfiles(self, rev, i):
106 """Return the files changed by rev compared to parent[i].
106 """Return the files changed by rev compared to parent[i].
107
107
108 i is an index selecting one of the parents of rev. The return
108 i is an index selecting one of the parents of rev. The return
109 value should be the list of files that are different in rev and
109 value should be the list of files that are different in rev and
110 this parent.
110 this parent.
111
111
112 If rev has no parents, i is None.
112 If rev has no parents, i is None.
113
113
114 This function is only needed to support --filemap
114 This function is only needed to support --filemap
115 """
115 """
116 raise NotImplementedError()
116 raise NotImplementedError()
117
117
118 def converted(self, rev, sinkrev):
118 def converted(self, rev, sinkrev):
119 '''Notify the source that a revision has been converted.'''
119 '''Notify the source that a revision has been converted.'''
120 pass
120 pass
121
121
122
122
123 class converter_sink(object):
123 class converter_sink(object):
124 """Conversion sink (target) interface"""
124 """Conversion sink (target) interface"""
125
125
126 def __init__(self, ui, path):
126 def __init__(self, ui, path):
127 """Initialize conversion sink (or raise NoRepo("message")
127 """Initialize conversion sink (or raise NoRepo("message")
128 exception if path is not a valid repository)
128 exception if path is not a valid repository)
129
129
130 created is a list of paths to remove if a fatal error occurs
130 created is a list of paths to remove if a fatal error occurs
131 later"""
131 later"""
132 self.ui = ui
132 self.ui = ui
133 self.path = path
133 self.path = path
134 self.created = []
134 self.created = []
135
135
136 def getheads(self):
136 def getheads(self):
137 """Return a list of this repository's heads"""
137 """Return a list of this repository's heads"""
138 raise NotImplementedError()
138 raise NotImplementedError()
139
139
140 def revmapfile(self):
140 def revmapfile(self):
141 """Path to a file that will contain lines
141 """Path to a file that will contain lines
142 source_rev_id sink_rev_id
142 source_rev_id sink_rev_id
143 mapping equivalent revision identifiers for each system."""
143 mapping equivalent revision identifiers for each system."""
144 raise NotImplementedError()
144 raise NotImplementedError()
145
145
146 def authorfile(self):
146 def authorfile(self):
147 """Path to a file that will contain lines
147 """Path to a file that will contain lines
148 srcauthor=dstauthor
148 srcauthor=dstauthor
149 mapping equivalent authors identifiers for each system."""
149 mapping equivalent authors identifiers for each system."""
150 return None
150 return None
151
151
152 def putfile(self, f, e, data):
152 def putfile(self, f, e, data):
153 """Put file for next putcommit().
153 """Put file for next putcommit().
154 f: path to file
154 f: path to file
155 e: '', 'x', or 'l' (regular file, executable, or symlink)
155 e: '', 'x', or 'l' (regular file, executable, or symlink)
156 data: file contents"""
156 data: file contents"""
157 raise NotImplementedError()
157 raise NotImplementedError()
158
158
159 def delfile(self, f):
159 def delfile(self, f):
160 """Delete file for next putcommit().
160 """Delete file for next putcommit().
161 f: path to file"""
161 f: path to file"""
162 raise NotImplementedError()
162 raise NotImplementedError()
163
163
164 def putcommit(self, files, parents, commit):
164 def putcommit(self, files, parents, commit):
165 """Create a revision with all changed files listed in 'files'
165 """Create a revision with all changed files listed in 'files'
166 and having listed parents. 'commit' is a commit object containing
166 and having listed parents. 'commit' is a commit object containing
167 at a minimum the author, date, and message for this changeset.
167 at a minimum the author, date, and message for this changeset.
168 Called after putfile() and delfile() calls. Note that the sink
168 Called after putfile() and delfile() calls. Note that the sink
169 repository is not told to update itself to a particular revision
169 repository is not told to update itself to a particular revision
170 (or even what that revision would be) before it receives the
170 (or even what that revision would be) before it receives the
171 file data."""
171 file data."""
172 raise NotImplementedError()
172 raise NotImplementedError()
173
173
174 def puttags(self, tags):
174 def puttags(self, tags):
175 """Put tags into sink.
175 """Put tags into sink.
176 tags: {tagname: sink_rev_id, ...}"""
176 tags: {tagname: sink_rev_id, ...}"""
177 raise NotImplementedError()
177 raise NotImplementedError()
178
178
179 def setbranch(self, branch, pbranch, parents):
179 def setbranch(self, branch, pbranch, parents):
180 """Set the current branch name. Called before the first putfile
180 """Set the current branch name. Called before the first putfile
181 on the branch.
181 on the branch.
182 branch: branch name for subsequent commits
182 branch: branch name for subsequent commits
183 pbranch: branch name of parent commit
183 pbranch: branch name of parent commit
184 parents: destination revisions of parent"""
184 parents: destination revisions of parent"""
185 pass
185 pass
186
186
187 def setfilemapmode(self, active):
187 def setfilemapmode(self, active):
188 """Tell the destination that we're using a filemap
188 """Tell the destination that we're using a filemap
189
189
190 Some converter_sources (svn in particular) can claim that a file
190 Some converter_sources (svn in particular) can claim that a file
191 was changed in a revision, even if there was no change. This method
191 was changed in a revision, even if there was no change. This method
192 tells the destination that we're using a filemap and that it should
192 tells the destination that we're using a filemap and that it should
193 filter empty revisions.
193 filter empty revisions.
194 """
194 """
195 pass
195 pass
196
196
197 def before(self):
197 def before(self):
198 pass
198 pass
199
199
200 def after(self):
200 def after(self):
201 pass
201 pass
202
202
203
203
204 class commandline(object):
204 class commandline(object):
205 def __init__(self, ui, command):
205 def __init__(self, ui, command):
206 self.ui = ui
206 self.ui = ui
207 self.command = command
207 self.command = command
208
208
209 def prerun(self):
209 def prerun(self):
210 pass
210 pass
211
211
212 def postrun(self):
212 def postrun(self):
213 pass
213 pass
214
214
215 def _run(self, cmd, *args, **kwargs):
215 def _run(self, cmd, *args, **kwargs):
216 cmdline = [self.command, cmd] + list(args)
216 cmdline = [self.command, cmd] + list(args)
217 for k, v in kwargs.iteritems():
217 for k, v in kwargs.iteritems():
218 if len(k) == 1:
218 if len(k) == 1:
219 cmdline.append('-' + k)
219 cmdline.append('-' + k)
220 else:
220 else:
221 cmdline.append('--' + k.replace('_', '-'))
221 cmdline.append('--' + k.replace('_', '-'))
222 try:
222 try:
223 if len(k) == 1:
223 if len(k) == 1:
224 cmdline.append('' + v)
224 cmdline.append('' + v)
225 else:
225 else:
226 cmdline[-1] += '=' + v
226 cmdline[-1] += '=' + v
227 except TypeError:
227 except TypeError:
228 pass
228 pass
229 cmdline = [util.shellquote(arg) for arg in cmdline]
229 cmdline = [util.shellquote(arg) for arg in cmdline]
230 cmdline += ['<', util.nulldev]
230 cmdline += ['<', util.nulldev]
231 cmdline = ' '.join(cmdline)
231 cmdline = ' '.join(cmdline)
232 self.ui.debug(cmdline, '\n')
232 self.ui.debug(cmdline, '\n')
233
233
234 self.prerun()
234 self.prerun()
235 try:
235 try:
236 return util.popen(cmdline)
236 return util.popen(cmdline)
237 finally:
237 finally:
238 self.postrun()
238 self.postrun()
239
239
240 def run(self, cmd, *args, **kwargs):
240 def run(self, cmd, *args, **kwargs):
241 fp = self._run(cmd, *args, **kwargs)
241 fp = self._run(cmd, *args, **kwargs)
242 output = fp.read()
242 output = fp.read()
243 self.ui.debug(output)
243 self.ui.debug(output)
244 return output, fp.close()
244 return output, fp.close()
245
245
246 def checkexit(self, status, output=''):
246 def checkexit(self, status, output=''):
247 if status:
247 if status:
248 if output:
248 if output:
249 self.ui.warn(_('%s error:\n') % self.command)
249 self.ui.warn(_('%s error:\n') % self.command)
250 self.ui.warn(output)
250 self.ui.warn(output)
251 msg = util.explain_exit(status)[0]
251 msg = util.explain_exit(status)[0]
252 raise util.Abort(_('%s %s') % (self.command, msg))
252 raise util.Abort(_('%s %s') % (self.command, msg))
253
253
254 def run0(self, cmd, *args, **kwargs):
254 def run0(self, cmd, *args, **kwargs):
255 output, status = self.run(cmd, *args, **kwargs)
255 output, status = self.run(cmd, *args, **kwargs)
256 self.checkexit(status, output)
256 self.checkexit(status, output)
257 return output
257 return output
258
258
259
259
260 class mapfile(dict):
260 class mapfile(dict):
261 def __init__(self, ui, path):
261 def __init__(self, ui, path):
262 super(mapfile, self).__init__()
262 super(mapfile, self).__init__()
263 self.ui = ui
263 self.ui = ui
264 self.path = path
264 self.path = path
265 self.fp = None
265 self.fp = None
266 self.order = []
266 self.order = []
267 self._read()
267 self._read()
268
268
269 def _read(self):
269 def _read(self):
270 try:
270 try:
271 fp = open(self.path, 'r')
271 fp = open(self.path, 'r')
272 except IOError, err:
272 except IOError, err:
273 if err.errno != errno.ENOENT:
273 if err.errno != errno.ENOENT:
274 raise
274 raise
275 return
275 return
276 for line in fp:
276 for line in fp:
277 key, value = line[:-1].split(' ', 1)
277 key, value = line[:-1].split(' ', 1)
278 if key not in self:
278 if key not in self:
279 self.order.append(key)
279 self.order.append(key)
280 super(mapfile, self).__setitem__(key, value)
280 super(mapfile, self).__setitem__(key, value)
281 fp.close()
281 fp.close()
282
282
283 def __setitem__(self, key, value):
283 def __setitem__(self, key, value):
284 if self.fp is None:
284 if self.fp is None:
285 try:
285 try:
286 self.fp = open(self.path, 'a')
286 self.fp = open(self.path, 'a')
287 except IOError, err:
287 except IOError, err:
288 raise util.Abort(_('could not open map file %r: %s') %
288 raise util.Abort(_('could not open map file %r: %s') %
289 (self.path, err.strerror))
289 (self.path, err.strerror))
290 self.fp.write('%s %s\n' % (key, value))
290 self.fp.write('%s %s\n' % (key, value))
291 self.fp.flush()
291 self.fp.flush()
292 super(mapfile, self).__setitem__(key, value)
292 super(mapfile, self).__setitem__(key, value)
293
293
294 def close(self):
294 def close(self):
295 if self.fp:
295 if self.fp:
296 self.fp.close()
296 self.fp.close()
297 self.fp = None
297 self.fp = None
@@ -1,352 +1,346 b''
1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
2 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
2 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
3 #
3 #
4 # This software may be used and distributed according to the terms of
4 # This software may be used and distributed according to the terms of
5 # the GNU General Public License, incorporated herein by reference.
5 # the GNU General Public License, incorporated herein by reference.
6
6
7 import shlex
7 import shlex
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial import util
9 from mercurial import util
10 from common import SKIPREV
10 from common import SKIPREV, converter_source
11
11
12 def rpairs(name):
12 def rpairs(name):
13 e = len(name)
13 e = len(name)
14 while e != -1:
14 while e != -1:
15 yield name[:e], name[e+1:]
15 yield name[:e], name[e+1:]
16 e = name.rfind('/', 0, e)
16 e = name.rfind('/', 0, e)
17
17
18 class filemapper(object):
18 class filemapper(object):
19 '''Map and filter filenames when importing.
19 '''Map and filter filenames when importing.
20 A name can be mapped to itself, a new name, or None (omit from new
20 A name can be mapped to itself, a new name, or None (omit from new
21 repository).'''
21 repository).'''
22
22
23 def __init__(self, ui, path=None):
23 def __init__(self, ui, path=None):
24 self.ui = ui
24 self.ui = ui
25 self.include = {}
25 self.include = {}
26 self.exclude = {}
26 self.exclude = {}
27 self.rename = {}
27 self.rename = {}
28 if path:
28 if path:
29 if self.parse(path):
29 if self.parse(path):
30 raise util.Abort(_('errors in filemap'))
30 raise util.Abort(_('errors in filemap'))
31
31
32 def parse(self, path):
32 def parse(self, path):
33 errs = 0
33 errs = 0
34 def check(name, mapping, listname):
34 def check(name, mapping, listname):
35 if name in mapping:
35 if name in mapping:
36 self.ui.warn(_('%s:%d: %r already in %s list\n') %
36 self.ui.warn(_('%s:%d: %r already in %s list\n') %
37 (lex.infile, lex.lineno, name, listname))
37 (lex.infile, lex.lineno, name, listname))
38 return 1
38 return 1
39 return 0
39 return 0
40 lex = shlex.shlex(open(path), path, True)
40 lex = shlex.shlex(open(path), path, True)
41 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
41 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
42 cmd = lex.get_token()
42 cmd = lex.get_token()
43 while cmd:
43 while cmd:
44 if cmd == 'include':
44 if cmd == 'include':
45 name = lex.get_token()
45 name = lex.get_token()
46 errs += check(name, self.exclude, 'exclude')
46 errs += check(name, self.exclude, 'exclude')
47 self.include[name] = name
47 self.include[name] = name
48 elif cmd == 'exclude':
48 elif cmd == 'exclude':
49 name = lex.get_token()
49 name = lex.get_token()
50 errs += check(name, self.include, 'include')
50 errs += check(name, self.include, 'include')
51 errs += check(name, self.rename, 'rename')
51 errs += check(name, self.rename, 'rename')
52 self.exclude[name] = name
52 self.exclude[name] = name
53 elif cmd == 'rename':
53 elif cmd == 'rename':
54 src = lex.get_token()
54 src = lex.get_token()
55 dest = lex.get_token()
55 dest = lex.get_token()
56 errs += check(src, self.exclude, 'exclude')
56 errs += check(src, self.exclude, 'exclude')
57 self.rename[src] = dest
57 self.rename[src] = dest
58 elif cmd == 'source':
58 elif cmd == 'source':
59 errs += self.parse(lex.get_token())
59 errs += self.parse(lex.get_token())
60 else:
60 else:
61 self.ui.warn(_('%s:%d: unknown directive %r\n') %
61 self.ui.warn(_('%s:%d: unknown directive %r\n') %
62 (lex.infile, lex.lineno, cmd))
62 (lex.infile, lex.lineno, cmd))
63 errs += 1
63 errs += 1
64 cmd = lex.get_token()
64 cmd = lex.get_token()
65 return errs
65 return errs
66
66
67 def lookup(self, name, mapping):
67 def lookup(self, name, mapping):
68 for pre, suf in rpairs(name):
68 for pre, suf in rpairs(name):
69 try:
69 try:
70 return mapping[pre], pre, suf
70 return mapping[pre], pre, suf
71 except KeyError, err:
71 except KeyError, err:
72 pass
72 pass
73 return '', name, ''
73 return '', name, ''
74
74
75 def __call__(self, name):
75 def __call__(self, name):
76 if self.include:
76 if self.include:
77 inc = self.lookup(name, self.include)[0]
77 inc = self.lookup(name, self.include)[0]
78 else:
78 else:
79 inc = name
79 inc = name
80 if self.exclude:
80 if self.exclude:
81 exc = self.lookup(name, self.exclude)[0]
81 exc = self.lookup(name, self.exclude)[0]
82 else:
82 else:
83 exc = ''
83 exc = ''
84 if not inc or exc:
84 if not inc or exc:
85 return None
85 return None
86 newpre, pre, suf = self.lookup(name, self.rename)
86 newpre, pre, suf = self.lookup(name, self.rename)
87 if newpre:
87 if newpre:
88 if newpre == '.':
88 if newpre == '.':
89 return suf
89 return suf
90 if suf:
90 if suf:
91 return newpre + '/' + suf
91 return newpre + '/' + suf
92 return newpre
92 return newpre
93 return name
93 return name
94
94
95 def active(self):
95 def active(self):
96 return bool(self.include or self.exclude or self.rename)
96 return bool(self.include or self.exclude or self.rename)
97
97
98 # This class does two additional things compared to a regular source:
98 # This class does two additional things compared to a regular source:
99 #
99 #
100 # - Filter and rename files. This is mostly wrapped by the filemapper
100 # - Filter and rename files. This is mostly wrapped by the filemapper
101 # class above. We hide the original filename in the revision that is
101 # class above. We hide the original filename in the revision that is
102 # returned by getchanges to be able to find things later in getfile
102 # returned by getchanges to be able to find things later in getfile
103 # and getmode.
103 # and getmode.
104 #
104 #
105 # - Return only revisions that matter for the files we're interested in.
105 # - Return only revisions that matter for the files we're interested in.
106 # This involves rewriting the parents of the original revision to
106 # This involves rewriting the parents of the original revision to
107 # create a graph that is restricted to those revisions.
107 # create a graph that is restricted to those revisions.
108 #
108 #
109 # This set of revisions includes not only revisions that directly
109 # This set of revisions includes not only revisions that directly
110 # touch files we're interested in, but also merges that merge two
110 # touch files we're interested in, but also merges that merge two
111 # or more interesting revisions.
111 # or more interesting revisions.
112
112
113 class filemap_source(object):
113 class filemap_source(converter_source):
114 def __init__(self, ui, baseconverter, filemap):
114 def __init__(self, ui, baseconverter, filemap):
115 self.ui = ui
115 super(filemap_source, self).__init__(ui)
116 self.base = baseconverter
116 self.base = baseconverter
117 self.filemapper = filemapper(ui, filemap)
117 self.filemapper = filemapper(ui, filemap)
118 self.commits = {}
118 self.commits = {}
119 # if a revision rev has parent p in the original revision graph, then
119 # if a revision rev has parent p in the original revision graph, then
120 # rev will have parent self.parentmap[p] in the restricted graph.
120 # rev will have parent self.parentmap[p] in the restricted graph.
121 self.parentmap = {}
121 self.parentmap = {}
122 # self.wantedancestors[rev] is the set of all ancestors of rev that
122 # self.wantedancestors[rev] is the set of all ancestors of rev that
123 # are in the restricted graph.
123 # are in the restricted graph.
124 self.wantedancestors = {}
124 self.wantedancestors = {}
125 self.convertedorder = None
125 self.convertedorder = None
126 self._rebuilt = False
126 self._rebuilt = False
127 self.origparents = {}
127 self.origparents = {}
128 self.children = {}
128 self.children = {}
129 self.seenchildren = {}
129 self.seenchildren = {}
130
130
131 def setrevmap(self, revmap):
131 def setrevmap(self, revmap):
132 # rebuild our state to make things restartable
132 # rebuild our state to make things restartable
133 #
133 #
134 # To avoid calling getcommit for every revision that has already
134 # To avoid calling getcommit for every revision that has already
135 # been converted, we rebuild only the parentmap, delaying the
135 # been converted, we rebuild only the parentmap, delaying the
136 # rebuild of wantedancestors until we need it (i.e. until a
136 # rebuild of wantedancestors until we need it (i.e. until a
137 # merge).
137 # merge).
138 #
138 #
139 # We assume the order argument lists the revisions in
139 # We assume the order argument lists the revisions in
140 # topological order, so that we can infer which revisions were
140 # topological order, so that we can infer which revisions were
141 # wanted by previous runs.
141 # wanted by previous runs.
142 self._rebuilt = not revmap
142 self._rebuilt = not revmap
143 seen = {SKIPREV: SKIPREV}
143 seen = {SKIPREV: SKIPREV}
144 dummyset = util.set()
144 dummyset = util.set()
145 converted = []
145 converted = []
146 for rev in revmap.order:
146 for rev in revmap.order:
147 mapped = revmap[rev]
147 mapped = revmap[rev]
148 wanted = mapped not in seen
148 wanted = mapped not in seen
149 if wanted:
149 if wanted:
150 seen[mapped] = rev
150 seen[mapped] = rev
151 self.parentmap[rev] = rev
151 self.parentmap[rev] = rev
152 else:
152 else:
153 self.parentmap[rev] = seen[mapped]
153 self.parentmap[rev] = seen[mapped]
154 self.wantedancestors[rev] = dummyset
154 self.wantedancestors[rev] = dummyset
155 arg = seen[mapped]
155 arg = seen[mapped]
156 if arg == SKIPREV:
156 if arg == SKIPREV:
157 arg = None
157 arg = None
158 converted.append((rev, wanted, arg))
158 converted.append((rev, wanted, arg))
159 self.convertedorder = converted
159 self.convertedorder = converted
160 return self.base.setrevmap(revmap)
160 return self.base.setrevmap(revmap)
161
161
162 def rebuild(self):
162 def rebuild(self):
163 if self._rebuilt:
163 if self._rebuilt:
164 return True
164 return True
165 self._rebuilt = True
165 self._rebuilt = True
166 self.parentmap.clear()
166 self.parentmap.clear()
167 self.wantedancestors.clear()
167 self.wantedancestors.clear()
168 self.seenchildren.clear()
168 self.seenchildren.clear()
169 for rev, wanted, arg in self.convertedorder:
169 for rev, wanted, arg in self.convertedorder:
170 if rev not in self.origparents:
170 if rev not in self.origparents:
171 self.origparents[rev] = self.getcommit(rev).parents
171 self.origparents[rev] = self.getcommit(rev).parents
172 if arg is not None:
172 if arg is not None:
173 self.children[arg] = self.children.get(arg, 0) + 1
173 self.children[arg] = self.children.get(arg, 0) + 1
174
174
175 for rev, wanted, arg in self.convertedorder:
175 for rev, wanted, arg in self.convertedorder:
176 parents = self.origparents[rev]
176 parents = self.origparents[rev]
177 if wanted:
177 if wanted:
178 self.mark_wanted(rev, parents)
178 self.mark_wanted(rev, parents)
179 else:
179 else:
180 self.mark_not_wanted(rev, arg)
180 self.mark_not_wanted(rev, arg)
181 self._discard(arg, *parents)
181 self._discard(arg, *parents)
182
182
183 return True
183 return True
184
184
185 def getheads(self):
185 def getheads(self):
186 return self.base.getheads()
186 return self.base.getheads()
187
187
188 def getcommit(self, rev):
188 def getcommit(self, rev):
189 # We want to save a reference to the commit objects to be able
189 # We want to save a reference to the commit objects to be able
190 # to rewrite their parents later on.
190 # to rewrite their parents later on.
191 c = self.commits[rev] = self.base.getcommit(rev)
191 c = self.commits[rev] = self.base.getcommit(rev)
192 for p in c.parents:
192 for p in c.parents:
193 self.children[p] = self.children.get(p, 0) + 1
193 self.children[p] = self.children.get(p, 0) + 1
194 return c
194 return c
195
195
196 def _discard(self, *revs):
196 def _discard(self, *revs):
197 for r in revs:
197 for r in revs:
198 if r is None:
198 if r is None:
199 continue
199 continue
200 self.seenchildren[r] = self.seenchildren.get(r, 0) + 1
200 self.seenchildren[r] = self.seenchildren.get(r, 0) + 1
201 if self.seenchildren[r] == self.children[r]:
201 if self.seenchildren[r] == self.children[r]:
202 del self.wantedancestors[r]
202 del self.wantedancestors[r]
203 del self.parentmap[r]
203 del self.parentmap[r]
204 del self.seenchildren[r]
204 del self.seenchildren[r]
205 if self._rebuilt:
205 if self._rebuilt:
206 del self.children[r]
206 del self.children[r]
207
207
208 def wanted(self, rev, i):
208 def wanted(self, rev, i):
209 # Return True if we're directly interested in rev.
209 # Return True if we're directly interested in rev.
210 #
210 #
211 # i is an index selecting one of the parents of rev (if rev
211 # i is an index selecting one of the parents of rev (if rev
212 # has no parents, i is None). getchangedfiles will give us
212 # has no parents, i is None). getchangedfiles will give us
213 # the list of files that are different in rev and in the parent
213 # the list of files that are different in rev and in the parent
214 # indicated by i. If we're interested in any of these files,
214 # indicated by i. If we're interested in any of these files,
215 # we're interested in rev.
215 # we're interested in rev.
216 try:
216 try:
217 files = self.base.getchangedfiles(rev, i)
217 files = self.base.getchangedfiles(rev, i)
218 except NotImplementedError:
218 except NotImplementedError:
219 raise util.Abort(_("source repository doesn't support --filemap"))
219 raise util.Abort(_("source repository doesn't support --filemap"))
220 for f in files:
220 for f in files:
221 if self.filemapper(f):
221 if self.filemapper(f):
222 return True
222 return True
223 return False
223 return False
224
224
225 def mark_not_wanted(self, rev, p):
225 def mark_not_wanted(self, rev, p):
226 # Mark rev as not interesting and update data structures.
226 # Mark rev as not interesting and update data structures.
227
227
228 if p is None:
228 if p is None:
229 # A root revision. Use SKIPREV to indicate that it doesn't
229 # A root revision. Use SKIPREV to indicate that it doesn't
230 # map to any revision in the restricted graph. Put SKIPREV
230 # map to any revision in the restricted graph. Put SKIPREV
231 # in the set of wanted ancestors to simplify code elsewhere
231 # in the set of wanted ancestors to simplify code elsewhere
232 self.parentmap[rev] = SKIPREV
232 self.parentmap[rev] = SKIPREV
233 self.wantedancestors[rev] = util.set((SKIPREV,))
233 self.wantedancestors[rev] = util.set((SKIPREV,))
234 return
234 return
235
235
236 # Reuse the data from our parent.
236 # Reuse the data from our parent.
237 self.parentmap[rev] = self.parentmap[p]
237 self.parentmap[rev] = self.parentmap[p]
238 self.wantedancestors[rev] = self.wantedancestors[p]
238 self.wantedancestors[rev] = self.wantedancestors[p]
239
239
240 def mark_wanted(self, rev, parents):
240 def mark_wanted(self, rev, parents):
241 # Mark rev ss wanted and update data structures.
241 # Mark rev ss wanted and update data structures.
242
242
243 # rev will be in the restricted graph, so children of rev in
243 # rev will be in the restricted graph, so children of rev in
244 # the original graph should still have rev as a parent in the
244 # the original graph should still have rev as a parent in the
245 # restricted graph.
245 # restricted graph.
246 self.parentmap[rev] = rev
246 self.parentmap[rev] = rev
247
247
248 # The set of wanted ancestors of rev is the union of the sets
248 # The set of wanted ancestors of rev is the union of the sets
249 # of wanted ancestors of its parents. Plus rev itself.
249 # of wanted ancestors of its parents. Plus rev itself.
250 wrev = util.set()
250 wrev = util.set()
251 for p in parents:
251 for p in parents:
252 wrev.update(self.wantedancestors[p])
252 wrev.update(self.wantedancestors[p])
253 wrev.add(rev)
253 wrev.add(rev)
254 self.wantedancestors[rev] = wrev
254 self.wantedancestors[rev] = wrev
255
255
256 def getchanges(self, rev):
256 def getchanges(self, rev):
257 parents = self.commits[rev].parents
257 parents = self.commits[rev].parents
258 if len(parents) > 1:
258 if len(parents) > 1:
259 self.rebuild()
259 self.rebuild()
260
260
261 # To decide whether we're interested in rev we:
261 # To decide whether we're interested in rev we:
262 #
262 #
263 # - calculate what parents rev will have if it turns out we're
263 # - calculate what parents rev will have if it turns out we're
264 # interested in it. If it's going to have more than 1 parent,
264 # interested in it. If it's going to have more than 1 parent,
265 # we're interested in it.
265 # we're interested in it.
266 #
266 #
267 # - otherwise, we'll compare it with the single parent we found.
267 # - otherwise, we'll compare it with the single parent we found.
268 # If any of the files we're interested in is different in the
268 # If any of the files we're interested in is different in the
269 # the two revisions, we're interested in rev.
269 # the two revisions, we're interested in rev.
270
270
271 # A parent p is interesting if its mapped version (self.parentmap[p]):
271 # A parent p is interesting if its mapped version (self.parentmap[p]):
272 # - is not SKIPREV
272 # - is not SKIPREV
273 # - is still not in the list of parents (we don't want duplicates)
273 # - is still not in the list of parents (we don't want duplicates)
274 # - is not an ancestor of the mapped versions of the other parents
274 # - is not an ancestor of the mapped versions of the other parents
275 mparents = []
275 mparents = []
276 wp = None
276 wp = None
277 for i, p1 in enumerate(parents):
277 for i, p1 in enumerate(parents):
278 mp1 = self.parentmap[p1]
278 mp1 = self.parentmap[p1]
279 if mp1 == SKIPREV or mp1 in mparents:
279 if mp1 == SKIPREV or mp1 in mparents:
280 continue
280 continue
281 for p2 in parents:
281 for p2 in parents:
282 if p1 == p2 or mp1 == self.parentmap[p2]:
282 if p1 == p2 or mp1 == self.parentmap[p2]:
283 continue
283 continue
284 if mp1 in self.wantedancestors[p2]:
284 if mp1 in self.wantedancestors[p2]:
285 break
285 break
286 else:
286 else:
287 mparents.append(mp1)
287 mparents.append(mp1)
288 wp = i
288 wp = i
289
289
290 if wp is None and parents:
290 if wp is None and parents:
291 wp = 0
291 wp = 0
292
292
293 self.origparents[rev] = parents
293 self.origparents[rev] = parents
294
294
295 if len(mparents) < 2 and not self.wanted(rev, wp):
295 if len(mparents) < 2 and not self.wanted(rev, wp):
296 # We don't want this revision.
296 # We don't want this revision.
297 # Update our state and tell the convert process to map this
297 # Update our state and tell the convert process to map this
298 # revision to the same revision its parent as mapped to.
298 # revision to the same revision its parent as mapped to.
299 p = None
299 p = None
300 if parents:
300 if parents:
301 p = parents[wp]
301 p = parents[wp]
302 self.mark_not_wanted(rev, p)
302 self.mark_not_wanted(rev, p)
303 self.convertedorder.append((rev, False, p))
303 self.convertedorder.append((rev, False, p))
304 self._discard(*parents)
304 self._discard(*parents)
305 return self.parentmap[rev]
305 return self.parentmap[rev]
306
306
307 # We want this revision.
307 # We want this revision.
308 # Rewrite the parents of the commit object
308 # Rewrite the parents of the commit object
309 self.commits[rev].parents = mparents
309 self.commits[rev].parents = mparents
310 self.mark_wanted(rev, parents)
310 self.mark_wanted(rev, parents)
311 self.convertedorder.append((rev, True, None))
311 self.convertedorder.append((rev, True, None))
312 self._discard(*parents)
312 self._discard(*parents)
313
313
314 # Get the real changes and do the filtering/mapping.
314 # Get the real changes and do the filtering/mapping.
315 # To be able to get the files later on in getfile and getmode,
315 # To be able to get the files later on in getfile and getmode,
316 # we hide the original filename in the rev part of the return
316 # we hide the original filename in the rev part of the return
317 # value.
317 # value.
318 changes, copies = self.base.getchanges(rev)
318 changes, copies = self.base.getchanges(rev)
319 newnames = {}
319 newnames = {}
320 files = []
320 files = []
321 for f, r in changes:
321 for f, r in changes:
322 newf = self.filemapper(f)
322 newf = self.filemapper(f)
323 if newf:
323 if newf:
324 files.append((newf, (f, r)))
324 files.append((newf, (f, r)))
325 newnames[f] = newf
325 newnames[f] = newf
326
326
327 ncopies = {}
327 ncopies = {}
328 for c in copies:
328 for c in copies:
329 newc = self.filemapper(c)
329 newc = self.filemapper(c)
330 if newc:
330 if newc:
331 newsource = self.filemapper(copies[c])
331 newsource = self.filemapper(copies[c])
332 if newsource:
332 if newsource:
333 ncopies[newc] = newsource
333 ncopies[newc] = newsource
334
334
335 return files, ncopies
335 return files, ncopies
336
336
337 def getfile(self, name, rev):
337 def getfile(self, name, rev):
338 realname, realrev = rev
338 realname, realrev = rev
339 return self.base.getfile(realname, realrev)
339 return self.base.getfile(realname, realrev)
340
340
341 def getmode(self, name, rev):
341 def getmode(self, name, rev):
342 realname, realrev = rev
342 realname, realrev = rev
343 return self.base.getmode(realname, realrev)
343 return self.base.getmode(realname, realrev)
344
344
345 def gettags(self):
345 def gettags(self):
346 return self.base.gettags()
346 return self.base.gettags()
347
348 def before(self):
349 pass
350
351 def after(self):
352 pass
@@ -1,265 +1,276 b''
1 # hg backend for convert extension
1 # hg backend for convert extension
2
2
3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
3 # Notes for hg->hg conversion:
4 # the whitespace from the ends of commit messages, but new versions
4 #
5 # do. Changesets created by those older versions, then converted, may
5 # * Old versions of Mercurial didn't trim the whitespace from the ends
6 # thus have different hashes for changesets that are otherwise
6 # of commit messages, but new versions do. Changesets created by
7 # identical.
7 # those older versions, then converted, may thus have different
8 # hashes for changesets that are otherwise identical.
9 #
10 # * By default, the source revision is stored in the converted
11 # revision. This will cause the converted revision to have a
12 # different identity than the source. To avoid this, use the
13 # following option: "--config convert.hg.saverev=false"
8
14
9
15
10 import os, time
16 import os, time
11 from mercurial.i18n import _
17 from mercurial.i18n import _
12 from mercurial.node import *
18 from mercurial.node import *
13 from mercurial import hg, lock, revlog, util
19 from mercurial import hg, lock, revlog, util
14
20
15 from common import NoRepo, commit, converter_source, converter_sink
21 from common import NoRepo, commit, converter_source, converter_sink
16
22
17 class mercurial_sink(converter_sink):
23 class mercurial_sink(converter_sink):
18 def __init__(self, ui, path):
24 def __init__(self, ui, path):
19 converter_sink.__init__(self, ui, path)
25 converter_sink.__init__(self, ui, path)
20 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
26 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
21 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
27 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
22 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
28 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
23 self.lastbranch = None
29 self.lastbranch = None
24 if os.path.isdir(path) and len(os.listdir(path)) > 0:
30 if os.path.isdir(path) and len(os.listdir(path)) > 0:
25 try:
31 try:
26 self.repo = hg.repository(self.ui, path)
32 self.repo = hg.repository(self.ui, path)
27 except hg.RepoError, err:
33 except hg.RepoError, err:
28 ui.print_exc()
34 ui.print_exc()
29 raise NoRepo(err.args[0])
35 raise NoRepo(err.args[0])
30 else:
36 else:
31 try:
37 try:
32 ui.status(_('initializing destination %s repository\n') % path)
38 ui.status(_('initializing destination %s repository\n') % path)
33 self.repo = hg.repository(self.ui, path, create=True)
39 self.repo = hg.repository(self.ui, path, create=True)
34 self.created.append(path)
40 self.created.append(path)
35 except hg.RepoError, err:
41 except hg.RepoError, err:
36 ui.print_exc()
42 ui.print_exc()
37 raise NoRepo("could not create hg repo %s as sink" % path)
43 raise NoRepo("could not create hg repo %s as sink" % path)
38 self.lock = None
44 self.lock = None
39 self.wlock = None
45 self.wlock = None
40 self.filemapmode = False
46 self.filemapmode = False
41
47
42 def before(self):
48 def before(self):
43 self.wlock = self.repo.wlock()
49 self.wlock = self.repo.wlock()
44 self.lock = self.repo.lock()
50 self.lock = self.repo.lock()
45 self.repo.dirstate.clear()
51 self.repo.dirstate.clear()
46
52
47 def after(self):
53 def after(self):
48 self.repo.dirstate.invalidate()
54 self.repo.dirstate.invalidate()
49 self.lock = None
55 self.lock = None
50 self.wlock = None
56 self.wlock = None
51
57
52 def revmapfile(self):
58 def revmapfile(self):
53 return os.path.join(self.path, ".hg", "shamap")
59 return os.path.join(self.path, ".hg", "shamap")
54
60
55 def authorfile(self):
61 def authorfile(self):
56 return os.path.join(self.path, ".hg", "authormap")
62 return os.path.join(self.path, ".hg", "authormap")
57
63
58 def getheads(self):
64 def getheads(self):
59 h = self.repo.changelog.heads()
65 h = self.repo.changelog.heads()
60 return [ hex(x) for x in h ]
66 return [ hex(x) for x in h ]
61
67
62 def putfile(self, f, e, data):
68 def putfile(self, f, e, data):
63 self.repo.wwrite(f, data, e)
69 self.repo.wwrite(f, data, e)
64 if f not in self.repo.dirstate:
70 if f not in self.repo.dirstate:
65 self.repo.dirstate.normallookup(f)
71 self.repo.dirstate.normallookup(f)
66
72
67 def copyfile(self, source, dest):
73 def copyfile(self, source, dest):
68 self.repo.copy(source, dest)
74 self.repo.copy(source, dest)
69
75
70 def delfile(self, f):
76 def delfile(self, f):
71 try:
77 try:
72 util.unlink(self.repo.wjoin(f))
78 util.unlink(self.repo.wjoin(f))
73 #self.repo.remove([f])
79 #self.repo.remove([f])
74 except OSError:
80 except OSError:
75 pass
81 pass
76
82
77 def setbranch(self, branch, pbranch, parents):
83 def setbranch(self, branch, pbranch, parents):
78 if (not self.clonebranches) or (branch == self.lastbranch):
84 if (not self.clonebranches) or (branch == self.lastbranch):
79 return
85 return
80
86
81 self.lastbranch = branch
87 self.lastbranch = branch
82 self.after()
88 self.after()
83 if not branch:
89 if not branch:
84 branch = 'default'
90 branch = 'default'
85 if not pbranch:
91 if not pbranch:
86 pbranch = 'default'
92 pbranch = 'default'
87
93
88 branchpath = os.path.join(self.path, branch)
94 branchpath = os.path.join(self.path, branch)
89 try:
95 try:
90 self.repo = hg.repository(self.ui, branchpath)
96 self.repo = hg.repository(self.ui, branchpath)
91 except:
97 except:
92 if not parents:
98 if not parents:
93 self.repo = hg.repository(self.ui, branchpath, create=True)
99 self.repo = hg.repository(self.ui, branchpath, create=True)
94 else:
100 else:
95 self.ui.note(_('cloning branch %s to %s\n') % (pbranch, branch))
101 self.ui.note(_('cloning branch %s to %s\n') % (pbranch, branch))
96 hg.clone(self.ui, os.path.join(self.path, pbranch),
102 hg.clone(self.ui, os.path.join(self.path, pbranch),
97 branchpath, rev=parents, update=False,
103 branchpath, rev=parents, update=False,
98 stream=True)
104 stream=True)
99 self.repo = hg.repository(self.ui, branchpath)
105 self.repo = hg.repository(self.ui, branchpath)
100 self.before()
106 self.before()
101
107
102 def putcommit(self, files, parents, commit):
108 def putcommit(self, files, parents, commit):
103 seen = {}
109 seen = {}
104 pl = []
110 pl = []
105 for p in parents:
111 for p in parents:
106 if p not in seen:
112 if p not in seen:
107 pl.append(p)
113 pl.append(p)
108 seen[p] = 1
114 seen[p] = 1
109 parents = pl
115 parents = pl
110 nparents = len(parents)
116 nparents = len(parents)
111 if self.filemapmode and nparents == 1:
117 if self.filemapmode and nparents == 1:
112 m1node = self.repo.changelog.read(bin(parents[0]))[0]
118 m1node = self.repo.changelog.read(bin(parents[0]))[0]
113 parent = parents[0]
119 parent = parents[0]
114
120
115 if len(parents) < 2: parents.append("0" * 40)
121 if len(parents) < 2: parents.append("0" * 40)
116 if len(parents) < 2: parents.append("0" * 40)
122 if len(parents) < 2: parents.append("0" * 40)
117 p2 = parents.pop(0)
123 p2 = parents.pop(0)
118
124
119 text = commit.desc
125 text = commit.desc
120 extra = commit.extra.copy()
126 extra = commit.extra.copy()
121 if self.branchnames and commit.branch:
127 if self.branchnames and commit.branch:
122 extra['branch'] = commit.branch
128 extra['branch'] = commit.branch
123 if commit.rev:
129 if commit.rev:
124 extra['convert_revision'] = commit.rev
130 extra['convert_revision'] = commit.rev
125
131
126 while parents:
132 while parents:
127 p1 = p2
133 p1 = p2
128 p2 = parents.pop(0)
134 p2 = parents.pop(0)
129 a = self.repo.rawcommit(files, text, commit.author, commit.date,
135 a = self.repo.rawcommit(files, text, commit.author, commit.date,
130 bin(p1), bin(p2), extra=extra)
136 bin(p1), bin(p2), extra=extra)
131 self.repo.dirstate.clear()
137 self.repo.dirstate.clear()
132 text = "(octopus merge fixup)\n"
138 text = "(octopus merge fixup)\n"
133 p2 = hg.hex(self.repo.changelog.tip())
139 p2 = hg.hex(self.repo.changelog.tip())
134
140
135 if self.filemapmode and nparents == 1:
141 if self.filemapmode and nparents == 1:
136 man = self.repo.manifest
142 man = self.repo.manifest
137 mnode = self.repo.changelog.read(bin(p2))[0]
143 mnode = self.repo.changelog.read(bin(p2))[0]
138 if not man.cmp(m1node, man.revision(mnode)):
144 if not man.cmp(m1node, man.revision(mnode)):
139 self.repo.rollback()
145 self.repo.rollback()
140 self.repo.dirstate.clear()
146 self.repo.dirstate.clear()
141 return parent
147 return parent
142 return p2
148 return p2
143
149
144 def puttags(self, tags):
150 def puttags(self, tags):
145 try:
151 try:
146 old = self.repo.wfile(".hgtags").read()
152 old = self.repo.wfile(".hgtags").read()
147 oldlines = old.splitlines(1)
153 oldlines = old.splitlines(1)
148 oldlines.sort()
154 oldlines.sort()
149 except:
155 except:
150 oldlines = []
156 oldlines = []
151
157
152 k = tags.keys()
158 k = tags.keys()
153 k.sort()
159 k.sort()
154 newlines = []
160 newlines = []
155 for tag in k:
161 for tag in k:
156 newlines.append("%s %s\n" % (tags[tag], tag))
162 newlines.append("%s %s\n" % (tags[tag], tag))
157
163
158 newlines.sort()
164 newlines.sort()
159
165
160 if newlines != oldlines:
166 if newlines != oldlines:
161 self.ui.status("updating tags\n")
167 self.ui.status("updating tags\n")
162 f = self.repo.wfile(".hgtags", "w")
168 f = self.repo.wfile(".hgtags", "w")
163 f.write("".join(newlines))
169 f.write("".join(newlines))
164 f.close()
170 f.close()
165 if not oldlines: self.repo.add([".hgtags"])
171 if not oldlines: self.repo.add([".hgtags"])
166 date = "%s 0" % int(time.mktime(time.gmtime()))
172 date = "%s 0" % int(time.mktime(time.gmtime()))
167 extra = {}
173 extra = {}
168 if self.tagsbranch != 'default':
174 if self.tagsbranch != 'default':
169 extra['branch'] = self.tagsbranch
175 extra['branch'] = self.tagsbranch
170 try:
176 try:
171 tagparent = self.repo.changectx(self.tagsbranch).node()
177 tagparent = self.repo.changectx(self.tagsbranch).node()
172 except hg.RepoError, inst:
178 except hg.RepoError, inst:
173 tagparent = nullid
179 tagparent = nullid
174 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
180 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
175 date, tagparent, nullid)
181 date, tagparent, nullid)
176 return hex(self.repo.changelog.tip())
182 return hex(self.repo.changelog.tip())
177
183
178 def setfilemapmode(self, active):
184 def setfilemapmode(self, active):
179 self.filemapmode = active
185 self.filemapmode = active
180
186
181 class mercurial_source(converter_source):
187 class mercurial_source(converter_source):
182 def __init__(self, ui, path, rev=None):
188 def __init__(self, ui, path, rev=None):
183 converter_source.__init__(self, ui, path, rev)
189 converter_source.__init__(self, ui, path, rev)
190 self.saverev = ui.configbool('convert', 'hg.saverev', True)
184 try:
191 try:
185 self.repo = hg.repository(self.ui, path)
192 self.repo = hg.repository(self.ui, path)
186 # try to provoke an exception if this isn't really a hg
193 # try to provoke an exception if this isn't really a hg
187 # repo, but some other bogus compatible-looking url
194 # repo, but some other bogus compatible-looking url
188 if not self.repo.local():
195 if not self.repo.local():
189 raise hg.RepoError()
196 raise hg.RepoError()
190 except hg.RepoError:
197 except hg.RepoError:
191 ui.print_exc()
198 ui.print_exc()
192 raise NoRepo("%s is not a local Mercurial repo" % path)
199 raise NoRepo("%s is not a local Mercurial repo" % path)
193 self.lastrev = None
200 self.lastrev = None
194 self.lastctx = None
201 self.lastctx = None
195 self._changescache = None
202 self._changescache = None
196 self.convertfp = None
203 self.convertfp = None
197
204
198 def changectx(self, rev):
205 def changectx(self, rev):
199 if self.lastrev != rev:
206 if self.lastrev != rev:
200 self.lastctx = self.repo.changectx(rev)
207 self.lastctx = self.repo.changectx(rev)
201 self.lastrev = rev
208 self.lastrev = rev
202 return self.lastctx
209 return self.lastctx
203
210
204 def getheads(self):
211 def getheads(self):
205 if self.rev:
212 if self.rev:
206 return [hex(self.repo.changectx(self.rev).node())]
213 return [hex(self.repo.changectx(self.rev).node())]
207 else:
214 else:
208 return [hex(node) for node in self.repo.heads()]
215 return [hex(node) for node in self.repo.heads()]
209
216
210 def getfile(self, name, rev):
217 def getfile(self, name, rev):
211 try:
218 try:
212 return self.changectx(rev).filectx(name).data()
219 return self.changectx(rev).filectx(name).data()
213 except revlog.LookupError, err:
220 except revlog.LookupError, err:
214 raise IOError(err)
221 raise IOError(err)
215
222
216 def getmode(self, name, rev):
223 def getmode(self, name, rev):
217 m = self.changectx(rev).manifest()
224 m = self.changectx(rev).manifest()
218 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
225 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
219
226
220 def getchanges(self, rev):
227 def getchanges(self, rev):
221 ctx = self.changectx(rev)
228 ctx = self.changectx(rev)
222 if self._changescache and self._changescache[0] == rev:
229 if self._changescache and self._changescache[0] == rev:
223 m, a, r = self._changescache[1]
230 m, a, r = self._changescache[1]
224 else:
231 else:
225 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
232 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
226 changes = [(name, rev) for name in m + a + r]
233 changes = [(name, rev) for name in m + a + r]
227 changes.sort()
234 changes.sort()
228 return (changes, self.getcopies(ctx, m + a))
235 return (changes, self.getcopies(ctx, m + a))
229
236
230 def getcopies(self, ctx, files):
237 def getcopies(self, ctx, files):
231 copies = {}
238 copies = {}
232 for name in files:
239 for name in files:
233 try:
240 try:
234 copies[name] = ctx.filectx(name).renamed()[0]
241 copies[name] = ctx.filectx(name).renamed()[0]
235 except TypeError:
242 except TypeError:
236 pass
243 pass
237 return copies
244 return copies
238
245
239 def getcommit(self, rev):
246 def getcommit(self, rev):
240 ctx = self.changectx(rev)
247 ctx = self.changectx(rev)
241 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
248 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
249 if self.saverev:
250 crev = rev
251 else:
252 crev = None
242 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
253 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
243 desc=ctx.description(), rev=rev, parents=parents,
254 desc=ctx.description(), rev=crev, parents=parents,
244 branch=ctx.branch(), extra=ctx.extra())
255 branch=ctx.branch(), extra=ctx.extra())
245
256
246 def gettags(self):
257 def gettags(self):
247 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
258 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
248 return dict([(name, hex(node)) for name, node in tags])
259 return dict([(name, hex(node)) for name, node in tags])
249
260
250 def getchangedfiles(self, rev, i):
261 def getchangedfiles(self, rev, i):
251 ctx = self.changectx(rev)
262 ctx = self.changectx(rev)
252 i = i or 0
263 i = i or 0
253 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
264 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
254
265
255 if i == 0:
266 if i == 0:
256 self._changescache = (rev, changes)
267 self._changescache = (rev, changes)
257
268
258 return changes[0] + changes[1] + changes[2]
269 return changes[0] + changes[1] + changes[2]
259
270
260 def converted(self, rev, destrev):
271 def converted(self, rev, destrev):
261 if self.convertfp is None:
272 if self.convertfp is None:
262 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
273 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
263 'a')
274 'a')
264 self.convertfp.write('%s %s\n' % (destrev, rev))
275 self.convertfp.write('%s %s\n' % (destrev, rev))
265 self.convertfp.flush()
276 self.convertfp.flush()
@@ -1,37 +1,41 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 cat >> $HGRCPATH <<EOF
4 echo "convert=" >> $HGRCPATH
4 [extensions]
5 convert=
6 [convert]
7 hg.saverev=False
8 EOF
5
9
6 hg help convert
10 hg help convert
7
11
8 hg init a
12 hg init a
9 cd a
13 cd a
10 echo a > a
14 echo a > a
11 hg ci -d'0 0' -Ama
15 hg ci -d'0 0' -Ama
12 hg cp a b
16 hg cp a b
13 hg ci -d'1 0' -mb
17 hg ci -d'1 0' -mb
14 hg rm a
18 hg rm a
15 hg ci -d'2 0' -mc
19 hg ci -d'2 0' -mc
16 hg mv b a
20 hg mv b a
17 hg ci -d'3 0' -md
21 hg ci -d'3 0' -md
18 echo a >> a
22 echo a >> a
19 hg ci -d'4 0' -me
23 hg ci -d'4 0' -me
20
24
21 cd ..
25 cd ..
22 hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
26 hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
23 hg --cwd a-hg pull ../a
27 hg --cwd a-hg pull ../a
24
28
25 touch bogusfile
29 touch bogusfile
26 echo % should fail
30 echo % should fail
27 hg convert a bogusfile
31 hg convert a bogusfile
28
32
29 mkdir bogusdir
33 mkdir bogusdir
30 chmod 000 bogusdir
34 chmod 000 bogusdir
31
35
32 echo % should fail
36 echo % should fail
33 hg convert a bogusdir
37 hg convert a bogusdir
34
38
35 echo % should succeed
39 echo % should succeed
36 chmod 700 bogusdir
40 chmod 700 bogusdir
37 hg convert a bogusdir
41 hg convert a bogusdir
@@ -1,71 +1,69 b''
1 % create cvs repository
1 % create cvs repository
2 % create source directory
2 % create source directory
3 % import source directory
3 % import source directory
4 N src/a
4 N src/a
5 N src/b/c
5 N src/b/c
6
6
7 No conflicts created by this import
7 No conflicts created by this import
8
8
9 % checkout source directory
9 % checkout source directory
10 U src/a
10 U src/a
11 U src/b/c
11 U src/b/c
12 % commit a new revision changing b/c
12 % commit a new revision changing b/c
13 checking in src/b/c,v
13 checking in src/b/c,v
14 % convert fresh repo
14 % convert fresh repo
15 initializing destination src-hg repository
15 initializing destination src-hg repository
16 connecting to cvsrepo
16 connecting to cvsrepo
17 scanning source...
17 scanning source...
18 sorting...
18 sorting...
19 converting...
19 converting...
20 2 Initial revision
20 2 Initial revision
21 1 import
21 1 import
22 0 ci0
22 0 ci0
23 updating tags
23 updating tags
24 a
24 a
25 c
25 c
26 c
26 c
27 % convert fresh repo with --filemap
27 % convert fresh repo with --filemap
28 initializing destination src-filemap repository
28 initializing destination src-filemap repository
29 connecting to cvsrepo
29 connecting to cvsrepo
30 scanning source...
30 scanning source...
31 sorting...
31 sorting...
32 converting...
32 converting...
33 2 Initial revision
33 2 Initial revision
34 1 import
34 1 import
35 rolling back last transaction
35 rolling back last transaction
36 0 ci0
36 0 ci0
37 updating tags
37 updating tags
38 c
38 c
39 c
39 c
40 2 update tags files: .hgtags
40 2 update tags files: .hgtags
41 1 ci0 files: b/c
41 1 ci0 files: b/c
42 0 Initial revision files: b/c
42 0 Initial revision files: b/c
43 % commit new file revisions
43 % commit new file revisions
44 checking in src/a,v
44 checking in src/a,v
45 checking in src/b/c,v
45 checking in src/b/c,v
46 % convert again
46 % convert again
47 destination src-hg is a Mercurial repository
48 connecting to cvsrepo
47 connecting to cvsrepo
49 scanning source...
48 scanning source...
50 sorting...
49 sorting...
51 converting...
50 converting...
52 0 ci1
51 0 ci1
53 a
52 a
54 a
53 a
55 c
54 c
56 c
55 c
57 c
56 c
58 % convert again with --filemap
57 % convert again with --filemap
59 destination src-filemap is a Mercurial repository
60 connecting to cvsrepo
58 connecting to cvsrepo
61 scanning source...
59 scanning source...
62 sorting...
60 sorting...
63 converting...
61 converting...
64 0 ci1
62 0 ci1
65 c
63 c
66 c
64 c
67 c
65 c
68 3 ci1 files: b/c
66 3 ci1 files: b/c
69 2 update tags files: .hgtags
67 2 update tags files: .hgtags
70 1 ci0 files: b/c
68 1 ci0 files: b/c
71 0 Initial revision files: b/c
69 0 Initial revision files: b/c
@@ -1,53 +1,57 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 cat >> $HGRCPATH <<EOF
4 echo "hgext.convert=" >> $HGRCPATH
4 [extensions]
5 convert=
6 [convert]
7 hg.saverev=False
8 EOF
5
9
6 hg init orig
10 hg init orig
7 cd orig
11 cd orig
8 echo foo > foo
12 echo foo > foo
9 echo bar > bar
13 echo bar > bar
10 hg ci -qAm 'add foo and bar' -d '0 0'
14 hg ci -qAm 'add foo and bar' -d '0 0'
11
15
12 hg rm foo
16 hg rm foo
13 hg ci -m 'remove foo' -d '0 0'
17 hg ci -m 'remove foo' -d '0 0'
14
18
15 mkdir foo
19 mkdir foo
16 echo file > foo/file
20 echo file > foo/file
17 hg ci -qAm 'add foo/file' -d '0 0'
21 hg ci -qAm 'add foo/file' -d '0 0'
18
22
19 hg tag -d '0 0' some-tag
23 hg tag -d '0 0' some-tag
20
24
21 hg log
25 hg log
22 cd ..
26 cd ..
23
27
24 hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
28 hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
25 cd new
29 cd new
26 hg out ../orig
30 hg out ../orig
27
31
28 echo '% dirstate should be empty:'
32 echo '% dirstate should be empty:'
29 hg debugstate
33 hg debugstate
30 hg parents -q
34 hg parents -q
31
35
32 hg up -C
36 hg up -C
33 hg copy bar baz
37 hg copy bar baz
34 echo '% put something in the dirstate:'
38 echo '% put something in the dirstate:'
35 hg debugstate > debugstate
39 hg debugstate > debugstate
36 grep baz debugstate
40 grep baz debugstate
37
41
38 echo '% add a new revision in the original repo'
42 echo '% add a new revision in the original repo'
39 cd ../orig
43 cd ../orig
40 echo baz > baz
44 echo baz > baz
41 hg ci -qAm 'add baz'
45 hg ci -qAm 'add baz'
42
46
43 cd ..
47 cd ..
44 hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
48 hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
45 cd new
49 cd new
46 hg out ../orig
50 hg out ../orig
47 echo '% dirstate should be the same (no output below):'
51 echo '% dirstate should be the same (no output below):'
48 hg debugstate > new-debugstate
52 hg debugstate > new-debugstate
49 diff debugstate new-debugstate
53 diff debugstate new-debugstate
50
54
51 echo '% no copies'
55 echo '% no copies'
52 hg up -C
56 hg up -C
53 hg debugrename baz
57 hg debugrename baz
@@ -1,51 +1,50 b''
1 changeset: 3:593cbf6fb2b4
1 changeset: 3:593cbf6fb2b4
2 tag: tip
2 tag: tip
3 user: test
3 user: test
4 date: Thu Jan 01 00:00:00 1970 +0000
4 date: Thu Jan 01 00:00:00 1970 +0000
5 summary: Added tag some-tag for changeset ad681a868e44
5 summary: Added tag some-tag for changeset ad681a868e44
6
6
7 changeset: 2:ad681a868e44
7 changeset: 2:ad681a868e44
8 tag: some-tag
8 tag: some-tag
9 user: test
9 user: test
10 date: Thu Jan 01 00:00:00 1970 +0000
10 date: Thu Jan 01 00:00:00 1970 +0000
11 summary: add foo/file
11 summary: add foo/file
12
12
13 changeset: 1:cbba8ecc03b7
13 changeset: 1:cbba8ecc03b7
14 user: test
14 user: test
15 date: Thu Jan 01 00:00:00 1970 +0000
15 date: Thu Jan 01 00:00:00 1970 +0000
16 summary: remove foo
16 summary: remove foo
17
17
18 changeset: 0:327daa9251fa
18 changeset: 0:327daa9251fa
19 user: test
19 user: test
20 date: Thu Jan 01 00:00:00 1970 +0000
20 date: Thu Jan 01 00:00:00 1970 +0000
21 summary: add foo and bar
21 summary: add foo and bar
22
22
23 initializing destination new repository
23 initializing destination new repository
24 scanning source...
24 scanning source...
25 sorting...
25 sorting...
26 converting...
26 converting...
27 3 add foo and bar
27 3 add foo and bar
28 2 remove foo
28 2 remove foo
29 1 add foo/file
29 1 add foo/file
30 0 Added tag some-tag for changeset ad681a868e44
30 0 Added tag some-tag for changeset ad681a868e44
31 comparing with ../orig
31 comparing with ../orig
32 searching for changes
32 searching for changes
33 no changes found
33 no changes found
34 % dirstate should be empty:
34 % dirstate should be empty:
35 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 % put something in the dirstate:
36 % put something in the dirstate:
37 a 0 -1 unset baz
37 a 0 -1 unset baz
38 copy: bar -> baz
38 copy: bar -> baz
39 % add a new revision in the original repo
39 % add a new revision in the original repo
40 destination new is a Mercurial repository
41 scanning source...
40 scanning source...
42 sorting...
41 sorting...
43 converting...
42 converting...
44 0 add baz
43 0 add baz
45 comparing with ../orig
44 comparing with ../orig
46 searching for changes
45 searching for changes
47 no changes found
46 no changes found
48 % dirstate should be the same (no output below):
47 % dirstate should be the same (no output below):
49 % no copies
48 % no copies
50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51 baz not renamed
50 baz not renamed
@@ -1,33 +1,37 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 cat >> $HGRCPATH <<EOF
4 echo "hgext.convert=" >> $HGRCPATH
4 [extensions]
5 convert=
6 [convert]
7 hg.saverev=False
8 EOF
5
9
6 hg init orig
10 hg init orig
7 cd orig
11 cd orig
8
12
9 echo foo > foo
13 echo foo > foo
10 echo bar > bar
14 echo bar > bar
11 hg ci -qAm 'add foo bar' -d '0 0'
15 hg ci -qAm 'add foo bar' -d '0 0'
12
16
13 echo >> foo
17 echo >> foo
14 hg ci -m 'change foo' -d '1 0'
18 hg ci -m 'change foo' -d '1 0'
15
19
16 hg up -qC 0
20 hg up -qC 0
17 hg copy --after --force foo bar
21 hg copy --after --force foo bar
18 hg copy foo baz
22 hg copy foo baz
19 hg ci -m 'make bar and baz copies of foo' -d '2 0'
23 hg ci -m 'make bar and baz copies of foo' -d '2 0'
20
24
21 hg merge
25 hg merge
22 hg ci -m 'merge local copy' -d '3 0'
26 hg ci -m 'merge local copy' -d '3 0'
23
27
24 hg up -C 1
28 hg up -C 1
25 hg merge 2
29 hg merge 2
26 hg ci -m 'merge remote copy' -d '4 0'
30 hg ci -m 'merge remote copy' -d '4 0'
27
31
28 cd ..
32 cd ..
29 hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
33 hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
30 cd new
34 cd new
31 hg out ../orig
35 hg out ../orig
32
36
33 true
37 true
@@ -1,114 +1,112 b''
1 % initial svn import
1 % initial svn import
2 Adding t/a
2 Adding t/a
3
3
4 Committed revision 1.
4 Committed revision 1.
5 % update svn repository
5 % update svn repository
6 A t2/a
6 A t2/a
7 Checked out revision 1.
7 Checked out revision 1.
8 A b
8 A b
9 Sending a
9 Sending a
10 Adding b
10 Adding b
11 Transmitting file data ..
11 Transmitting file data ..
12 Committed revision 2.
12 Committed revision 2.
13 % convert to hg once
13 % convert to hg once
14 assuming destination trunk-hg
14 assuming destination trunk-hg
15 initializing destination trunk-hg repository
15 initializing destination trunk-hg repository
16 scanning source...
16 scanning source...
17 sorting...
17 sorting...
18 converting...
18 converting...
19 1 init
19 1 init
20 0 changea
20 0 changea
21 % update svn repository again
21 % update svn repository again
22 Sending a
22 Sending a
23 Sending b
23 Sending b
24 Transmitting file data ..
24 Transmitting file data ..
25 Committed revision 3.
25 Committed revision 3.
26 % test incremental conversion
26 % test incremental conversion
27 assuming destination trunk-hg
27 assuming destination trunk-hg
28 destination trunk-hg is a Mercurial repository
29 scanning source...
28 scanning source...
30 sorting...
29 sorting...
31 converting...
30 converting...
32 0 changeb
31 0 changeb
33 % test filemap
32 % test filemap
34 initializing destination fmap repository
33 initializing destination fmap repository
35 scanning source...
34 scanning source...
36 sorting...
35 sorting...
37 converting...
36 converting...
38 2 init
37 2 init
39 1 changea
38 1 changea
40 0 changeb
39 0 changeb
41 o 1 changeb files: b
40 o 1 changeb files: b
42 |
41 |
43 o 0 changea files: b
42 o 0 changea files: b
44
43
45 # now tests that it works with trunk/branches/tags layout
44 # now tests that it works with trunk/branches/tags layout
46
45
47 % initial svn import
46 % initial svn import
48 Adding projA/trunk
47 Adding projA/trunk
49 Adding projA/branches
48 Adding projA/branches
50 Adding projA/tags
49 Adding projA/tags
51
50
52 Committed revision 4.
51 Committed revision 4.
53 % update svn repository
52 % update svn repository
54 Checked out revision 4.
53 Checked out revision 4.
55 A letter.txt
54 A letter.txt
56 Adding letter.txt
55 Adding letter.txt
57 Transmitting file data .
56 Transmitting file data .
58 Committed revision 5.
57 Committed revision 5.
59 Sending letter.txt
58 Sending letter.txt
60 Transmitting file data .
59 Transmitting file data .
61 Committed revision 6.
60 Committed revision 6.
62
61
63 Committed revision 7.
62 Committed revision 7.
64 Sending letter.txt
63 Sending letter.txt
65 Transmitting file data .
64 Transmitting file data .
66 Committed revision 8.
65 Committed revision 8.
67 % convert to hg once
66 % convert to hg once
68 initializing destination A-hg repository
67 initializing destination A-hg repository
69 scanning source...
68 scanning source...
70 sorting...
69 sorting...
71 converting...
70 converting...
72 3 init projA
71 3 init projA
73 2 hello
72 2 hello
74 1 world
73 1 world
75 0 nice day
74 0 nice day
76 updating tags
75 updating tags
77 % update svn repository again
76 % update svn repository again
78 A letter2.txt
77 A letter2.txt
79 Sending letter.txt
78 Sending letter.txt
80 Adding letter2.txt
79 Adding letter2.txt
81 Transmitting file data ..
80 Transmitting file data ..
82 Committed revision 9.
81 Committed revision 9.
83
82
84 Committed revision 10.
83 Committed revision 10.
85 Sending letter2.txt
84 Sending letter2.txt
86 Transmitting file data .
85 Transmitting file data .
87 Committed revision 11.
86 Committed revision 11.
88 % test incremental conversion
87 % test incremental conversion
89 destination A-hg is a Mercurial repository
90 scanning source...
88 scanning source...
91 sorting...
89 sorting...
92 converting...
90 converting...
93 1 second letter
91 1 second letter
94 0 work in progress
92 0 work in progress
95 updating tags
93 updating tags
96 o 7 update tags files: .hgtags
94 o 7 update tags files: .hgtags
97 |
95 |
98 o 6 work in progress files: letter2.txt
96 o 6 work in progress files: letter2.txt
99 |
97 |
100 o 5 second letter files: letter.txt letter2.txt
98 o 5 second letter files: letter.txt letter2.txt
101 |
99 |
102 o 4 update tags files: .hgtags
100 o 4 update tags files: .hgtags
103 |
101 |
104 o 3 nice day files: letter.txt
102 o 3 nice day files: letter.txt
105 |
103 |
106 o 2 world files: letter.txt
104 o 2 world files: letter.txt
107 |
105 |
108 o 1 hello files: letter.txt
106 o 1 hello files: letter.txt
109 |
107 |
110 o 0 init projA files:
108 o 0 init projA files:
111
109
112 tip
110 tip
113 v0.2
111 v0.2
114 v0.1
112 v0.1
@@ -1,96 +1,114 b''
1 hg convert [OPTION]... SOURCE [DEST [MAPFILE]]
1 hg convert [OPTION]... SOURCE [DEST [MAPFILE]]
2
2
3 Convert a foreign SCM repository to a Mercurial one.
3 Convert a foreign SCM repository to a Mercurial one.
4
4
5 Accepted source formats:
5 Accepted source formats:
6 - Mercurial
6 - Mercurial
7 - CVS
7 - CVS
8 - Darcs
8 - Darcs
9 - git
9 - git
10 - Subversion
10 - Subversion
11
11
12 Accepted destination formats:
12 Accepted destination formats:
13 - Mercurial
13 - Mercurial
14 - Subversion (history on branches is not preserved)
14 - Subversion (history on branches is not preserved)
15
15
16 If no revision is given, all revisions will be converted. Otherwise,
16 If no revision is given, all revisions will be converted. Otherwise,
17 convert will only import up to the named revision (given in a format
17 convert will only import up to the named revision (given in a format
18 understood by the source).
18 understood by the source).
19
19
20 If no destination directory name is specified, it defaults to the
20 If no destination directory name is specified, it defaults to the
21 basename of the source with '-hg' appended. If the destination
21 basename of the source with '-hg' appended. If the destination
22 repository doesn't exist, it will be created.
22 repository doesn't exist, it will be created.
23
23
24 If <MAPFILE> isn't given, it will be put in a default location
24 If <MAPFILE> isn't given, it will be put in a default location
25 (<dest>/.hg/shamap by default). The <MAPFILE> is a simple text
25 (<dest>/.hg/shamap by default). The <MAPFILE> is a simple text
26 file that maps each source commit ID to the destination ID for
26 file that maps each source commit ID to the destination ID for
27 that revision, like so:
27 that revision, like so:
28 <source ID> <destination ID>
28 <source ID> <destination ID>
29
29
30 If the file doesn't exist, it's automatically created. It's updated
30 If the file doesn't exist, it's automatically created. It's updated
31 on each commit copied, so convert-repo can be interrupted and can
31 on each commit copied, so convert-repo can be interrupted and can
32 be run repeatedly to copy new commits.
32 be run repeatedly to copy new commits.
33
33
34 The [username mapping] file is a simple text file that maps each source
34 The [username mapping] file is a simple text file that maps each source
35 commit author to a destination commit author. It is handy for source SCMs
35 commit author to a destination commit author. It is handy for source SCMs
36 that use unix logins to identify authors (eg: CVS). One line per author
36 that use unix logins to identify authors (eg: CVS). One line per author
37 mapping and the line format is:
37 mapping and the line format is:
38 srcauthor=whatever string you want
38 srcauthor=whatever string you want
39
39
40 The filemap is a file that allows filtering and remapping of files
40 The filemap is a file that allows filtering and remapping of files
41 and directories. Comment lines start with '#'. Each line can
41 and directories. Comment lines start with '#'. Each line can
42 contain one of the following directives:
42 contain one of the following directives:
43
43
44 include path/to/file
44 include path/to/file
45
45
46 exclude path/to/file
46 exclude path/to/file
47
47
48 rename from/file to/file
48 rename from/file to/file
49
49
50 The 'include' directive causes a file, or all files under a
50 The 'include' directive causes a file, or all files under a
51 directory, to be included in the destination repository, and the
51 directory, to be included in the destination repository, and the
52 exclusion of all other files and dirs not explicitely included.
52 exclusion of all other files and dirs not explicitely included.
53 The 'exclude' directive causes files or directories to be omitted.
53 The 'exclude' directive causes files or directories to be omitted.
54 The 'rename' directive renames a file or directory. To rename from a
54 The 'rename' directive renames a file or directory. To rename from a
55 subdirectory into the root of the repository, use '.' as the path to
55 subdirectory into the root of the repository, use '.' as the path to
56 rename to.
56 rename to.
57
57
58 Back end options:
59
60 --config convert.hg.clonebranches=False (boolean)
61 hg target: XXX not documented
62 --config convert.hg.saverev=True (boolean)
63 hg source: allow target to preserve source revision ID
64 --config convert.hg.tagsbranch=default (branch name)
65 hg target: XXX not documented
66 --config convert.hg.usebranchnames=True (boolean)
67 hg target: preserve branch names
68
69 --config convert.svn.branches=branches (directory name)
70 svn source: specify the directory containing branches
71 --config convert.svn.tags=tags (directory name)
72 svn source: specify the directory containing tags
73 --config convert.svn.trunk=trunk (directory name)
74 svn source: specify the name of the trunk branch
75
58 options:
76 options:
59
77
60 -A --authors username mapping filename
78 -A --authors username mapping filename
61 -d --dest-type destination repository type
79 -d --dest-type destination repository type
62 --filemap remap file names using contents of file
80 --filemap remap file names using contents of file
63 -r --rev import up to target revision REV
81 -r --rev import up to target revision REV
64 -s --source-type source repository type
82 -s --source-type source repository type
65 --datesort try to sort changesets by date
83 --datesort try to sort changesets by date
66
84
67 use "hg -v help convert" to show global options
85 use "hg -v help convert" to show global options
68 adding a
86 adding a
69 assuming destination a-hg
87 assuming destination a-hg
70 initializing destination a-hg repository
88 initializing destination a-hg repository
71 scanning source...
89 scanning source...
72 sorting...
90 sorting...
73 converting...
91 converting...
74 4 a
92 4 a
75 3 b
93 3 b
76 2 c
94 2 c
77 1 d
95 1 d
78 0 e
96 0 e
79 pulling from ../a
97 pulling from ../a
80 searching for changes
98 searching for changes
81 no changes found
99 no changes found
82 % should fail
100 % should fail
83 initializing destination bogusfile repository
101 initializing destination bogusfile repository
84 abort: cannot create new bundle repository
102 abort: cannot create new bundle repository
85 % should fail
103 % should fail
86 abort: Permission denied: bogusdir
104 abort: Permission denied: bogusdir
87 % should succeed
105 % should succeed
88 initializing destination bogusdir repository
106 initializing destination bogusdir repository
89 scanning source...
107 scanning source...
90 sorting...
108 sorting...
91 converting...
109 converting...
92 4 a
110 4 a
93 3 b
111 3 b
94 2 c
112 2 c
95 1 d
113 1 d
96 0 e
114 0 e
General Comments 0
You need to be logged in to leave comments. Login now