##// END OF EJS Templates
convert: use 'default' for specifying branch name in branchmap (issue4753)...
Eugene Baranov -
r25805:584044e5 default
parent child Browse files
Show More
@@ -1,548 +1,576 b''
1 # convcmd - convert extension commands definition
1 # convcmd - convert extension commands definition
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from common import NoRepo, MissingTool, SKIPREV, mapfile
8 from common import NoRepo, MissingTool, SKIPREV, 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 svn_source, svn_sink
13 from subversion import svn_source, svn_sink
14 from monotone import monotone_source
14 from monotone import monotone_source
15 from gnuarch import gnuarch_source
15 from gnuarch import gnuarch_source
16 from bzr import bzr_source
16 from bzr import bzr_source
17 from p4 import p4_source
17 from p4 import p4_source
18 import filemap
18 import filemap
19
19
20 import os, shutil, shlex
20 import os, shutil, shlex
21 from mercurial import hg, util, encoding
21 from mercurial import hg, util, encoding
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23
23
24 orig_encoding = 'ascii'
24 orig_encoding = 'ascii'
25
25
26 def recode(s):
26 def recode(s):
27 if isinstance(s, unicode):
27 if isinstance(s, unicode):
28 return s.encode(orig_encoding, 'replace')
28 return s.encode(orig_encoding, 'replace')
29 else:
29 else:
30 return s.decode('utf-8').encode(orig_encoding, 'replace')
30 return s.decode('utf-8').encode(orig_encoding, 'replace')
31
31
32 def mapbranch(branch, branchmap):
33 '''
34 >>> bmap = {'default': 'branch1'}
35 >>> for i in ['', None]:
36 ... mapbranch(i, bmap)
37 'branch1'
38 'branch1'
39 >>> bmap = {'None': 'branch2'}
40 >>> for i in ['', None]:
41 ... mapbranch(i, bmap)
42 'branch2'
43 'branch2'
44 >>> bmap = {'None': 'branch3', 'default': 'branch4'}
45 >>> for i in ['None', '', None, 'default', 'branch5']:
46 ... mapbranch(i, bmap)
47 'branch3'
48 'branch4'
49 'branch4'
50 'branch4'
51 'branch5'
52 '''
53 # If branch is None or empty, this commit is coming from the source
54 # repository's default branch and destined for the default branch in the
55 # destination repository. For such commits, using a literal "default"
56 # in branchmap below allows the user to map "default" to an alternate
57 # default branch in the destination repository.
58 branch = branchmap.get(branch or 'default', branch)
59 # At some point we used "None" literal to denote the default branch,
60 # attempt to use that for backward compatibility.
61 if (not branch):
62 branch = branchmap.get(str(None), branch)
63 return branch
64
32 source_converters = [
65 source_converters = [
33 ('cvs', convert_cvs, 'branchsort'),
66 ('cvs', convert_cvs, 'branchsort'),
34 ('git', convert_git, 'branchsort'),
67 ('git', convert_git, 'branchsort'),
35 ('svn', svn_source, 'branchsort'),
68 ('svn', svn_source, 'branchsort'),
36 ('hg', mercurial_source, 'sourcesort'),
69 ('hg', mercurial_source, 'sourcesort'),
37 ('darcs', darcs_source, 'branchsort'),
70 ('darcs', darcs_source, 'branchsort'),
38 ('mtn', monotone_source, 'branchsort'),
71 ('mtn', monotone_source, 'branchsort'),
39 ('gnuarch', gnuarch_source, 'branchsort'),
72 ('gnuarch', gnuarch_source, 'branchsort'),
40 ('bzr', bzr_source, 'branchsort'),
73 ('bzr', bzr_source, 'branchsort'),
41 ('p4', p4_source, 'branchsort'),
74 ('p4', p4_source, 'branchsort'),
42 ]
75 ]
43
76
44 sink_converters = [
77 sink_converters = [
45 ('hg', mercurial_sink),
78 ('hg', mercurial_sink),
46 ('svn', svn_sink),
79 ('svn', svn_sink),
47 ]
80 ]
48
81
49 def convertsource(ui, path, type, revs):
82 def convertsource(ui, path, type, revs):
50 exceptions = []
83 exceptions = []
51 if type and type not in [s[0] for s in source_converters]:
84 if type and type not in [s[0] for s in source_converters]:
52 raise util.Abort(_('%s: invalid source repository type') % type)
85 raise util.Abort(_('%s: invalid source repository type') % type)
53 for name, source, sortmode in source_converters:
86 for name, source, sortmode in source_converters:
54 try:
87 try:
55 if not type or name == type:
88 if not type or name == type:
56 return source(ui, path, revs), sortmode
89 return source(ui, path, revs), sortmode
57 except (NoRepo, MissingTool) as inst:
90 except (NoRepo, MissingTool) as inst:
58 exceptions.append(inst)
91 exceptions.append(inst)
59 if not ui.quiet:
92 if not ui.quiet:
60 for inst in exceptions:
93 for inst in exceptions:
61 ui.write("%s\n" % inst)
94 ui.write("%s\n" % inst)
62 raise util.Abort(_('%s: missing or unsupported repository') % path)
95 raise util.Abort(_('%s: missing or unsupported repository') % path)
63
96
64 def convertsink(ui, path, type):
97 def convertsink(ui, path, type):
65 if type and type not in [s[0] for s in sink_converters]:
98 if type and type not in [s[0] for s in sink_converters]:
66 raise util.Abort(_('%s: invalid destination repository type') % type)
99 raise util.Abort(_('%s: invalid destination repository type') % type)
67 for name, sink in sink_converters:
100 for name, sink in sink_converters:
68 try:
101 try:
69 if not type or name == type:
102 if not type or name == type:
70 return sink(ui, path)
103 return sink(ui, path)
71 except NoRepo as inst:
104 except NoRepo as inst:
72 ui.note(_("convert: %s\n") % inst)
105 ui.note(_("convert: %s\n") % inst)
73 except MissingTool as inst:
106 except MissingTool as inst:
74 raise util.Abort('%s\n' % inst)
107 raise util.Abort('%s\n' % inst)
75 raise util.Abort(_('%s: unknown repository type') % path)
108 raise util.Abort(_('%s: unknown repository type') % path)
76
109
77 class progresssource(object):
110 class progresssource(object):
78 def __init__(self, ui, source, filecount):
111 def __init__(self, ui, source, filecount):
79 self.ui = ui
112 self.ui = ui
80 self.source = source
113 self.source = source
81 self.filecount = filecount
114 self.filecount = filecount
82 self.retrieved = 0
115 self.retrieved = 0
83
116
84 def getfile(self, file, rev):
117 def getfile(self, file, rev):
85 self.retrieved += 1
118 self.retrieved += 1
86 self.ui.progress(_('getting files'), self.retrieved,
119 self.ui.progress(_('getting files'), self.retrieved,
87 item=file, total=self.filecount)
120 item=file, total=self.filecount)
88 return self.source.getfile(file, rev)
121 return self.source.getfile(file, rev)
89
122
90 def lookuprev(self, rev):
123 def lookuprev(self, rev):
91 return self.source.lookuprev(rev)
124 return self.source.lookuprev(rev)
92
125
93 def close(self):
126 def close(self):
94 self.ui.progress(_('getting files'), None)
127 self.ui.progress(_('getting files'), None)
95
128
96 class converter(object):
129 class converter(object):
97 def __init__(self, ui, source, dest, revmapfile, opts):
130 def __init__(self, ui, source, dest, revmapfile, opts):
98
131
99 self.source = source
132 self.source = source
100 self.dest = dest
133 self.dest = dest
101 self.ui = ui
134 self.ui = ui
102 self.opts = opts
135 self.opts = opts
103 self.commitcache = {}
136 self.commitcache = {}
104 self.authors = {}
137 self.authors = {}
105 self.authorfile = None
138 self.authorfile = None
106
139
107 # Record converted revisions persistently: maps source revision
140 # Record converted revisions persistently: maps source revision
108 # ID to target revision ID (both strings). (This is how
141 # ID to target revision ID (both strings). (This is how
109 # incremental conversions work.)
142 # incremental conversions work.)
110 self.map = mapfile(ui, revmapfile)
143 self.map = mapfile(ui, revmapfile)
111
144
112 # Read first the dst author map if any
145 # Read first the dst author map if any
113 authorfile = self.dest.authorfile()
146 authorfile = self.dest.authorfile()
114 if authorfile and os.path.exists(authorfile):
147 if authorfile and os.path.exists(authorfile):
115 self.readauthormap(authorfile)
148 self.readauthormap(authorfile)
116 # Extend/Override with new author map if necessary
149 # Extend/Override with new author map if necessary
117 if opts.get('authormap'):
150 if opts.get('authormap'):
118 self.readauthormap(opts.get('authormap'))
151 self.readauthormap(opts.get('authormap'))
119 self.authorfile = self.dest.authorfile()
152 self.authorfile = self.dest.authorfile()
120
153
121 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
154 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
122 self.branchmap = mapfile(ui, opts.get('branchmap'))
155 self.branchmap = mapfile(ui, opts.get('branchmap'))
123
156
124 def parsesplicemap(self, path):
157 def parsesplicemap(self, path):
125 """ check and validate the splicemap format and
158 """ check and validate the splicemap format and
126 return a child/parents dictionary.
159 return a child/parents dictionary.
127 Format checking has two parts.
160 Format checking has two parts.
128 1. generic format which is same across all source types
161 1. generic format which is same across all source types
129 2. specific format checking which may be different for
162 2. specific format checking which may be different for
130 different source type. This logic is implemented in
163 different source type. This logic is implemented in
131 checkrevformat function in source files like
164 checkrevformat function in source files like
132 hg.py, subversion.py etc.
165 hg.py, subversion.py etc.
133 """
166 """
134
167
135 if not path:
168 if not path:
136 return {}
169 return {}
137 m = {}
170 m = {}
138 try:
171 try:
139 fp = open(path, 'r')
172 fp = open(path, 'r')
140 for i, line in enumerate(fp):
173 for i, line in enumerate(fp):
141 line = line.splitlines()[0].rstrip()
174 line = line.splitlines()[0].rstrip()
142 if not line:
175 if not line:
143 # Ignore blank lines
176 # Ignore blank lines
144 continue
177 continue
145 # split line
178 # split line
146 lex = shlex.shlex(line, posix=True)
179 lex = shlex.shlex(line, posix=True)
147 lex.whitespace_split = True
180 lex.whitespace_split = True
148 lex.whitespace += ','
181 lex.whitespace += ','
149 line = list(lex)
182 line = list(lex)
150 # check number of parents
183 # check number of parents
151 if not (2 <= len(line) <= 3):
184 if not (2 <= len(line) <= 3):
152 raise util.Abort(_('syntax error in %s(%d): child parent1'
185 raise util.Abort(_('syntax error in %s(%d): child parent1'
153 '[,parent2] expected') % (path, i + 1))
186 '[,parent2] expected') % (path, i + 1))
154 for part in line:
187 for part in line:
155 self.source.checkrevformat(part)
188 self.source.checkrevformat(part)
156 child, p1, p2 = line[0], line[1:2], line[2:]
189 child, p1, p2 = line[0], line[1:2], line[2:]
157 if p1 == p2:
190 if p1 == p2:
158 m[child] = p1
191 m[child] = p1
159 else:
192 else:
160 m[child] = p1 + p2
193 m[child] = p1 + p2
161 # if file does not exist or error reading, exit
194 # if file does not exist or error reading, exit
162 except IOError:
195 except IOError:
163 raise util.Abort(_('splicemap file not found or error reading %s:')
196 raise util.Abort(_('splicemap file not found or error reading %s:')
164 % path)
197 % path)
165 return m
198 return m
166
199
167
200
168 def walktree(self, heads):
201 def walktree(self, heads):
169 '''Return a mapping that identifies the uncommitted parents of every
202 '''Return a mapping that identifies the uncommitted parents of every
170 uncommitted changeset.'''
203 uncommitted changeset.'''
171 visit = heads
204 visit = heads
172 known = set()
205 known = set()
173 parents = {}
206 parents = {}
174 numcommits = self.source.numcommits()
207 numcommits = self.source.numcommits()
175 while visit:
208 while visit:
176 n = visit.pop(0)
209 n = visit.pop(0)
177 if n in known:
210 if n in known:
178 continue
211 continue
179 if n in self.map:
212 if n in self.map:
180 m = self.map[n]
213 m = self.map[n]
181 if m == SKIPREV or self.dest.hascommitfrommap(m):
214 if m == SKIPREV or self.dest.hascommitfrommap(m):
182 continue
215 continue
183 known.add(n)
216 known.add(n)
184 self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
217 self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
185 total=numcommits)
218 total=numcommits)
186 commit = self.cachecommit(n)
219 commit = self.cachecommit(n)
187 parents[n] = []
220 parents[n] = []
188 for p in commit.parents:
221 for p in commit.parents:
189 parents[n].append(p)
222 parents[n].append(p)
190 visit.append(p)
223 visit.append(p)
191 self.ui.progress(_('scanning'), None)
224 self.ui.progress(_('scanning'), None)
192
225
193 return parents
226 return parents
194
227
195 def mergesplicemap(self, parents, splicemap):
228 def mergesplicemap(self, parents, splicemap):
196 """A splicemap redefines child/parent relationships. Check the
229 """A splicemap redefines child/parent relationships. Check the
197 map contains valid revision identifiers and merge the new
230 map contains valid revision identifiers and merge the new
198 links in the source graph.
231 links in the source graph.
199 """
232 """
200 for c in sorted(splicemap):
233 for c in sorted(splicemap):
201 if c not in parents:
234 if c not in parents:
202 if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
235 if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
203 # Could be in source but not converted during this run
236 # Could be in source but not converted during this run
204 self.ui.warn(_('splice map revision %s is not being '
237 self.ui.warn(_('splice map revision %s is not being '
205 'converted, ignoring\n') % c)
238 'converted, ignoring\n') % c)
206 continue
239 continue
207 pc = []
240 pc = []
208 for p in splicemap[c]:
241 for p in splicemap[c]:
209 # We do not have to wait for nodes already in dest.
242 # We do not have to wait for nodes already in dest.
210 if self.dest.hascommitforsplicemap(self.map.get(p, p)):
243 if self.dest.hascommitforsplicemap(self.map.get(p, p)):
211 continue
244 continue
212 # Parent is not in dest and not being converted, not good
245 # Parent is not in dest and not being converted, not good
213 if p not in parents:
246 if p not in parents:
214 raise util.Abort(_('unknown splice map parent: %s') % p)
247 raise util.Abort(_('unknown splice map parent: %s') % p)
215 pc.append(p)
248 pc.append(p)
216 parents[c] = pc
249 parents[c] = pc
217
250
218 def toposort(self, parents, sortmode):
251 def toposort(self, parents, sortmode):
219 '''Return an ordering such that every uncommitted changeset is
252 '''Return an ordering such that every uncommitted changeset is
220 preceded by all its uncommitted ancestors.'''
253 preceded by all its uncommitted ancestors.'''
221
254
222 def mapchildren(parents):
255 def mapchildren(parents):
223 """Return a (children, roots) tuple where 'children' maps parent
256 """Return a (children, roots) tuple where 'children' maps parent
224 revision identifiers to children ones, and 'roots' is the list of
257 revision identifiers to children ones, and 'roots' is the list of
225 revisions without parents. 'parents' must be a mapping of revision
258 revisions without parents. 'parents' must be a mapping of revision
226 identifier to its parents ones.
259 identifier to its parents ones.
227 """
260 """
228 visit = sorted(parents)
261 visit = sorted(parents)
229 seen = set()
262 seen = set()
230 children = {}
263 children = {}
231 roots = []
264 roots = []
232
265
233 while visit:
266 while visit:
234 n = visit.pop(0)
267 n = visit.pop(0)
235 if n in seen:
268 if n in seen:
236 continue
269 continue
237 seen.add(n)
270 seen.add(n)
238 # Ensure that nodes without parents are present in the
271 # Ensure that nodes without parents are present in the
239 # 'children' mapping.
272 # 'children' mapping.
240 children.setdefault(n, [])
273 children.setdefault(n, [])
241 hasparent = False
274 hasparent = False
242 for p in parents[n]:
275 for p in parents[n]:
243 if p not in self.map:
276 if p not in self.map:
244 visit.append(p)
277 visit.append(p)
245 hasparent = True
278 hasparent = True
246 children.setdefault(p, []).append(n)
279 children.setdefault(p, []).append(n)
247 if not hasparent:
280 if not hasparent:
248 roots.append(n)
281 roots.append(n)
249
282
250 return children, roots
283 return children, roots
251
284
252 # Sort functions are supposed to take a list of revisions which
285 # Sort functions are supposed to take a list of revisions which
253 # can be converted immediately and pick one
286 # can be converted immediately and pick one
254
287
255 def makebranchsorter():
288 def makebranchsorter():
256 """If the previously converted revision has a child in the
289 """If the previously converted revision has a child in the
257 eligible revisions list, pick it. Return the list head
290 eligible revisions list, pick it. Return the list head
258 otherwise. Branch sort attempts to minimize branch
291 otherwise. Branch sort attempts to minimize branch
259 switching, which is harmful for Mercurial backend
292 switching, which is harmful for Mercurial backend
260 compression.
293 compression.
261 """
294 """
262 prev = [None]
295 prev = [None]
263 def picknext(nodes):
296 def picknext(nodes):
264 next = nodes[0]
297 next = nodes[0]
265 for n in nodes:
298 for n in nodes:
266 if prev[0] in parents[n]:
299 if prev[0] in parents[n]:
267 next = n
300 next = n
268 break
301 break
269 prev[0] = next
302 prev[0] = next
270 return next
303 return next
271 return picknext
304 return picknext
272
305
273 def makesourcesorter():
306 def makesourcesorter():
274 """Source specific sort."""
307 """Source specific sort."""
275 keyfn = lambda n: self.commitcache[n].sortkey
308 keyfn = lambda n: self.commitcache[n].sortkey
276 def picknext(nodes):
309 def picknext(nodes):
277 return sorted(nodes, key=keyfn)[0]
310 return sorted(nodes, key=keyfn)[0]
278 return picknext
311 return picknext
279
312
280 def makeclosesorter():
313 def makeclosesorter():
281 """Close order sort."""
314 """Close order sort."""
282 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
315 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
283 self.commitcache[n].sortkey)
316 self.commitcache[n].sortkey)
284 def picknext(nodes):
317 def picknext(nodes):
285 return sorted(nodes, key=keyfn)[0]
318 return sorted(nodes, key=keyfn)[0]
286 return picknext
319 return picknext
287
320
288 def makedatesorter():
321 def makedatesorter():
289 """Sort revisions by date."""
322 """Sort revisions by date."""
290 dates = {}
323 dates = {}
291 def getdate(n):
324 def getdate(n):
292 if n not in dates:
325 if n not in dates:
293 dates[n] = util.parsedate(self.commitcache[n].date)
326 dates[n] = util.parsedate(self.commitcache[n].date)
294 return dates[n]
327 return dates[n]
295
328
296 def picknext(nodes):
329 def picknext(nodes):
297 return min([(getdate(n), n) for n in nodes])[1]
330 return min([(getdate(n), n) for n in nodes])[1]
298
331
299 return picknext
332 return picknext
300
333
301 if sortmode == 'branchsort':
334 if sortmode == 'branchsort':
302 picknext = makebranchsorter()
335 picknext = makebranchsorter()
303 elif sortmode == 'datesort':
336 elif sortmode == 'datesort':
304 picknext = makedatesorter()
337 picknext = makedatesorter()
305 elif sortmode == 'sourcesort':
338 elif sortmode == 'sourcesort':
306 picknext = makesourcesorter()
339 picknext = makesourcesorter()
307 elif sortmode == 'closesort':
340 elif sortmode == 'closesort':
308 picknext = makeclosesorter()
341 picknext = makeclosesorter()
309 else:
342 else:
310 raise util.Abort(_('unknown sort mode: %s') % sortmode)
343 raise util.Abort(_('unknown sort mode: %s') % sortmode)
311
344
312 children, actives = mapchildren(parents)
345 children, actives = mapchildren(parents)
313
346
314 s = []
347 s = []
315 pendings = {}
348 pendings = {}
316 while actives:
349 while actives:
317 n = picknext(actives)
350 n = picknext(actives)
318 actives.remove(n)
351 actives.remove(n)
319 s.append(n)
352 s.append(n)
320
353
321 # Update dependents list
354 # Update dependents list
322 for c in children.get(n, []):
355 for c in children.get(n, []):
323 if c not in pendings:
356 if c not in pendings:
324 pendings[c] = [p for p in parents[c] if p not in self.map]
357 pendings[c] = [p for p in parents[c] if p not in self.map]
325 try:
358 try:
326 pendings[c].remove(n)
359 pendings[c].remove(n)
327 except ValueError:
360 except ValueError:
328 raise util.Abort(_('cycle detected between %s and %s')
361 raise util.Abort(_('cycle detected between %s and %s')
329 % (recode(c), recode(n)))
362 % (recode(c), recode(n)))
330 if not pendings[c]:
363 if not pendings[c]:
331 # Parents are converted, node is eligible
364 # Parents are converted, node is eligible
332 actives.insert(0, c)
365 actives.insert(0, c)
333 pendings[c] = None
366 pendings[c] = None
334
367
335 if len(s) != len(parents):
368 if len(s) != len(parents):
336 raise util.Abort(_("not all revisions were sorted"))
369 raise util.Abort(_("not all revisions were sorted"))
337
370
338 return s
371 return s
339
372
340 def writeauthormap(self):
373 def writeauthormap(self):
341 authorfile = self.authorfile
374 authorfile = self.authorfile
342 if authorfile:
375 if authorfile:
343 self.ui.status(_('writing author map file %s\n') % authorfile)
376 self.ui.status(_('writing author map file %s\n') % authorfile)
344 ofile = open(authorfile, 'w+')
377 ofile = open(authorfile, 'w+')
345 for author in self.authors:
378 for author in self.authors:
346 ofile.write("%s=%s\n" % (author, self.authors[author]))
379 ofile.write("%s=%s\n" % (author, self.authors[author]))
347 ofile.close()
380 ofile.close()
348
381
349 def readauthormap(self, authorfile):
382 def readauthormap(self, authorfile):
350 afile = open(authorfile, 'r')
383 afile = open(authorfile, 'r')
351 for line in afile:
384 for line in afile:
352
385
353 line = line.strip()
386 line = line.strip()
354 if not line or line.startswith('#'):
387 if not line or line.startswith('#'):
355 continue
388 continue
356
389
357 try:
390 try:
358 srcauthor, dstauthor = line.split('=', 1)
391 srcauthor, dstauthor = line.split('=', 1)
359 except ValueError:
392 except ValueError:
360 msg = _('ignoring bad line in author map file %s: %s\n')
393 msg = _('ignoring bad line in author map file %s: %s\n')
361 self.ui.warn(msg % (authorfile, line.rstrip()))
394 self.ui.warn(msg % (authorfile, line.rstrip()))
362 continue
395 continue
363
396
364 srcauthor = srcauthor.strip()
397 srcauthor = srcauthor.strip()
365 dstauthor = dstauthor.strip()
398 dstauthor = dstauthor.strip()
366 if self.authors.get(srcauthor) in (None, dstauthor):
399 if self.authors.get(srcauthor) in (None, dstauthor):
367 msg = _('mapping author %s to %s\n')
400 msg = _('mapping author %s to %s\n')
368 self.ui.debug(msg % (srcauthor, dstauthor))
401 self.ui.debug(msg % (srcauthor, dstauthor))
369 self.authors[srcauthor] = dstauthor
402 self.authors[srcauthor] = dstauthor
370 continue
403 continue
371
404
372 m = _('overriding mapping for author %s, was %s, will be %s\n')
405 m = _('overriding mapping for author %s, was %s, will be %s\n')
373 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
406 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
374
407
375 afile.close()
408 afile.close()
376
409
377 def cachecommit(self, rev):
410 def cachecommit(self, rev):
378 commit = self.source.getcommit(rev)
411 commit = self.source.getcommit(rev)
379 commit.author = self.authors.get(commit.author, commit.author)
412 commit.author = self.authors.get(commit.author, commit.author)
380 # If commit.branch is None, this commit is coming from the source
413 commit.branch = mapbranch(commit.branch, self.branchmap)
381 # repository's default branch and destined for the default branch in the
382 # destination repository. For such commits, passing a literal "None"
383 # string to branchmap.get() below allows the user to map "None" to an
384 # alternate default branch in the destination repository.
385 commit.branch = self.branchmap.get(str(commit.branch), commit.branch)
386 self.commitcache[rev] = commit
414 self.commitcache[rev] = commit
387 return commit
415 return commit
388
416
389 def copy(self, rev):
417 def copy(self, rev):
390 commit = self.commitcache[rev]
418 commit = self.commitcache[rev]
391 full = self.opts.get('full')
419 full = self.opts.get('full')
392 changes = self.source.getchanges(rev, full)
420 changes = self.source.getchanges(rev, full)
393 if isinstance(changes, basestring):
421 if isinstance(changes, basestring):
394 if changes == SKIPREV:
422 if changes == SKIPREV:
395 dest = SKIPREV
423 dest = SKIPREV
396 else:
424 else:
397 dest = self.map[changes]
425 dest = self.map[changes]
398 self.map[rev] = dest
426 self.map[rev] = dest
399 return
427 return
400 files, copies, cleanp2 = changes
428 files, copies, cleanp2 = changes
401 pbranches = []
429 pbranches = []
402 if commit.parents:
430 if commit.parents:
403 for prev in commit.parents:
431 for prev in commit.parents:
404 if prev not in self.commitcache:
432 if prev not in self.commitcache:
405 self.cachecommit(prev)
433 self.cachecommit(prev)
406 pbranches.append((self.map[prev],
434 pbranches.append((self.map[prev],
407 self.commitcache[prev].branch))
435 self.commitcache[prev].branch))
408 self.dest.setbranch(commit.branch, pbranches)
436 self.dest.setbranch(commit.branch, pbranches)
409 try:
437 try:
410 parents = self.splicemap[rev]
438 parents = self.splicemap[rev]
411 self.ui.status(_('spliced in %s as parents of %s\n') %
439 self.ui.status(_('spliced in %s as parents of %s\n') %
412 (parents, rev))
440 (parents, rev))
413 parents = [self.map.get(p, p) for p in parents]
441 parents = [self.map.get(p, p) for p in parents]
414 except KeyError:
442 except KeyError:
415 parents = [b[0] for b in pbranches]
443 parents = [b[0] for b in pbranches]
416 if len(pbranches) != 2:
444 if len(pbranches) != 2:
417 cleanp2 = set()
445 cleanp2 = set()
418 if len(parents) < 3:
446 if len(parents) < 3:
419 source = progresssource(self.ui, self.source, len(files))
447 source = progresssource(self.ui, self.source, len(files))
420 else:
448 else:
421 # For an octopus merge, we end up traversing the list of
449 # For an octopus merge, we end up traversing the list of
422 # changed files N-1 times. This tweak to the number of
450 # changed files N-1 times. This tweak to the number of
423 # files makes it so the progress bar doesn't overflow
451 # files makes it so the progress bar doesn't overflow
424 # itself.
452 # itself.
425 source = progresssource(self.ui, self.source,
453 source = progresssource(self.ui, self.source,
426 len(files) * (len(parents) - 1))
454 len(files) * (len(parents) - 1))
427 newnode = self.dest.putcommit(files, copies, parents, commit,
455 newnode = self.dest.putcommit(files, copies, parents, commit,
428 source, self.map, full, cleanp2)
456 source, self.map, full, cleanp2)
429 source.close()
457 source.close()
430 self.source.converted(rev, newnode)
458 self.source.converted(rev, newnode)
431 self.map[rev] = newnode
459 self.map[rev] = newnode
432
460
433 def convert(self, sortmode):
461 def convert(self, sortmode):
434 try:
462 try:
435 self.source.before()
463 self.source.before()
436 self.dest.before()
464 self.dest.before()
437 self.source.setrevmap(self.map)
465 self.source.setrevmap(self.map)
438 self.ui.status(_("scanning source...\n"))
466 self.ui.status(_("scanning source...\n"))
439 heads = self.source.getheads()
467 heads = self.source.getheads()
440 parents = self.walktree(heads)
468 parents = self.walktree(heads)
441 self.mergesplicemap(parents, self.splicemap)
469 self.mergesplicemap(parents, self.splicemap)
442 self.ui.status(_("sorting...\n"))
470 self.ui.status(_("sorting...\n"))
443 t = self.toposort(parents, sortmode)
471 t = self.toposort(parents, sortmode)
444 num = len(t)
472 num = len(t)
445 c = None
473 c = None
446
474
447 self.ui.status(_("converting...\n"))
475 self.ui.status(_("converting...\n"))
448 for i, c in enumerate(t):
476 for i, c in enumerate(t):
449 num -= 1
477 num -= 1
450 desc = self.commitcache[c].desc
478 desc = self.commitcache[c].desc
451 if "\n" in desc:
479 if "\n" in desc:
452 desc = desc.splitlines()[0]
480 desc = desc.splitlines()[0]
453 # convert log message to local encoding without using
481 # convert log message to local encoding without using
454 # tolocal() because the encoding.encoding convert()
482 # tolocal() because the encoding.encoding convert()
455 # uses is 'utf-8'
483 # uses is 'utf-8'
456 self.ui.status("%d %s\n" % (num, recode(desc)))
484 self.ui.status("%d %s\n" % (num, recode(desc)))
457 self.ui.note(_("source: %s\n") % recode(c))
485 self.ui.note(_("source: %s\n") % recode(c))
458 self.ui.progress(_('converting'), i, unit=_('revisions'),
486 self.ui.progress(_('converting'), i, unit=_('revisions'),
459 total=len(t))
487 total=len(t))
460 self.copy(c)
488 self.copy(c)
461 self.ui.progress(_('converting'), None)
489 self.ui.progress(_('converting'), None)
462
490
463 if not self.ui.configbool('convert', 'skiptags'):
491 if not self.ui.configbool('convert', 'skiptags'):
464 tags = self.source.gettags()
492 tags = self.source.gettags()
465 ctags = {}
493 ctags = {}
466 for k in tags:
494 for k in tags:
467 v = tags[k]
495 v = tags[k]
468 if self.map.get(v, SKIPREV) != SKIPREV:
496 if self.map.get(v, SKIPREV) != SKIPREV:
469 ctags[k] = self.map[v]
497 ctags[k] = self.map[v]
470
498
471 if c and ctags:
499 if c and ctags:
472 nrev, tagsparent = self.dest.puttags(ctags)
500 nrev, tagsparent = self.dest.puttags(ctags)
473 if nrev and tagsparent:
501 if nrev and tagsparent:
474 # write another hash correspondence to override the
502 # write another hash correspondence to override the
475 # previous one so we don't end up with extra tag heads
503 # previous one so we don't end up with extra tag heads
476 tagsparents = [e for e in self.map.iteritems()
504 tagsparents = [e for e in self.map.iteritems()
477 if e[1] == tagsparent]
505 if e[1] == tagsparent]
478 if tagsparents:
506 if tagsparents:
479 self.map[tagsparents[0][0]] = nrev
507 self.map[tagsparents[0][0]] = nrev
480
508
481 bookmarks = self.source.getbookmarks()
509 bookmarks = self.source.getbookmarks()
482 cbookmarks = {}
510 cbookmarks = {}
483 for k in bookmarks:
511 for k in bookmarks:
484 v = bookmarks[k]
512 v = bookmarks[k]
485 if self.map.get(v, SKIPREV) != SKIPREV:
513 if self.map.get(v, SKIPREV) != SKIPREV:
486 cbookmarks[k] = self.map[v]
514 cbookmarks[k] = self.map[v]
487
515
488 if c and cbookmarks:
516 if c and cbookmarks:
489 self.dest.putbookmarks(cbookmarks)
517 self.dest.putbookmarks(cbookmarks)
490
518
491 self.writeauthormap()
519 self.writeauthormap()
492 finally:
520 finally:
493 self.cleanup()
521 self.cleanup()
494
522
495 def cleanup(self):
523 def cleanup(self):
496 try:
524 try:
497 self.dest.after()
525 self.dest.after()
498 finally:
526 finally:
499 self.source.after()
527 self.source.after()
500 self.map.close()
528 self.map.close()
501
529
502 def convert(ui, src, dest=None, revmapfile=None, **opts):
530 def convert(ui, src, dest=None, revmapfile=None, **opts):
503 global orig_encoding
531 global orig_encoding
504 orig_encoding = encoding.encoding
532 orig_encoding = encoding.encoding
505 encoding.encoding = 'UTF-8'
533 encoding.encoding = 'UTF-8'
506
534
507 # support --authors as an alias for --authormap
535 # support --authors as an alias for --authormap
508 if not opts.get('authormap'):
536 if not opts.get('authormap'):
509 opts['authormap'] = opts.get('authors')
537 opts['authormap'] = opts.get('authors')
510
538
511 if not dest:
539 if not dest:
512 dest = hg.defaultdest(src) + "-hg"
540 dest = hg.defaultdest(src) + "-hg"
513 ui.status(_("assuming destination %s\n") % dest)
541 ui.status(_("assuming destination %s\n") % dest)
514
542
515 destc = convertsink(ui, dest, opts.get('dest_type'))
543 destc = convertsink(ui, dest, opts.get('dest_type'))
516
544
517 try:
545 try:
518 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
546 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
519 opts.get('rev'))
547 opts.get('rev'))
520 except Exception:
548 except Exception:
521 for path in destc.created:
549 for path in destc.created:
522 shutil.rmtree(path, True)
550 shutil.rmtree(path, True)
523 raise
551 raise
524
552
525 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
553 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
526 sortmode = [m for m in sortmodes if opts.get(m)]
554 sortmode = [m for m in sortmodes if opts.get(m)]
527 if len(sortmode) > 1:
555 if len(sortmode) > 1:
528 raise util.Abort(_('more than one sort mode specified'))
556 raise util.Abort(_('more than one sort mode specified'))
529 if sortmode:
557 if sortmode:
530 sortmode = sortmode[0]
558 sortmode = sortmode[0]
531 else:
559 else:
532 sortmode = defaultsort
560 sortmode = defaultsort
533
561
534 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
562 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
535 raise util.Abort(_('--sourcesort is not supported by this data source'))
563 raise util.Abort(_('--sourcesort is not supported by this data source'))
536 if sortmode == 'closesort' and not srcc.hasnativeclose():
564 if sortmode == 'closesort' and not srcc.hasnativeclose():
537 raise util.Abort(_('--closesort is not supported by this data source'))
565 raise util.Abort(_('--closesort is not supported by this data source'))
538
566
539 fmap = opts.get('filemap')
567 fmap = opts.get('filemap')
540 if fmap:
568 if fmap:
541 srcc = filemap.filemap_source(ui, srcc, fmap)
569 srcc = filemap.filemap_source(ui, srcc, fmap)
542 destc.setfilemapmode(True)
570 destc.setfilemapmode(True)
543
571
544 if not revmapfile:
572 if not revmapfile:
545 revmapfile = destc.revmapfile()
573 revmapfile = destc.revmapfile()
546
574
547 c = converter(ui, srcc, destc, revmapfile, opts)
575 c = converter(ui, srcc, destc, revmapfile, opts)
548 c.convert(sortmode)
576 c.convert(sortmode)
@@ -1,284 +1,284 b''
1 # Perforce source for convert extension.
1 # Perforce source for convert extension.
2 #
2 #
3 # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk>
3 # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from mercurial import util
8 from mercurial import util
9 from mercurial.i18n import _
9 from mercurial.i18n import _
10
10
11 from common import commit, converter_source, checktool, NoRepo
11 from common import commit, converter_source, checktool, NoRepo
12 import marshal
12 import marshal
13 import re
13 import re
14
14
15 def loaditer(f):
15 def loaditer(f):
16 "Yield the dictionary objects generated by p4"
16 "Yield the dictionary objects generated by p4"
17 try:
17 try:
18 while True:
18 while True:
19 d = marshal.load(f)
19 d = marshal.load(f)
20 if not d:
20 if not d:
21 break
21 break
22 yield d
22 yield d
23 except EOFError:
23 except EOFError:
24 pass
24 pass
25
25
26 def decodefilename(filename):
26 def decodefilename(filename):
27 """Perforce escapes special characters @, #, *, or %
27 """Perforce escapes special characters @, #, *, or %
28 with %40, %23, %2A, or %25 respectively
28 with %40, %23, %2A, or %25 respectively
29
29
30 >>> decodefilename('portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
30 >>> decodefilename('portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
31 'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
31 'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
32 >>> decodefilename('//Depot/Directory/%2525/%2523/%23%40.%2A')
32 >>> decodefilename('//Depot/Directory/%2525/%2523/%23%40.%2A')
33 '//Depot/Directory/%25/%23/#@.*'
33 '//Depot/Directory/%25/%23/#@.*'
34 """
34 """
35 replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
35 replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
36 for k, v in replacements:
36 for k, v in replacements:
37 filename = filename.replace(k, v)
37 filename = filename.replace(k, v)
38 return filename
38 return filename
39
39
40 class p4_source(converter_source):
40 class p4_source(converter_source):
41 def __init__(self, ui, path, revs=None):
41 def __init__(self, ui, path, revs=None):
42 super(p4_source, self).__init__(ui, path, revs=revs)
42 super(p4_source, self).__init__(ui, path, revs=revs)
43
43
44 if "/" in path and not path.startswith('//'):
44 if "/" in path and not path.startswith('//'):
45 raise NoRepo(_('%s does not look like a P4 repository') % path)
45 raise NoRepo(_('%s does not look like a P4 repository') % path)
46
46
47 checktool('p4', abort=False)
47 checktool('p4', abort=False)
48
48
49 self.p4changes = {}
49 self.p4changes = {}
50 self.heads = {}
50 self.heads = {}
51 self.changeset = {}
51 self.changeset = {}
52 self.files = {}
52 self.files = {}
53 self.copies = {}
53 self.copies = {}
54 self.tags = {}
54 self.tags = {}
55 self.lastbranch = {}
55 self.lastbranch = {}
56 self.parent = {}
56 self.parent = {}
57 self.encoding = "latin_1"
57 self.encoding = "latin_1"
58 self.depotname = {} # mapping from local name to depot name
58 self.depotname = {} # mapping from local name to depot name
59 self.localname = {} # mapping from depot name to local name
59 self.localname = {} # mapping from depot name to local name
60 self.re_type = re.compile(
60 self.re_type = re.compile(
61 "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
61 "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
62 "(\+\w+)?$")
62 "(\+\w+)?$")
63 self.re_keywords = re.compile(
63 self.re_keywords = re.compile(
64 r"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)"
64 r"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)"
65 r":[^$\n]*\$")
65 r":[^$\n]*\$")
66 self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$")
66 self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$")
67
67
68 if revs and len(revs) > 1:
68 if revs and len(revs) > 1:
69 raise util.Abort(_("p4 source does not support specifying "
69 raise util.Abort(_("p4 source does not support specifying "
70 "multiple revisions"))
70 "multiple revisions"))
71 self._parse(ui, path)
71 self._parse(ui, path)
72
72
73 def _parse_view(self, path):
73 def _parse_view(self, path):
74 "Read changes affecting the path"
74 "Read changes affecting the path"
75 cmd = 'p4 -G changes -s submitted %s' % util.shellquote(path)
75 cmd = 'p4 -G changes -s submitted %s' % util.shellquote(path)
76 stdout = util.popen(cmd, mode='rb')
76 stdout = util.popen(cmd, mode='rb')
77 for d in loaditer(stdout):
77 for d in loaditer(stdout):
78 c = d.get("change", None)
78 c = d.get("change", None)
79 if c:
79 if c:
80 self.p4changes[c] = True
80 self.p4changes[c] = True
81
81
82 def _parse(self, ui, path):
82 def _parse(self, ui, path):
83 "Prepare list of P4 filenames and revisions to import"
83 "Prepare list of P4 filenames and revisions to import"
84 ui.status(_('reading p4 views\n'))
84 ui.status(_('reading p4 views\n'))
85
85
86 # read client spec or view
86 # read client spec or view
87 if "/" in path:
87 if "/" in path:
88 self._parse_view(path)
88 self._parse_view(path)
89 if path.startswith("//") and path.endswith("/..."):
89 if path.startswith("//") and path.endswith("/..."):
90 views = {path[:-3]:""}
90 views = {path[:-3]:""}
91 else:
91 else:
92 views = {"//": ""}
92 views = {"//": ""}
93 else:
93 else:
94 cmd = 'p4 -G client -o %s' % util.shellquote(path)
94 cmd = 'p4 -G client -o %s' % util.shellquote(path)
95 clientspec = marshal.load(util.popen(cmd, mode='rb'))
95 clientspec = marshal.load(util.popen(cmd, mode='rb'))
96
96
97 views = {}
97 views = {}
98 for client in clientspec:
98 for client in clientspec:
99 if client.startswith("View"):
99 if client.startswith("View"):
100 sview, cview = clientspec[client].split()
100 sview, cview = clientspec[client].split()
101 self._parse_view(sview)
101 self._parse_view(sview)
102 if sview.endswith("...") and cview.endswith("..."):
102 if sview.endswith("...") and cview.endswith("..."):
103 sview = sview[:-3]
103 sview = sview[:-3]
104 cview = cview[:-3]
104 cview = cview[:-3]
105 cview = cview[2:]
105 cview = cview[2:]
106 cview = cview[cview.find("/") + 1:]
106 cview = cview[cview.find("/") + 1:]
107 views[sview] = cview
107 views[sview] = cview
108
108
109 # list of changes that affect our source files
109 # list of changes that affect our source files
110 self.p4changes = self.p4changes.keys()
110 self.p4changes = self.p4changes.keys()
111 self.p4changes.sort(key=int)
111 self.p4changes.sort(key=int)
112
112
113 # list with depot pathnames, longest first
113 # list with depot pathnames, longest first
114 vieworder = views.keys()
114 vieworder = views.keys()
115 vieworder.sort(key=len, reverse=True)
115 vieworder.sort(key=len, reverse=True)
116
116
117 # handle revision limiting
117 # handle revision limiting
118 startrev = self.ui.config('convert', 'p4.startrev', default=0)
118 startrev = self.ui.config('convert', 'p4.startrev', default=0)
119 self.p4changes = [x for x in self.p4changes
119 self.p4changes = [x for x in self.p4changes
120 if ((not startrev or int(x) >= int(startrev)) and
120 if ((not startrev or int(x) >= int(startrev)) and
121 (not self.revs or int(x) <= int(self.revs[0])))]
121 (not self.revs or int(x) <= int(self.revs[0])))]
122
122
123 # now read the full changelists to get the list of file revisions
123 # now read the full changelists to get the list of file revisions
124 ui.status(_('collecting p4 changelists\n'))
124 ui.status(_('collecting p4 changelists\n'))
125 lastid = None
125 lastid = None
126 for change in self.p4changes:
126 for change in self.p4changes:
127 cmd = "p4 -G describe -s %s" % change
127 cmd = "p4 -G describe -s %s" % change
128 stdout = util.popen(cmd, mode='rb')
128 stdout = util.popen(cmd, mode='rb')
129 d = marshal.load(stdout)
129 d = marshal.load(stdout)
130 desc = self.recode(d.get("desc", ""))
130 desc = self.recode(d.get("desc", ""))
131 shortdesc = desc.split("\n", 1)[0]
131 shortdesc = desc.split("\n", 1)[0]
132 t = '%s %s' % (d["change"], repr(shortdesc)[1:-1])
132 t = '%s %s' % (d["change"], repr(shortdesc)[1:-1])
133 ui.status(util.ellipsis(t, 80) + '\n')
133 ui.status(util.ellipsis(t, 80) + '\n')
134
134
135 if lastid:
135 if lastid:
136 parents = [lastid]
136 parents = [lastid]
137 else:
137 else:
138 parents = []
138 parents = []
139
139
140 date = (int(d["time"]), 0) # timezone not set
140 date = (int(d["time"]), 0) # timezone not set
141 c = commit(author=self.recode(d["user"]),
141 c = commit(author=self.recode(d["user"]),
142 date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
142 date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
143 parents=parents, desc=desc, branch='',
143 parents=parents, desc=desc, branch=None,
144 extra={"p4": change})
144 extra={"p4": change})
145
145
146 files = []
146 files = []
147 copies = {}
147 copies = {}
148 copiedfiles = []
148 copiedfiles = []
149 i = 0
149 i = 0
150 while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
150 while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
151 oldname = d["depotFile%d" % i]
151 oldname = d["depotFile%d" % i]
152 filename = None
152 filename = None
153 for v in vieworder:
153 for v in vieworder:
154 if oldname.lower().startswith(v.lower()):
154 if oldname.lower().startswith(v.lower()):
155 filename = decodefilename(views[v] + oldname[len(v):])
155 filename = decodefilename(views[v] + oldname[len(v):])
156 break
156 break
157 if filename:
157 if filename:
158 files.append((filename, d["rev%d" % i]))
158 files.append((filename, d["rev%d" % i]))
159 self.depotname[filename] = oldname
159 self.depotname[filename] = oldname
160 if (d.get("action%d" % i) == "move/add"):
160 if (d.get("action%d" % i) == "move/add"):
161 copiedfiles.append(filename)
161 copiedfiles.append(filename)
162 self.localname[oldname] = filename
162 self.localname[oldname] = filename
163 i += 1
163 i += 1
164
164
165 # Collect information about copied files
165 # Collect information about copied files
166 for filename in copiedfiles:
166 for filename in copiedfiles:
167 oldname = self.depotname[filename]
167 oldname = self.depotname[filename]
168
168
169 flcmd = 'p4 -G filelog %s' \
169 flcmd = 'p4 -G filelog %s' \
170 % util.shellquote(oldname)
170 % util.shellquote(oldname)
171 flstdout = util.popen(flcmd, mode='rb')
171 flstdout = util.popen(flcmd, mode='rb')
172
172
173 copiedfilename = None
173 copiedfilename = None
174 for d in loaditer(flstdout):
174 for d in loaditer(flstdout):
175 copiedoldname = None
175 copiedoldname = None
176
176
177 i = 0
177 i = 0
178 while ("change%d" % i) in d:
178 while ("change%d" % i) in d:
179 if (d["change%d" % i] == change and
179 if (d["change%d" % i] == change and
180 d["action%d" % i] == "move/add"):
180 d["action%d" % i] == "move/add"):
181 j = 0
181 j = 0
182 while ("file%d,%d" % (i, j)) in d:
182 while ("file%d,%d" % (i, j)) in d:
183 if d["how%d,%d" % (i, j)] == "moved from":
183 if d["how%d,%d" % (i, j)] == "moved from":
184 copiedoldname = d["file%d,%d" % (i, j)]
184 copiedoldname = d["file%d,%d" % (i, j)]
185 break
185 break
186 j += 1
186 j += 1
187 i += 1
187 i += 1
188
188
189 if copiedoldname and copiedoldname in self.localname:
189 if copiedoldname and copiedoldname in self.localname:
190 copiedfilename = self.localname[copiedoldname]
190 copiedfilename = self.localname[copiedoldname]
191 break
191 break
192
192
193 if copiedfilename:
193 if copiedfilename:
194 copies[filename] = copiedfilename
194 copies[filename] = copiedfilename
195 else:
195 else:
196 ui.warn(_("cannot find source for copied file: %s@%s\n")
196 ui.warn(_("cannot find source for copied file: %s@%s\n")
197 % (filename, change))
197 % (filename, change))
198
198
199 self.changeset[change] = c
199 self.changeset[change] = c
200 self.files[change] = files
200 self.files[change] = files
201 self.copies[change] = copies
201 self.copies[change] = copies
202 lastid = change
202 lastid = change
203
203
204 if lastid:
204 if lastid:
205 self.heads = [lastid]
205 self.heads = [lastid]
206
206
207 def getheads(self):
207 def getheads(self):
208 return self.heads
208 return self.heads
209
209
210 def getfile(self, name, rev):
210 def getfile(self, name, rev):
211 cmd = 'p4 -G print %s' \
211 cmd = 'p4 -G print %s' \
212 % util.shellquote("%s#%s" % (self.depotname[name], rev))
212 % util.shellquote("%s#%s" % (self.depotname[name], rev))
213
213
214 lasterror = None
214 lasterror = None
215 while True:
215 while True:
216 stdout = util.popen(cmd, mode='rb')
216 stdout = util.popen(cmd, mode='rb')
217
217
218 mode = None
218 mode = None
219 contents = ""
219 contents = ""
220 keywords = None
220 keywords = None
221
221
222 for d in loaditer(stdout):
222 for d in loaditer(stdout):
223 code = d["code"]
223 code = d["code"]
224 data = d.get("data")
224 data = d.get("data")
225
225
226 if code == "error":
226 if code == "error":
227 # if this is the first time error happened
227 # if this is the first time error happened
228 # re-attempt getting the file
228 # re-attempt getting the file
229 if not lasterror:
229 if not lasterror:
230 lasterror = IOError(d["generic"], data)
230 lasterror = IOError(d["generic"], data)
231 # this will exit inner-most for-loop
231 # this will exit inner-most for-loop
232 break
232 break
233 else:
233 else:
234 raise lasterror
234 raise lasterror
235
235
236 elif code == "stat":
236 elif code == "stat":
237 action = d.get("action")
237 action = d.get("action")
238 if action in ["purge", "delete", "move/delete"]:
238 if action in ["purge", "delete", "move/delete"]:
239 return None, None
239 return None, None
240 p4type = self.re_type.match(d["type"])
240 p4type = self.re_type.match(d["type"])
241 if p4type:
241 if p4type:
242 mode = ""
242 mode = ""
243 flags = ((p4type.group(1) or "")
243 flags = ((p4type.group(1) or "")
244 + (p4type.group(3) or ""))
244 + (p4type.group(3) or ""))
245 if "x" in flags:
245 if "x" in flags:
246 mode = "x"
246 mode = "x"
247 if p4type.group(2) == "symlink":
247 if p4type.group(2) == "symlink":
248 mode = "l"
248 mode = "l"
249 if "ko" in flags:
249 if "ko" in flags:
250 keywords = self.re_keywords_old
250 keywords = self.re_keywords_old
251 elif "k" in flags:
251 elif "k" in flags:
252 keywords = self.re_keywords
252 keywords = self.re_keywords
253
253
254 elif code == "text" or code == "binary":
254 elif code == "text" or code == "binary":
255 contents += data
255 contents += data
256
256
257 lasterror = None
257 lasterror = None
258
258
259 if not lasterror:
259 if not lasterror:
260 break
260 break
261
261
262 if mode is None:
262 if mode is None:
263 return None, None
263 return None, None
264
264
265 if keywords:
265 if keywords:
266 contents = keywords.sub("$\\1$", contents)
266 contents = keywords.sub("$\\1$", contents)
267 if mode == "l" and contents.endswith("\n"):
267 if mode == "l" and contents.endswith("\n"):
268 contents = contents[:-1]
268 contents = contents[:-1]
269
269
270 return contents, mode
270 return contents, mode
271
271
272 def getchanges(self, rev, full):
272 def getchanges(self, rev, full):
273 if full:
273 if full:
274 raise util.Abort(_("convert from p4 do not support --full"))
274 raise util.Abort(_("convert from p4 do not support --full"))
275 return self.files[rev], self.copies[rev], set()
275 return self.files[rev], self.copies[rev], set()
276
276
277 def getcommit(self, rev):
277 def getcommit(self, rev):
278 return self.changeset[rev]
278 return self.changeset[rev]
279
279
280 def gettags(self):
280 def gettags(self):
281 return self.tags
281 return self.tags
282
282
283 def getchangedfiles(self, rev, i):
283 def getchangedfiles(self, rev, i):
284 return sorted([x[0] for x in self.files[rev]])
284 return sorted([x[0] for x in self.files[rev]])
@@ -1,129 +1,128 b''
1 #require svn svn-bindings
1 #require svn svn-bindings
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [extensions]
4 > [extensions]
5 > convert =
5 > convert =
6 > EOF
6 > EOF
7
7
8 $ svnadmin create svn-repo
8 $ svnadmin create svn-repo
9 $ svnadmin load -q svn-repo < "$TESTDIR/svn/branches.svndump"
9 $ svnadmin load -q svn-repo < "$TESTDIR/svn/branches.svndump"
10
10
11 Convert trunk and branches
11 Convert trunk and branches
12
12
13 $ cat > branchmap <<EOF
13 $ cat > branchmap <<EOF
14 > old3 newbranch
14 > old3 newbranch
15 >
15 >
16 >
16 >
17 > EOF
17 > EOF
18 $ hg convert --branchmap=branchmap --datesort -r 10 svn-repo A-hg
18 $ hg convert --branchmap=branchmap --datesort -r 10 svn-repo A-hg
19 initializing destination A-hg repository
19 initializing destination A-hg repository
20 scanning source...
20 scanning source...
21 sorting...
21 sorting...
22 converting...
22 converting...
23 10 init projA
23 10 init projA
24 9 hello
24 9 hello
25 8 branch trunk, remove c and dir
25 8 branch trunk, remove c and dir
26 7 change a
26 7 change a
27 6 change b
27 6 change b
28 5 move and update c
28 5 move and update c
29 4 move and update c
29 4 move and update c
30 3 change b again
30 3 change b again
31 2 move to old2
31 2 move to old2
32 1 move back to old
32 1 move back to old
33 0 last change to a
33 0 last change to a
34
34
35 Test template keywords
35 Test template keywords
36
36
37 $ hg -R A-hg log --template '{rev} {svnuuid}{svnpath}@{svnrev}\n'
37 $ hg -R A-hg log --template '{rev} {svnuuid}{svnpath}@{svnrev}\n'
38 10 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@10
38 10 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@10
39 9 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@9
39 9 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@9
40 8 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old2@8
40 8 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old2@8
41 7 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@7
41 7 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@7
42 6 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@6
42 6 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@6
43 5 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@6
43 5 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@6
44 4 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@5
44 4 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@5
45 3 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@4
45 3 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@4
46 2 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@3
46 2 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@3
47 1 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@2
47 1 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@2
48 0 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@1
48 0 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@1
49
49
50 Convert again
50 Convert again
51
51
52 $ hg convert --branchmap=branchmap --datesort svn-repo A-hg
52 $ hg convert --branchmap=branchmap --datesort svn-repo A-hg
53 scanning source...
53 scanning source...
54 sorting...
54 sorting...
55 converting...
55 converting...
56 0 branch trunk@1 into old3
56 0 branch trunk@1 into old3
57
57
58 $ cd A-hg
58 $ cd A-hg
59 $ hg log -G --template 'branch={branches} {rev} {desc|firstline} files: {files}\n'
59 $ hg log -G --template 'branch={branches} {rev} {desc|firstline} files: {files}\n'
60 o branch=newbranch 11 branch trunk@1 into old3 files:
60 o branch=newbranch 11 branch trunk@1 into old3 files:
61 |
61 |
62 | o branch= 10 last change to a files: a
62 | o branch= 10 last change to a files: a
63 | |
63 | |
64 | | o branch=old 9 move back to old files:
64 | | o branch=old 9 move back to old files:
65 | | |
65 | | |
66 | | o branch=old2 8 move to old2 files:
66 | | o branch=old2 8 move to old2 files:
67 | | |
67 | | |
68 | | o branch=old 7 change b again files: b
68 | | o branch=old 7 change b again files: b
69 | | |
69 | | |
70 | o | branch= 6 move and update c files: b
70 | o | branch= 6 move and update c files: b
71 | | |
71 | | |
72 | | o branch=old 5 move and update c files: c
72 | | o branch=old 5 move and update c files: c
73 | | |
73 | | |
74 | | o branch=old 4 change b files: b
74 | | o branch=old 4 change b files: b
75 | | |
75 | | |
76 | o | branch= 3 change a files: a
76 | o | branch= 3 change a files: a
77 | | |
77 | | |
78 | | o branch=old 2 branch trunk, remove c and dir files: c
78 | | o branch=old 2 branch trunk, remove c and dir files: c
79 | |/
79 | |/
80 | o branch= 1 hello files: a b c dir/e
80 | o branch= 1 hello files: a b c dir/e
81 |/
81 |/
82 o branch= 0 init projA files:
82 o branch= 0 init projA files:
83
83
84
84
85 $ hg branches
85 $ hg branches
86 newbranch 11:a6d7cc050ad1
86 newbranch 11:a6d7cc050ad1
87 default 10:6e2b33404495
87 default 10:6e2b33404495
88 old 9:93c4b0f99529
88 old 9:93c4b0f99529
89 old2 8:b52884d7bead (inactive)
89 old2 8:b52884d7bead (inactive)
90 $ hg tags -q
90 $ hg tags -q
91 tip
91 tip
92 $ cd ..
92 $ cd ..
93
93
94 Test hg failing to call itself
94 Test hg failing to call itself
95
95
96 $ HG=foobar hg convert svn-repo B-hg 2>&1 | grep abort
96 $ HG=foobar hg convert svn-repo B-hg 2>&1 | grep abort
97 abort: Mercurial failed to run itself, check hg executable is in PATH
97 abort: Mercurial failed to run itself, check hg executable is in PATH
98
98
99 Convert 'trunk' to branch other than 'default'
99 Convert 'trunk' to branch other than 'default'
100
100
101 $ cat > branchmap <<EOF
101 $ cat > branchmap <<EOF
102 > None hgtrunk
102 > default hgtrunk
103 >
103 >
104 >
104 >
105 > EOF
105 > EOF
106 $ hg convert --branchmap=branchmap --datesort -r 10 svn-repo C-hg
106 $ hg convert --branchmap=branchmap --datesort -r 10 svn-repo C-hg
107 initializing destination C-hg repository
107 initializing destination C-hg repository
108 scanning source...
108 scanning source...
109 sorting...
109 sorting...
110 converting...
110 converting...
111 10 init projA
111 10 init projA
112 9 hello
112 9 hello
113 8 branch trunk, remove c and dir
113 8 branch trunk, remove c and dir
114 7 change a
114 7 change a
115 6 change b
115 6 change b
116 5 move and update c
116 5 move and update c
117 4 move and update c
117 4 move and update c
118 3 change b again
118 3 change b again
119 2 move to old2
119 2 move to old2
120 1 move back to old
120 1 move back to old
121 0 last change to a
121 0 last change to a
122
122
123 $ cd C-hg
123 $ cd C-hg
124 $ hg branches
124 $ hg branches --template '{branch}\n'
125 hgtrunk 10:745f063703b4
125 hgtrunk
126 old 9:aa50d7b8d922
126 old
127 old2 8:c85a22267b6e (inactive)
127 old2
128 $ cd ..
128 $ cd ..
129
@@ -1,38 +1,39 b''
1 # this is hack to make sure no escape characters are inserted into the output
1 # this is hack to make sure no escape characters are inserted into the output
2 import os, sys
2 import os, sys
3 if 'TERM' in os.environ:
3 if 'TERM' in os.environ:
4 del os.environ['TERM']
4 del os.environ['TERM']
5 import doctest
5 import doctest
6
6
7 def testmod(name, optionflags=0, testtarget=None):
7 def testmod(name, optionflags=0, testtarget=None):
8 __import__(name)
8 __import__(name)
9 mod = sys.modules[name]
9 mod = sys.modules[name]
10 if testtarget is not None:
10 if testtarget is not None:
11 mod = getattr(mod, testtarget)
11 mod = getattr(mod, testtarget)
12 doctest.testmod(mod, optionflags=optionflags)
12 doctest.testmod(mod, optionflags=optionflags)
13
13
14 testmod('mercurial.changelog')
14 testmod('mercurial.changelog')
15 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
15 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
16 testmod('mercurial.dispatch')
16 testmod('mercurial.dispatch')
17 testmod('mercurial.encoding')
17 testmod('mercurial.encoding')
18 testmod('mercurial.hg')
18 testmod('mercurial.hg')
19 testmod('mercurial.hgweb.hgwebdir_mod')
19 testmod('mercurial.hgweb.hgwebdir_mod')
20 testmod('mercurial.match')
20 testmod('mercurial.match')
21 testmod('mercurial.minirst')
21 testmod('mercurial.minirst')
22 testmod('mercurial.patch')
22 testmod('mercurial.patch')
23 testmod('mercurial.pathutil')
23 testmod('mercurial.pathutil')
24 testmod('mercurial.parser')
24 testmod('mercurial.parser')
25 testmod('mercurial.revset')
25 testmod('mercurial.revset')
26 testmod('mercurial.store')
26 testmod('mercurial.store')
27 testmod('mercurial.subrepo')
27 testmod('mercurial.subrepo')
28 testmod('mercurial.templatefilters')
28 testmod('mercurial.templatefilters')
29 testmod('mercurial.templater')
29 testmod('mercurial.templater')
30 testmod('mercurial.ui')
30 testmod('mercurial.ui')
31 testmod('mercurial.url')
31 testmod('mercurial.url')
32 testmod('mercurial.util')
32 testmod('mercurial.util')
33 testmod('mercurial.util', testtarget='platform')
33 testmod('mercurial.util', testtarget='platform')
34 testmod('hgext.convert.convcmd')
34 testmod('hgext.convert.cvsps')
35 testmod('hgext.convert.cvsps')
35 testmod('hgext.convert.filemap')
36 testmod('hgext.convert.filemap')
36 testmod('hgext.convert.p4')
37 testmod('hgext.convert.p4')
37 testmod('hgext.convert.subversion')
38 testmod('hgext.convert.subversion')
38 testmod('hgext.mq')
39 testmod('hgext.mq')
General Comments 0
You need to be logged in to leave comments. Login now