##// END OF EJS Templates
convert: fix history topology when using hg.tagsbranch...
Patrick Mezard -
r9431:d1b135f2 default
parent child Browse files
Show More
@@ -0,0 +1,64 b''
1 #!/bin/sh
2
3 "$TESTDIR/hghave" git || exit 80
4
5 echo "[extensions]" >> $HGRCPATH
6 echo "convert=" >> $HGRCPATH
7 echo 'hgext.graphlog =' >> $HGRCPATH
8 echo '[convert]' >> $HGRCPATH
9 echo 'hg.usebranchnames = True' >> $HGRCPATH
10 echo 'hg.tagsbranch = tags-update' >> $HGRCPATH
11
12 GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
13 GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
14 GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
15 GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
16 GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
17 GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
18
19 count=10
20 action()
21 {
22 GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
23 GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
24 git "$@" >/dev/null 2>/dev/null || echo "git command error"
25 count=`expr $count + 1`
26 }
27
28 glog()
29 {
30 hg glog --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
31 }
32
33 convertrepo()
34 {
35 hg convert --datesort git-repo hg-repo
36 }
37
38 # Build a GIT repo with at least 1 tag
39 mkdir git-repo
40 cd git-repo
41 git init >/dev/null 2>&1
42 echo a > a
43 git add a
44 action commit -m "rev1"
45 action tag -m "tag1" tag1
46 cd ..
47
48 # Do a first conversion
49 convertrepo
50
51 # Simulate upstream updates after first conversion
52 cd git-repo
53 echo b > a
54 git add a
55 action commit -m "rev2"
56 action tag -m "tag2" tag2
57 cd ..
58
59 # Perform an incremental conversion
60 convertrepo
61
62 # Print the log
63 cd hg-repo
64 glog
@@ -0,0 +1,19 b''
1 initializing destination hg-repo repository
2 scanning source...
3 sorting...
4 converting...
5 0 rev1
6 updating tags
7 scanning source...
8 sorting...
9 converting...
10 0 rev2
11 updating tags
12 o 3 "update tags" files: .hgtags
13 |
14 | o 2 "rev2" files: a
15 | |
16 o | 1 "update tags" files: .hgtags
17 /
18 o 0 "rev1" files: a
19
@@ -1,389 +1,391 b''
1 # common.py - common code for the convert extension
1 # common.py - common code for the convert extension
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 import base64, errno
8 import base64, errno
9 import os
9 import os
10 import cPickle as pickle
10 import cPickle as pickle
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 def encodeargs(args):
14 def encodeargs(args):
15 def encodearg(s):
15 def encodearg(s):
16 lines = base64.encodestring(s)
16 lines = base64.encodestring(s)
17 lines = [l.splitlines()[0] for l in lines]
17 lines = [l.splitlines()[0] for l in lines]
18 return ''.join(lines)
18 return ''.join(lines)
19
19
20 s = pickle.dumps(args)
20 s = pickle.dumps(args)
21 return encodearg(s)
21 return encodearg(s)
22
22
23 def decodeargs(s):
23 def decodeargs(s):
24 s = base64.decodestring(s)
24 s = base64.decodestring(s)
25 return pickle.loads(s)
25 return pickle.loads(s)
26
26
27 class MissingTool(Exception): pass
27 class MissingTool(Exception): pass
28
28
29 def checktool(exe, name=None, abort=True):
29 def checktool(exe, name=None, abort=True):
30 name = name or exe
30 name = name or exe
31 if not util.find_exe(exe):
31 if not util.find_exe(exe):
32 exc = abort and util.Abort or MissingTool
32 exc = abort and util.Abort or MissingTool
33 raise exc(_('cannot find required "%s" tool') % name)
33 raise exc(_('cannot find required "%s" tool') % name)
34
34
35 class NoRepo(Exception): pass
35 class NoRepo(Exception): pass
36
36
37 SKIPREV = 'SKIP'
37 SKIPREV = 'SKIP'
38
38
39 class commit(object):
39 class commit(object):
40 def __init__(self, author, date, desc, parents, branch=None, rev=None,
40 def __init__(self, author, date, desc, parents, branch=None, rev=None,
41 extra={}, sortkey=None):
41 extra={}, sortkey=None):
42 self.author = author or 'unknown'
42 self.author = author or 'unknown'
43 self.date = date or '0 0'
43 self.date = date or '0 0'
44 self.desc = desc
44 self.desc = desc
45 self.parents = parents
45 self.parents = parents
46 self.branch = branch
46 self.branch = branch
47 self.rev = rev
47 self.rev = rev
48 self.extra = extra
48 self.extra = extra
49 self.sortkey = sortkey
49 self.sortkey = sortkey
50
50
51 class converter_source(object):
51 class converter_source(object):
52 """Conversion source interface"""
52 """Conversion source interface"""
53
53
54 def __init__(self, ui, path=None, rev=None):
54 def __init__(self, ui, path=None, rev=None):
55 """Initialize conversion source (or raise NoRepo("message")
55 """Initialize conversion source (or raise NoRepo("message")
56 exception if path is not a valid repository)"""
56 exception if path is not a valid repository)"""
57 self.ui = ui
57 self.ui = ui
58 self.path = path
58 self.path = path
59 self.rev = rev
59 self.rev = rev
60
60
61 self.encoding = 'utf-8'
61 self.encoding = 'utf-8'
62
62
63 def before(self):
63 def before(self):
64 pass
64 pass
65
65
66 def after(self):
66 def after(self):
67 pass
67 pass
68
68
69 def setrevmap(self, revmap):
69 def setrevmap(self, revmap):
70 """set the map of already-converted revisions"""
70 """set the map of already-converted revisions"""
71 pass
71 pass
72
72
73 def getheads(self):
73 def getheads(self):
74 """Return a list of this repository's heads"""
74 """Return a list of this repository's heads"""
75 raise NotImplementedError()
75 raise NotImplementedError()
76
76
77 def getfile(self, name, rev):
77 def getfile(self, name, rev):
78 """Return file contents as a string. rev is the identifier returned
78 """Return file contents as a string. rev is the identifier returned
79 by a previous call to getchanges(). Raise IOError to indicate that
79 by a previous call to getchanges(). Raise IOError to indicate that
80 name was deleted in rev.
80 name was deleted in rev.
81 """
81 """
82 raise NotImplementedError()
82 raise NotImplementedError()
83
83
84 def getmode(self, name, rev):
84 def getmode(self, name, rev):
85 """Return file mode, eg. '', 'x', or 'l'. rev is the identifier
85 """Return file mode, eg. '', 'x', or 'l'. rev is the identifier
86 returned by a previous call to getchanges().
86 returned by a previous call to getchanges().
87 """
87 """
88 raise NotImplementedError()
88 raise NotImplementedError()
89
89
90 def getchanges(self, version):
90 def getchanges(self, version):
91 """Returns a tuple of (files, copies).
91 """Returns a tuple of (files, copies).
92
92
93 files is a sorted list of (filename, id) tuples for all files
93 files is a sorted list of (filename, id) tuples for all files
94 changed between version and its first parent returned by
94 changed between version and its first parent returned by
95 getcommit(). id is the source revision id of the file.
95 getcommit(). id is the source revision id of the file.
96
96
97 copies is a dictionary of dest: source
97 copies is a dictionary of dest: source
98 """
98 """
99 raise NotImplementedError()
99 raise NotImplementedError()
100
100
101 def getcommit(self, version):
101 def getcommit(self, version):
102 """Return the commit object for version"""
102 """Return the commit object for version"""
103 raise NotImplementedError()
103 raise NotImplementedError()
104
104
105 def gettags(self):
105 def gettags(self):
106 """Return the tags as a dictionary of name: revision
106 """Return the tags as a dictionary of name: revision
107
107
108 Tag names must be UTF-8 strings.
108 Tag names must be UTF-8 strings.
109 """
109 """
110 raise NotImplementedError()
110 raise NotImplementedError()
111
111
112 def recode(self, s, encoding=None):
112 def recode(self, s, encoding=None):
113 if not encoding:
113 if not encoding:
114 encoding = self.encoding or 'utf-8'
114 encoding = self.encoding or 'utf-8'
115
115
116 if isinstance(s, unicode):
116 if isinstance(s, unicode):
117 return s.encode("utf-8")
117 return s.encode("utf-8")
118 try:
118 try:
119 return s.decode(encoding).encode("utf-8")
119 return s.decode(encoding).encode("utf-8")
120 except:
120 except:
121 try:
121 try:
122 return s.decode("latin-1").encode("utf-8")
122 return s.decode("latin-1").encode("utf-8")
123 except:
123 except:
124 return s.decode(encoding, "replace").encode("utf-8")
124 return s.decode(encoding, "replace").encode("utf-8")
125
125
126 def getchangedfiles(self, rev, i):
126 def getchangedfiles(self, rev, i):
127 """Return the files changed by rev compared to parent[i].
127 """Return the files changed by rev compared to parent[i].
128
128
129 i is an index selecting one of the parents of rev. The return
129 i is an index selecting one of the parents of rev. The return
130 value should be the list of files that are different in rev and
130 value should be the list of files that are different in rev and
131 this parent.
131 this parent.
132
132
133 If rev has no parents, i is None.
133 If rev has no parents, i is None.
134
134
135 This function is only needed to support --filemap
135 This function is only needed to support --filemap
136 """
136 """
137 raise NotImplementedError()
137 raise NotImplementedError()
138
138
139 def converted(self, rev, sinkrev):
139 def converted(self, rev, sinkrev):
140 '''Notify the source that a revision has been converted.'''
140 '''Notify the source that a revision has been converted.'''
141 pass
141 pass
142
142
143 def hasnativeorder(self):
143 def hasnativeorder(self):
144 """Return true if this source has a meaningful, native revision
144 """Return true if this source has a meaningful, native revision
145 order. For instance, Mercurial revisions are store sequentially
145 order. For instance, Mercurial revisions are store sequentially
146 while there is no such global ordering with Darcs.
146 while there is no such global ordering with Darcs.
147 """
147 """
148 return False
148 return False
149
149
150 def lookuprev(self, rev):
150 def lookuprev(self, rev):
151 """If rev is a meaningful revision reference in source, return
151 """If rev is a meaningful revision reference in source, return
152 the referenced identifier in the same format used by getcommit().
152 the referenced identifier in the same format used by getcommit().
153 return None otherwise.
153 return None otherwise.
154 """
154 """
155 return None
155 return None
156
156
157 class converter_sink(object):
157 class converter_sink(object):
158 """Conversion sink (target) interface"""
158 """Conversion sink (target) interface"""
159
159
160 def __init__(self, ui, path):
160 def __init__(self, ui, path):
161 """Initialize conversion sink (or raise NoRepo("message")
161 """Initialize conversion sink (or raise NoRepo("message")
162 exception if path is not a valid repository)
162 exception if path is not a valid repository)
163
163
164 created is a list of paths to remove if a fatal error occurs
164 created is a list of paths to remove if a fatal error occurs
165 later"""
165 later"""
166 self.ui = ui
166 self.ui = ui
167 self.path = path
167 self.path = path
168 self.created = []
168 self.created = []
169
169
170 def getheads(self):
170 def getheads(self):
171 """Return a list of this repository's heads"""
171 """Return a list of this repository's heads"""
172 raise NotImplementedError()
172 raise NotImplementedError()
173
173
174 def revmapfile(self):
174 def revmapfile(self):
175 """Path to a file that will contain lines
175 """Path to a file that will contain lines
176 source_rev_id sink_rev_id
176 source_rev_id sink_rev_id
177 mapping equivalent revision identifiers for each system."""
177 mapping equivalent revision identifiers for each system."""
178 raise NotImplementedError()
178 raise NotImplementedError()
179
179
180 def authorfile(self):
180 def authorfile(self):
181 """Path to a file that will contain lines
181 """Path to a file that will contain lines
182 srcauthor=dstauthor
182 srcauthor=dstauthor
183 mapping equivalent authors identifiers for each system."""
183 mapping equivalent authors identifiers for each system."""
184 return None
184 return None
185
185
186 def putcommit(self, files, copies, parents, commit, source, revmap):
186 def putcommit(self, files, copies, parents, commit, source, revmap):
187 """Create a revision with all changed files listed in 'files'
187 """Create a revision with all changed files listed in 'files'
188 and having listed parents. 'commit' is a commit object
188 and having listed parents. 'commit' is a commit object
189 containing at a minimum the author, date, and message for this
189 containing at a minimum the author, date, and message for this
190 changeset. 'files' is a list of (path, version) tuples,
190 changeset. 'files' is a list of (path, version) tuples,
191 'copies' is a dictionary mapping destinations to sources,
191 'copies' is a dictionary mapping destinations to sources,
192 'source' is the source repository, and 'revmap' is a mapfile
192 'source' is the source repository, and 'revmap' is a mapfile
193 of source revisions to converted revisions. Only getfile(),
193 of source revisions to converted revisions. Only getfile(),
194 getmode(), and lookuprev() should be called on 'source'.
194 getmode(), and lookuprev() should be called on 'source'.
195
195
196 Note that the sink repository is not told to update itself to
196 Note that the sink repository is not told to update itself to
197 a particular revision (or even what that revision would be)
197 a particular revision (or even what that revision would be)
198 before it receives the file data.
198 before it receives the file data.
199 """
199 """
200 raise NotImplementedError()
200 raise NotImplementedError()
201
201
202 def puttags(self, tags):
202 def puttags(self, tags):
203 """Put tags into sink.
203 """Put tags into sink.
204
204
205 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
205 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
206 Return a pair (tag_revision, tag_parent_revision), or (None, None)
207 if nothing was changed.
206 """
208 """
207 raise NotImplementedError()
209 raise NotImplementedError()
208
210
209 def setbranch(self, branch, pbranches):
211 def setbranch(self, branch, pbranches):
210 """Set the current branch name. Called before the first putcommit
212 """Set the current branch name. Called before the first putcommit
211 on the branch.
213 on the branch.
212 branch: branch name for subsequent commits
214 branch: branch name for subsequent commits
213 pbranches: (converted parent revision, parent branch) tuples"""
215 pbranches: (converted parent revision, parent branch) tuples"""
214 pass
216 pass
215
217
216 def setfilemapmode(self, active):
218 def setfilemapmode(self, active):
217 """Tell the destination that we're using a filemap
219 """Tell the destination that we're using a filemap
218
220
219 Some converter_sources (svn in particular) can claim that a file
221 Some converter_sources (svn in particular) can claim that a file
220 was changed in a revision, even if there was no change. This method
222 was changed in a revision, even if there was no change. This method
221 tells the destination that we're using a filemap and that it should
223 tells the destination that we're using a filemap and that it should
222 filter empty revisions.
224 filter empty revisions.
223 """
225 """
224 pass
226 pass
225
227
226 def before(self):
228 def before(self):
227 pass
229 pass
228
230
229 def after(self):
231 def after(self):
230 pass
232 pass
231
233
232
234
233 class commandline(object):
235 class commandline(object):
234 def __init__(self, ui, command):
236 def __init__(self, ui, command):
235 self.ui = ui
237 self.ui = ui
236 self.command = command
238 self.command = command
237
239
238 def prerun(self):
240 def prerun(self):
239 pass
241 pass
240
242
241 def postrun(self):
243 def postrun(self):
242 pass
244 pass
243
245
244 def _cmdline(self, cmd, *args, **kwargs):
246 def _cmdline(self, cmd, *args, **kwargs):
245 cmdline = [self.command, cmd] + list(args)
247 cmdline = [self.command, cmd] + list(args)
246 for k, v in kwargs.iteritems():
248 for k, v in kwargs.iteritems():
247 if len(k) == 1:
249 if len(k) == 1:
248 cmdline.append('-' + k)
250 cmdline.append('-' + k)
249 else:
251 else:
250 cmdline.append('--' + k.replace('_', '-'))
252 cmdline.append('--' + k.replace('_', '-'))
251 try:
253 try:
252 if len(k) == 1:
254 if len(k) == 1:
253 cmdline.append('' + v)
255 cmdline.append('' + v)
254 else:
256 else:
255 cmdline[-1] += '=' + v
257 cmdline[-1] += '=' + v
256 except TypeError:
258 except TypeError:
257 pass
259 pass
258 cmdline = [util.shellquote(arg) for arg in cmdline]
260 cmdline = [util.shellquote(arg) for arg in cmdline]
259 if not self.ui.debugflag:
261 if not self.ui.debugflag:
260 cmdline += ['2>', util.nulldev]
262 cmdline += ['2>', util.nulldev]
261 cmdline += ['<', util.nulldev]
263 cmdline += ['<', util.nulldev]
262 cmdline = ' '.join(cmdline)
264 cmdline = ' '.join(cmdline)
263 return cmdline
265 return cmdline
264
266
265 def _run(self, cmd, *args, **kwargs):
267 def _run(self, cmd, *args, **kwargs):
266 cmdline = self._cmdline(cmd, *args, **kwargs)
268 cmdline = self._cmdline(cmd, *args, **kwargs)
267 self.ui.debug(_('running: %s\n') % (cmdline,))
269 self.ui.debug(_('running: %s\n') % (cmdline,))
268 self.prerun()
270 self.prerun()
269 try:
271 try:
270 return util.popen(cmdline)
272 return util.popen(cmdline)
271 finally:
273 finally:
272 self.postrun()
274 self.postrun()
273
275
274 def run(self, cmd, *args, **kwargs):
276 def run(self, cmd, *args, **kwargs):
275 fp = self._run(cmd, *args, **kwargs)
277 fp = self._run(cmd, *args, **kwargs)
276 output = fp.read()
278 output = fp.read()
277 self.ui.debug(output)
279 self.ui.debug(output)
278 return output, fp.close()
280 return output, fp.close()
279
281
280 def runlines(self, cmd, *args, **kwargs):
282 def runlines(self, cmd, *args, **kwargs):
281 fp = self._run(cmd, *args, **kwargs)
283 fp = self._run(cmd, *args, **kwargs)
282 output = fp.readlines()
284 output = fp.readlines()
283 self.ui.debug(''.join(output))
285 self.ui.debug(''.join(output))
284 return output, fp.close()
286 return output, fp.close()
285
287
286 def checkexit(self, status, output=''):
288 def checkexit(self, status, output=''):
287 if status:
289 if status:
288 if output:
290 if output:
289 self.ui.warn(_('%s error:\n') % self.command)
291 self.ui.warn(_('%s error:\n') % self.command)
290 self.ui.warn(output)
292 self.ui.warn(output)
291 msg = util.explain_exit(status)[0]
293 msg = util.explain_exit(status)[0]
292 raise util.Abort('%s %s' % (self.command, msg))
294 raise util.Abort('%s %s' % (self.command, msg))
293
295
294 def run0(self, cmd, *args, **kwargs):
296 def run0(self, cmd, *args, **kwargs):
295 output, status = self.run(cmd, *args, **kwargs)
297 output, status = self.run(cmd, *args, **kwargs)
296 self.checkexit(status, output)
298 self.checkexit(status, output)
297 return output
299 return output
298
300
299 def runlines0(self, cmd, *args, **kwargs):
301 def runlines0(self, cmd, *args, **kwargs):
300 output, status = self.runlines(cmd, *args, **kwargs)
302 output, status = self.runlines(cmd, *args, **kwargs)
301 self.checkexit(status, ''.join(output))
303 self.checkexit(status, ''.join(output))
302 return output
304 return output
303
305
304 def getargmax(self):
306 def getargmax(self):
305 if '_argmax' in self.__dict__:
307 if '_argmax' in self.__dict__:
306 return self._argmax
308 return self._argmax
307
309
308 # POSIX requires at least 4096 bytes for ARG_MAX
310 # POSIX requires at least 4096 bytes for ARG_MAX
309 self._argmax = 4096
311 self._argmax = 4096
310 try:
312 try:
311 self._argmax = os.sysconf("SC_ARG_MAX")
313 self._argmax = os.sysconf("SC_ARG_MAX")
312 except:
314 except:
313 pass
315 pass
314
316
315 # Windows shells impose their own limits on command line length,
317 # Windows shells impose their own limits on command line length,
316 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
318 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
317 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
319 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
318 # details about cmd.exe limitations.
320 # details about cmd.exe limitations.
319
321
320 # Since ARG_MAX is for command line _and_ environment, lower our limit
322 # Since ARG_MAX is for command line _and_ environment, lower our limit
321 # (and make happy Windows shells while doing this).
323 # (and make happy Windows shells while doing this).
322
324
323 self._argmax = self._argmax/2 - 1
325 self._argmax = self._argmax/2 - 1
324 return self._argmax
326 return self._argmax
325
327
326 def limit_arglist(self, arglist, cmd, *args, **kwargs):
328 def limit_arglist(self, arglist, cmd, *args, **kwargs):
327 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
329 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
328 bytes = 0
330 bytes = 0
329 fl = []
331 fl = []
330 for fn in arglist:
332 for fn in arglist:
331 b = len(fn) + 3
333 b = len(fn) + 3
332 if bytes + b < limit or len(fl) == 0:
334 if bytes + b < limit or len(fl) == 0:
333 fl.append(fn)
335 fl.append(fn)
334 bytes += b
336 bytes += b
335 else:
337 else:
336 yield fl
338 yield fl
337 fl = [fn]
339 fl = [fn]
338 bytes = b
340 bytes = b
339 if fl:
341 if fl:
340 yield fl
342 yield fl
341
343
342 def xargs(self, arglist, cmd, *args, **kwargs):
344 def xargs(self, arglist, cmd, *args, **kwargs):
343 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
345 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
344 self.run0(cmd, *(list(args) + l), **kwargs)
346 self.run0(cmd, *(list(args) + l), **kwargs)
345
347
346 class mapfile(dict):
348 class mapfile(dict):
347 def __init__(self, ui, path):
349 def __init__(self, ui, path):
348 super(mapfile, self).__init__()
350 super(mapfile, self).__init__()
349 self.ui = ui
351 self.ui = ui
350 self.path = path
352 self.path = path
351 self.fp = None
353 self.fp = None
352 self.order = []
354 self.order = []
353 self._read()
355 self._read()
354
356
355 def _read(self):
357 def _read(self):
356 if not self.path:
358 if not self.path:
357 return
359 return
358 try:
360 try:
359 fp = open(self.path, 'r')
361 fp = open(self.path, 'r')
360 except IOError, err:
362 except IOError, err:
361 if err.errno != errno.ENOENT:
363 if err.errno != errno.ENOENT:
362 raise
364 raise
363 return
365 return
364 for i, line in enumerate(fp):
366 for i, line in enumerate(fp):
365 try:
367 try:
366 key, value = line[:-1].rsplit(' ', 1)
368 key, value = line[:-1].rsplit(' ', 1)
367 except ValueError:
369 except ValueError:
368 raise util.Abort(_('syntax error in %s(%d): key/value pair expected')
370 raise util.Abort(_('syntax error in %s(%d): key/value pair expected')
369 % (self.path, i+1))
371 % (self.path, i+1))
370 if key not in self:
372 if key not in self:
371 self.order.append(key)
373 self.order.append(key)
372 super(mapfile, self).__setitem__(key, value)
374 super(mapfile, self).__setitem__(key, value)
373 fp.close()
375 fp.close()
374
376
375 def __setitem__(self, key, value):
377 def __setitem__(self, key, value):
376 if self.fp is None:
378 if self.fp is None:
377 try:
379 try:
378 self.fp = open(self.path, 'a')
380 self.fp = open(self.path, 'a')
379 except IOError, err:
381 except IOError, err:
380 raise util.Abort(_('could not open map file %r: %s') %
382 raise util.Abort(_('could not open map file %r: %s') %
381 (self.path, err.strerror))
383 (self.path, err.strerror))
382 self.fp.write('%s %s\n' % (key, value))
384 self.fp.write('%s %s\n' % (key, value))
383 self.fp.flush()
385 self.fp.flush()
384 super(mapfile, self).__setitem__(key, value)
386 super(mapfile, self).__setitem__(key, value)
385
387
386 def close(self):
388 def close(self):
387 if self.fp:
389 if self.fp:
388 self.fp.close()
390 self.fp.close()
389 self.fp = None
391 self.fp = None
@@ -1,396 +1,399 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
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
20 import os, shutil
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 source_converters = [
32 source_converters = [
33 ('cvs', convert_cvs, 'branchsort'),
33 ('cvs', convert_cvs, 'branchsort'),
34 ('git', convert_git, 'branchsort'),
34 ('git', convert_git, 'branchsort'),
35 ('svn', svn_source, 'branchsort'),
35 ('svn', svn_source, 'branchsort'),
36 ('hg', mercurial_source, 'sourcesort'),
36 ('hg', mercurial_source, 'sourcesort'),
37 ('darcs', darcs_source, 'branchsort'),
37 ('darcs', darcs_source, 'branchsort'),
38 ('mtn', monotone_source, 'branchsort'),
38 ('mtn', monotone_source, 'branchsort'),
39 ('gnuarch', gnuarch_source, 'branchsort'),
39 ('gnuarch', gnuarch_source, 'branchsort'),
40 ('bzr', bzr_source, 'branchsort'),
40 ('bzr', bzr_source, 'branchsort'),
41 ('p4', p4_source, 'branchsort'),
41 ('p4', p4_source, 'branchsort'),
42 ]
42 ]
43
43
44 sink_converters = [
44 sink_converters = [
45 ('hg', mercurial_sink),
45 ('hg', mercurial_sink),
46 ('svn', svn_sink),
46 ('svn', svn_sink),
47 ]
47 ]
48
48
49 def convertsource(ui, path, type, rev):
49 def convertsource(ui, path, type, rev):
50 exceptions = []
50 exceptions = []
51 for name, source, sortmode in source_converters:
51 for name, source, sortmode in source_converters:
52 try:
52 try:
53 if not type or name == type:
53 if not type or name == type:
54 return source(ui, path, rev), sortmode
54 return source(ui, path, rev), sortmode
55 except (NoRepo, MissingTool), inst:
55 except (NoRepo, MissingTool), inst:
56 exceptions.append(inst)
56 exceptions.append(inst)
57 if not ui.quiet:
57 if not ui.quiet:
58 for inst in exceptions:
58 for inst in exceptions:
59 ui.write("%s\n" % inst)
59 ui.write("%s\n" % inst)
60 raise util.Abort(_('%s: missing or unsupported repository') % path)
60 raise util.Abort(_('%s: missing or unsupported repository') % path)
61
61
62 def convertsink(ui, path, type):
62 def convertsink(ui, path, type):
63 for name, sink in sink_converters:
63 for name, sink in sink_converters:
64 try:
64 try:
65 if not type or name == type:
65 if not type or name == type:
66 return sink(ui, path)
66 return sink(ui, path)
67 except NoRepo, inst:
67 except NoRepo, inst:
68 ui.note(_("convert: %s\n") % inst)
68 ui.note(_("convert: %s\n") % inst)
69 raise util.Abort(_('%s: unknown repository type') % path)
69 raise util.Abort(_('%s: unknown repository type') % path)
70
70
71 class converter(object):
71 class converter(object):
72 def __init__(self, ui, source, dest, revmapfile, opts):
72 def __init__(self, ui, source, dest, revmapfile, opts):
73
73
74 self.source = source
74 self.source = source
75 self.dest = dest
75 self.dest = dest
76 self.ui = ui
76 self.ui = ui
77 self.opts = opts
77 self.opts = opts
78 self.commitcache = {}
78 self.commitcache = {}
79 self.authors = {}
79 self.authors = {}
80 self.authorfile = None
80 self.authorfile = None
81
81
82 # Record converted revisions persistently: maps source revision
82 # Record converted revisions persistently: maps source revision
83 # ID to target revision ID (both strings). (This is how
83 # ID to target revision ID (both strings). (This is how
84 # incremental conversions work.)
84 # incremental conversions work.)
85 self.map = mapfile(ui, revmapfile)
85 self.map = mapfile(ui, revmapfile)
86
86
87 # Read first the dst author map if any
87 # Read first the dst author map if any
88 authorfile = self.dest.authorfile()
88 authorfile = self.dest.authorfile()
89 if authorfile and os.path.exists(authorfile):
89 if authorfile and os.path.exists(authorfile):
90 self.readauthormap(authorfile)
90 self.readauthormap(authorfile)
91 # Extend/Override with new author map if necessary
91 # Extend/Override with new author map if necessary
92 if opts.get('authors'):
92 if opts.get('authors'):
93 self.readauthormap(opts.get('authors'))
93 self.readauthormap(opts.get('authors'))
94 self.authorfile = self.dest.authorfile()
94 self.authorfile = self.dest.authorfile()
95
95
96 self.splicemap = mapfile(ui, opts.get('splicemap'))
96 self.splicemap = mapfile(ui, opts.get('splicemap'))
97 self.branchmap = mapfile(ui, opts.get('branchmap'))
97 self.branchmap = mapfile(ui, opts.get('branchmap'))
98
98
99 def walktree(self, heads):
99 def walktree(self, heads):
100 '''Return a mapping that identifies the uncommitted parents of every
100 '''Return a mapping that identifies the uncommitted parents of every
101 uncommitted changeset.'''
101 uncommitted changeset.'''
102 visit = heads
102 visit = heads
103 known = set()
103 known = set()
104 parents = {}
104 parents = {}
105 while visit:
105 while visit:
106 n = visit.pop(0)
106 n = visit.pop(0)
107 if n in known or n in self.map: continue
107 if n in known or n in self.map: continue
108 known.add(n)
108 known.add(n)
109 commit = self.cachecommit(n)
109 commit = self.cachecommit(n)
110 parents[n] = []
110 parents[n] = []
111 for p in commit.parents:
111 for p in commit.parents:
112 parents[n].append(p)
112 parents[n].append(p)
113 visit.append(p)
113 visit.append(p)
114
114
115 return parents
115 return parents
116
116
117 def toposort(self, parents, sortmode):
117 def toposort(self, parents, sortmode):
118 '''Return an ordering such that every uncommitted changeset is
118 '''Return an ordering such that every uncommitted changeset is
119 preceeded by all its uncommitted ancestors.'''
119 preceeded by all its uncommitted ancestors.'''
120
120
121 def mapchildren(parents):
121 def mapchildren(parents):
122 """Return a (children, roots) tuple where 'children' maps parent
122 """Return a (children, roots) tuple where 'children' maps parent
123 revision identifiers to children ones, and 'roots' is the list of
123 revision identifiers to children ones, and 'roots' is the list of
124 revisions without parents. 'parents' must be a mapping of revision
124 revisions without parents. 'parents' must be a mapping of revision
125 identifier to its parents ones.
125 identifier to its parents ones.
126 """
126 """
127 visit = parents.keys()
127 visit = parents.keys()
128 seen = set()
128 seen = set()
129 children = {}
129 children = {}
130 roots = []
130 roots = []
131
131
132 while visit:
132 while visit:
133 n = visit.pop(0)
133 n = visit.pop(0)
134 if n in seen:
134 if n in seen:
135 continue
135 continue
136 seen.add(n)
136 seen.add(n)
137 # Ensure that nodes without parents are present in the
137 # Ensure that nodes without parents are present in the
138 # 'children' mapping.
138 # 'children' mapping.
139 children.setdefault(n, [])
139 children.setdefault(n, [])
140 hasparent = False
140 hasparent = False
141 for p in parents[n]:
141 for p in parents[n]:
142 if not p in self.map:
142 if not p in self.map:
143 visit.append(p)
143 visit.append(p)
144 hasparent = True
144 hasparent = True
145 children.setdefault(p, []).append(n)
145 children.setdefault(p, []).append(n)
146 if not hasparent:
146 if not hasparent:
147 roots.append(n)
147 roots.append(n)
148
148
149 return children, roots
149 return children, roots
150
150
151 # Sort functions are supposed to take a list of revisions which
151 # Sort functions are supposed to take a list of revisions which
152 # can be converted immediately and pick one
152 # can be converted immediately and pick one
153
153
154 def makebranchsorter():
154 def makebranchsorter():
155 """If the previously converted revision has a child in the
155 """If the previously converted revision has a child in the
156 eligible revisions list, pick it. Return the list head
156 eligible revisions list, pick it. Return the list head
157 otherwise. Branch sort attempts to minimize branch
157 otherwise. Branch sort attempts to minimize branch
158 switching, which is harmful for Mercurial backend
158 switching, which is harmful for Mercurial backend
159 compression.
159 compression.
160 """
160 """
161 prev = [None]
161 prev = [None]
162 def picknext(nodes):
162 def picknext(nodes):
163 next = nodes[0]
163 next = nodes[0]
164 for n in nodes:
164 for n in nodes:
165 if prev[0] in parents[n]:
165 if prev[0] in parents[n]:
166 next = n
166 next = n
167 break
167 break
168 prev[0] = next
168 prev[0] = next
169 return next
169 return next
170 return picknext
170 return picknext
171
171
172 def makesourcesorter():
172 def makesourcesorter():
173 """Source specific sort."""
173 """Source specific sort."""
174 keyfn = lambda n: self.commitcache[n].sortkey
174 keyfn = lambda n: self.commitcache[n].sortkey
175 def picknext(nodes):
175 def picknext(nodes):
176 return sorted(nodes, key=keyfn)[0]
176 return sorted(nodes, key=keyfn)[0]
177 return picknext
177 return picknext
178
178
179 def makedatesorter():
179 def makedatesorter():
180 """Sort revisions by date."""
180 """Sort revisions by date."""
181 dates = {}
181 dates = {}
182 def getdate(n):
182 def getdate(n):
183 if n not in dates:
183 if n not in dates:
184 dates[n] = util.parsedate(self.commitcache[n].date)
184 dates[n] = util.parsedate(self.commitcache[n].date)
185 return dates[n]
185 return dates[n]
186
186
187 def picknext(nodes):
187 def picknext(nodes):
188 return min([(getdate(n), n) for n in nodes])[1]
188 return min([(getdate(n), n) for n in nodes])[1]
189
189
190 return picknext
190 return picknext
191
191
192 if sortmode == 'branchsort':
192 if sortmode == 'branchsort':
193 picknext = makebranchsorter()
193 picknext = makebranchsorter()
194 elif sortmode == 'datesort':
194 elif sortmode == 'datesort':
195 picknext = makedatesorter()
195 picknext = makedatesorter()
196 elif sortmode == 'sourcesort':
196 elif sortmode == 'sourcesort':
197 picknext = makesourcesorter()
197 picknext = makesourcesorter()
198 else:
198 else:
199 raise util.Abort(_('unknown sort mode: %s') % sortmode)
199 raise util.Abort(_('unknown sort mode: %s') % sortmode)
200
200
201 children, actives = mapchildren(parents)
201 children, actives = mapchildren(parents)
202
202
203 s = []
203 s = []
204 pendings = {}
204 pendings = {}
205 while actives:
205 while actives:
206 n = picknext(actives)
206 n = picknext(actives)
207 actives.remove(n)
207 actives.remove(n)
208 s.append(n)
208 s.append(n)
209
209
210 # Update dependents list
210 # Update dependents list
211 for c in children.get(n, []):
211 for c in children.get(n, []):
212 if c not in pendings:
212 if c not in pendings:
213 pendings[c] = [p for p in parents[c] if p not in self.map]
213 pendings[c] = [p for p in parents[c] if p not in self.map]
214 try:
214 try:
215 pendings[c].remove(n)
215 pendings[c].remove(n)
216 except ValueError:
216 except ValueError:
217 raise util.Abort(_('cycle detected between %s and %s')
217 raise util.Abort(_('cycle detected between %s and %s')
218 % (recode(c), recode(n)))
218 % (recode(c), recode(n)))
219 if not pendings[c]:
219 if not pendings[c]:
220 # Parents are converted, node is eligible
220 # Parents are converted, node is eligible
221 actives.insert(0, c)
221 actives.insert(0, c)
222 pendings[c] = None
222 pendings[c] = None
223
223
224 if len(s) != len(parents):
224 if len(s) != len(parents):
225 raise util.Abort(_("not all revisions were sorted"))
225 raise util.Abort(_("not all revisions were sorted"))
226
226
227 return s
227 return s
228
228
229 def writeauthormap(self):
229 def writeauthormap(self):
230 authorfile = self.authorfile
230 authorfile = self.authorfile
231 if authorfile:
231 if authorfile:
232 self.ui.status(_('Writing author map file %s\n') % authorfile)
232 self.ui.status(_('Writing author map file %s\n') % authorfile)
233 ofile = open(authorfile, 'w+')
233 ofile = open(authorfile, 'w+')
234 for author in self.authors:
234 for author in self.authors:
235 ofile.write("%s=%s\n" % (author, self.authors[author]))
235 ofile.write("%s=%s\n" % (author, self.authors[author]))
236 ofile.close()
236 ofile.close()
237
237
238 def readauthormap(self, authorfile):
238 def readauthormap(self, authorfile):
239 afile = open(authorfile, 'r')
239 afile = open(authorfile, 'r')
240 for line in afile:
240 for line in afile:
241
241
242 line = line.strip()
242 line = line.strip()
243 if not line or line.startswith('#'):
243 if not line or line.startswith('#'):
244 continue
244 continue
245
245
246 try:
246 try:
247 srcauthor, dstauthor = line.split('=', 1)
247 srcauthor, dstauthor = line.split('=', 1)
248 except ValueError:
248 except ValueError:
249 msg = _('Ignoring bad line in author map file %s: %s\n')
249 msg = _('Ignoring bad line in author map file %s: %s\n')
250 self.ui.warn(msg % (authorfile, line.rstrip()))
250 self.ui.warn(msg % (authorfile, line.rstrip()))
251 continue
251 continue
252
252
253 srcauthor = srcauthor.strip()
253 srcauthor = srcauthor.strip()
254 dstauthor = dstauthor.strip()
254 dstauthor = dstauthor.strip()
255 if self.authors.get(srcauthor) in (None, dstauthor):
255 if self.authors.get(srcauthor) in (None, dstauthor):
256 msg = _('mapping author %s to %s\n')
256 msg = _('mapping author %s to %s\n')
257 self.ui.debug(msg % (srcauthor, dstauthor))
257 self.ui.debug(msg % (srcauthor, dstauthor))
258 self.authors[srcauthor] = dstauthor
258 self.authors[srcauthor] = dstauthor
259 continue
259 continue
260
260
261 m = _('overriding mapping for author %s, was %s, will be %s\n')
261 m = _('overriding mapping for author %s, was %s, will be %s\n')
262 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
262 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
263
263
264 afile.close()
264 afile.close()
265
265
266 def cachecommit(self, rev):
266 def cachecommit(self, rev):
267 commit = self.source.getcommit(rev)
267 commit = self.source.getcommit(rev)
268 commit.author = self.authors.get(commit.author, commit.author)
268 commit.author = self.authors.get(commit.author, commit.author)
269 commit.branch = self.branchmap.get(commit.branch, commit.branch)
269 commit.branch = self.branchmap.get(commit.branch, commit.branch)
270 self.commitcache[rev] = commit
270 self.commitcache[rev] = commit
271 return commit
271 return commit
272
272
273 def copy(self, rev):
273 def copy(self, rev):
274 commit = self.commitcache[rev]
274 commit = self.commitcache[rev]
275
275
276 changes = self.source.getchanges(rev)
276 changes = self.source.getchanges(rev)
277 if isinstance(changes, basestring):
277 if isinstance(changes, basestring):
278 if changes == SKIPREV:
278 if changes == SKIPREV:
279 dest = SKIPREV
279 dest = SKIPREV
280 else:
280 else:
281 dest = self.map[changes]
281 dest = self.map[changes]
282 self.map[rev] = dest
282 self.map[rev] = dest
283 return
283 return
284 files, copies = changes
284 files, copies = changes
285 pbranches = []
285 pbranches = []
286 if commit.parents:
286 if commit.parents:
287 for prev in commit.parents:
287 for prev in commit.parents:
288 if prev not in self.commitcache:
288 if prev not in self.commitcache:
289 self.cachecommit(prev)
289 self.cachecommit(prev)
290 pbranches.append((self.map[prev],
290 pbranches.append((self.map[prev],
291 self.commitcache[prev].branch))
291 self.commitcache[prev].branch))
292 self.dest.setbranch(commit.branch, pbranches)
292 self.dest.setbranch(commit.branch, pbranches)
293 try:
293 try:
294 parents = self.splicemap[rev].replace(',', ' ').split()
294 parents = self.splicemap[rev].replace(',', ' ').split()
295 self.ui.status(_('spliced in %s as parents of %s\n') %
295 self.ui.status(_('spliced in %s as parents of %s\n') %
296 (parents, rev))
296 (parents, rev))
297 parents = [self.map.get(p, p) for p in parents]
297 parents = [self.map.get(p, p) for p in parents]
298 except KeyError:
298 except KeyError:
299 parents = [b[0] for b in pbranches]
299 parents = [b[0] for b in pbranches]
300 newnode = self.dest.putcommit(files, copies, parents, commit,
300 newnode = self.dest.putcommit(files, copies, parents, commit,
301 self.source, self.map)
301 self.source, self.map)
302 self.source.converted(rev, newnode)
302 self.source.converted(rev, newnode)
303 self.map[rev] = newnode
303 self.map[rev] = newnode
304
304
305 def convert(self, sortmode):
305 def convert(self, sortmode):
306 try:
306 try:
307 self.source.before()
307 self.source.before()
308 self.dest.before()
308 self.dest.before()
309 self.source.setrevmap(self.map)
309 self.source.setrevmap(self.map)
310 self.ui.status(_("scanning source...\n"))
310 self.ui.status(_("scanning source...\n"))
311 heads = self.source.getheads()
311 heads = self.source.getheads()
312 parents = self.walktree(heads)
312 parents = self.walktree(heads)
313 self.ui.status(_("sorting...\n"))
313 self.ui.status(_("sorting...\n"))
314 t = self.toposort(parents, sortmode)
314 t = self.toposort(parents, sortmode)
315 num = len(t)
315 num = len(t)
316 c = None
316 c = None
317
317
318 self.ui.status(_("converting...\n"))
318 self.ui.status(_("converting...\n"))
319 for c in t:
319 for c in t:
320 num -= 1
320 num -= 1
321 desc = self.commitcache[c].desc
321 desc = self.commitcache[c].desc
322 if "\n" in desc:
322 if "\n" in desc:
323 desc = desc.splitlines()[0]
323 desc = desc.splitlines()[0]
324 # convert log message to local encoding without using
324 # convert log message to local encoding without using
325 # tolocal() because encoding.encoding conver() use it as
325 # tolocal() because encoding.encoding conver() use it as
326 # 'utf-8'
326 # 'utf-8'
327 self.ui.status("%d %s\n" % (num, recode(desc)))
327 self.ui.status("%d %s\n" % (num, recode(desc)))
328 self.ui.note(_("source: %s\n") % recode(c))
328 self.ui.note(_("source: %s\n") % recode(c))
329 self.copy(c)
329 self.copy(c)
330
330
331 tags = self.source.gettags()
331 tags = self.source.gettags()
332 ctags = {}
332 ctags = {}
333 for k in tags:
333 for k in tags:
334 v = tags[k]
334 v = tags[k]
335 if self.map.get(v, SKIPREV) != SKIPREV:
335 if self.map.get(v, SKIPREV) != SKIPREV:
336 ctags[k] = self.map[v]
336 ctags[k] = self.map[v]
337
337
338 if c and ctags:
338 if c and ctags:
339 nrev = self.dest.puttags(ctags)
339 nrev, tagsparent = self.dest.puttags(ctags)
340 # write another hash correspondence to override the previous
340 if nrev and tagsparent:
341 # one so we don't end up with extra tag heads
341 # write another hash correspondence to override the previous
342 if nrev:
342 # one so we don't end up with extra tag heads
343 self.map[c] = nrev
343 tagsparents = [e for e in self.map.iteritems()
344 if e[1] == tagsparent]
345 if tagsparents:
346 self.map[tagsparents[0][0]] = nrev
344
347
345 self.writeauthormap()
348 self.writeauthormap()
346 finally:
349 finally:
347 self.cleanup()
350 self.cleanup()
348
351
349 def cleanup(self):
352 def cleanup(self):
350 try:
353 try:
351 self.dest.after()
354 self.dest.after()
352 finally:
355 finally:
353 self.source.after()
356 self.source.after()
354 self.map.close()
357 self.map.close()
355
358
356 def convert(ui, src, dest=None, revmapfile=None, **opts):
359 def convert(ui, src, dest=None, revmapfile=None, **opts):
357 global orig_encoding
360 global orig_encoding
358 orig_encoding = encoding.encoding
361 orig_encoding = encoding.encoding
359 encoding.encoding = 'UTF-8'
362 encoding.encoding = 'UTF-8'
360
363
361 if not dest:
364 if not dest:
362 dest = hg.defaultdest(src) + "-hg"
365 dest = hg.defaultdest(src) + "-hg"
363 ui.status(_("assuming destination %s\n") % dest)
366 ui.status(_("assuming destination %s\n") % dest)
364
367
365 destc = convertsink(ui, dest, opts.get('dest_type'))
368 destc = convertsink(ui, dest, opts.get('dest_type'))
366
369
367 try:
370 try:
368 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
371 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
369 opts.get('rev'))
372 opts.get('rev'))
370 except Exception:
373 except Exception:
371 for path in destc.created:
374 for path in destc.created:
372 shutil.rmtree(path, True)
375 shutil.rmtree(path, True)
373 raise
376 raise
374
377
375 sortmodes = ('branchsort', 'datesort', 'sourcesort')
378 sortmodes = ('branchsort', 'datesort', 'sourcesort')
376 sortmode = [m for m in sortmodes if opts.get(m)]
379 sortmode = [m for m in sortmodes if opts.get(m)]
377 if len(sortmode) > 1:
380 if len(sortmode) > 1:
378 raise util.Abort(_('more than one sort mode specified'))
381 raise util.Abort(_('more than one sort mode specified'))
379 sortmode = sortmode and sortmode[0] or defaultsort
382 sortmode = sortmode and sortmode[0] or defaultsort
380 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
383 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
381 raise util.Abort(_('--sourcesort is not supported by this data source'))
384 raise util.Abort(_('--sourcesort is not supported by this data source'))
382
385
383 fmap = opts.get('filemap')
386 fmap = opts.get('filemap')
384 if fmap:
387 if fmap:
385 srcc = filemap.filemap_source(ui, srcc, fmap)
388 srcc = filemap.filemap_source(ui, srcc, fmap)
386 destc.setfilemapmode(True)
389 destc.setfilemapmode(True)
387
390
388 if not revmapfile:
391 if not revmapfile:
389 try:
392 try:
390 revmapfile = destc.revmapfile()
393 revmapfile = destc.revmapfile()
391 except:
394 except:
392 revmapfile = os.path.join(destc, "map")
395 revmapfile = os.path.join(destc, "map")
393
396
394 c = converter(ui, srcc, destc, revmapfile, opts)
397 c = converter(ui, srcc, destc, revmapfile, opts)
395 c.convert(sortmode)
398 c.convert(sortmode)
396
399
@@ -1,363 +1,363 b''
1 # hg.py - hg backend for convert extension
1 # hg.py - hg backend for convert extension
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 # Notes for hg->hg conversion:
8 # Notes for hg->hg conversion:
9 #
9 #
10 # * Old versions of Mercurial didn't trim the whitespace from the ends
10 # * Old versions of Mercurial didn't trim the whitespace from the ends
11 # of commit messages, but new versions do. Changesets created by
11 # of commit messages, but new versions do. Changesets created by
12 # those older versions, then converted, may thus have different
12 # those older versions, then converted, may thus have different
13 # hashes for changesets that are otherwise identical.
13 # hashes for changesets that are otherwise identical.
14 #
14 #
15 # * Using "--config convert.hg.saverev=true" will make the source
15 # * Using "--config convert.hg.saverev=true" will make the source
16 # identifier to be stored in the converted revision. This will cause
16 # identifier to be stored in the converted revision. This will cause
17 # the converted revision to have a different identity than the
17 # the converted revision to have a different identity than the
18 # source.
18 # source.
19
19
20
20
21 import os, time, cStringIO
21 import os, time, cStringIO
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 from mercurial.node import bin, hex, nullid
23 from mercurial.node import bin, hex, nullid
24 from mercurial import hg, util, context, error
24 from mercurial import hg, util, context, error
25
25
26 from common import NoRepo, commit, converter_source, converter_sink
26 from common import NoRepo, commit, converter_source, converter_sink
27
27
28 class mercurial_sink(converter_sink):
28 class mercurial_sink(converter_sink):
29 def __init__(self, ui, path):
29 def __init__(self, ui, path):
30 converter_sink.__init__(self, ui, path)
30 converter_sink.__init__(self, ui, path)
31 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
31 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
32 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
32 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
33 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
33 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
34 self.lastbranch = None
34 self.lastbranch = None
35 if os.path.isdir(path) and len(os.listdir(path)) > 0:
35 if os.path.isdir(path) and len(os.listdir(path)) > 0:
36 try:
36 try:
37 self.repo = hg.repository(self.ui, path)
37 self.repo = hg.repository(self.ui, path)
38 if not self.repo.local():
38 if not self.repo.local():
39 raise NoRepo(_('%s is not a local Mercurial repo') % path)
39 raise NoRepo(_('%s is not a local Mercurial repo') % path)
40 except error.RepoError, err:
40 except error.RepoError, err:
41 ui.traceback()
41 ui.traceback()
42 raise NoRepo(err.args[0])
42 raise NoRepo(err.args[0])
43 else:
43 else:
44 try:
44 try:
45 ui.status(_('initializing destination %s repository\n') % path)
45 ui.status(_('initializing destination %s repository\n') % path)
46 self.repo = hg.repository(self.ui, path, create=True)
46 self.repo = hg.repository(self.ui, path, create=True)
47 if not self.repo.local():
47 if not self.repo.local():
48 raise NoRepo(_('%s is not a local Mercurial repo') % path)
48 raise NoRepo(_('%s is not a local Mercurial repo') % path)
49 self.created.append(path)
49 self.created.append(path)
50 except error.RepoError:
50 except error.RepoError:
51 ui.traceback()
51 ui.traceback()
52 raise NoRepo("could not create hg repo %s as sink" % path)
52 raise NoRepo("could not create hg repo %s as sink" % path)
53 self.lock = None
53 self.lock = None
54 self.wlock = None
54 self.wlock = None
55 self.filemapmode = False
55 self.filemapmode = False
56
56
57 def before(self):
57 def before(self):
58 self.ui.debug(_('run hg sink pre-conversion action\n'))
58 self.ui.debug(_('run hg sink pre-conversion action\n'))
59 self.wlock = self.repo.wlock()
59 self.wlock = self.repo.wlock()
60 self.lock = self.repo.lock()
60 self.lock = self.repo.lock()
61
61
62 def after(self):
62 def after(self):
63 self.ui.debug(_('run hg sink post-conversion action\n'))
63 self.ui.debug(_('run hg sink post-conversion action\n'))
64 self.lock.release()
64 self.lock.release()
65 self.wlock.release()
65 self.wlock.release()
66
66
67 def revmapfile(self):
67 def revmapfile(self):
68 return os.path.join(self.path, ".hg", "shamap")
68 return os.path.join(self.path, ".hg", "shamap")
69
69
70 def authorfile(self):
70 def authorfile(self):
71 return os.path.join(self.path, ".hg", "authormap")
71 return os.path.join(self.path, ".hg", "authormap")
72
72
73 def getheads(self):
73 def getheads(self):
74 h = self.repo.changelog.heads()
74 h = self.repo.changelog.heads()
75 return [ hex(x) for x in h ]
75 return [ hex(x) for x in h ]
76
76
77 def setbranch(self, branch, pbranches):
77 def setbranch(self, branch, pbranches):
78 if not self.clonebranches:
78 if not self.clonebranches:
79 return
79 return
80
80
81 setbranch = (branch != self.lastbranch)
81 setbranch = (branch != self.lastbranch)
82 self.lastbranch = branch
82 self.lastbranch = branch
83 if not branch:
83 if not branch:
84 branch = 'default'
84 branch = 'default'
85 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
85 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
86 pbranch = pbranches and pbranches[0][1] or 'default'
86 pbranch = pbranches and pbranches[0][1] or 'default'
87
87
88 branchpath = os.path.join(self.path, branch)
88 branchpath = os.path.join(self.path, branch)
89 if setbranch:
89 if setbranch:
90 self.after()
90 self.after()
91 try:
91 try:
92 self.repo = hg.repository(self.ui, branchpath)
92 self.repo = hg.repository(self.ui, branchpath)
93 except:
93 except:
94 self.repo = hg.repository(self.ui, branchpath, create=True)
94 self.repo = hg.repository(self.ui, branchpath, create=True)
95 self.before()
95 self.before()
96
96
97 # pbranches may bring revisions from other branches (merge parents)
97 # pbranches may bring revisions from other branches (merge parents)
98 # Make sure we have them, or pull them.
98 # Make sure we have them, or pull them.
99 missings = {}
99 missings = {}
100 for b in pbranches:
100 for b in pbranches:
101 try:
101 try:
102 self.repo.lookup(b[0])
102 self.repo.lookup(b[0])
103 except:
103 except:
104 missings.setdefault(b[1], []).append(b[0])
104 missings.setdefault(b[1], []).append(b[0])
105
105
106 if missings:
106 if missings:
107 self.after()
107 self.after()
108 for pbranch, heads in missings.iteritems():
108 for pbranch, heads in missings.iteritems():
109 pbranchpath = os.path.join(self.path, pbranch)
109 pbranchpath = os.path.join(self.path, pbranch)
110 prepo = hg.repository(self.ui, pbranchpath)
110 prepo = hg.repository(self.ui, pbranchpath)
111 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
111 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
112 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
112 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
113 self.before()
113 self.before()
114
114
115 def _rewritetags(self, source, revmap, data):
115 def _rewritetags(self, source, revmap, data):
116 fp = cStringIO.StringIO()
116 fp = cStringIO.StringIO()
117 for line in data.splitlines():
117 for line in data.splitlines():
118 s = line.split(' ', 1)
118 s = line.split(' ', 1)
119 if len(s) != 2:
119 if len(s) != 2:
120 continue
120 continue
121 revid = revmap.get(source.lookuprev(s[0]))
121 revid = revmap.get(source.lookuprev(s[0]))
122 if not revid:
122 if not revid:
123 continue
123 continue
124 fp.write('%s %s\n' % (revid, s[1]))
124 fp.write('%s %s\n' % (revid, s[1]))
125 return fp.getvalue()
125 return fp.getvalue()
126
126
127 def putcommit(self, files, copies, parents, commit, source, revmap):
127 def putcommit(self, files, copies, parents, commit, source, revmap):
128
128
129 files = dict(files)
129 files = dict(files)
130 def getfilectx(repo, memctx, f):
130 def getfilectx(repo, memctx, f):
131 v = files[f]
131 v = files[f]
132 data = source.getfile(f, v)
132 data = source.getfile(f, v)
133 e = source.getmode(f, v)
133 e = source.getmode(f, v)
134 if f == '.hgtags':
134 if f == '.hgtags':
135 data = self._rewritetags(source, revmap, data)
135 data = self._rewritetags(source, revmap, data)
136 return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f))
136 return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f))
137
137
138 pl = []
138 pl = []
139 for p in parents:
139 for p in parents:
140 if p not in pl:
140 if p not in pl:
141 pl.append(p)
141 pl.append(p)
142 parents = pl
142 parents = pl
143 nparents = len(parents)
143 nparents = len(parents)
144 if self.filemapmode and nparents == 1:
144 if self.filemapmode and nparents == 1:
145 m1node = self.repo.changelog.read(bin(parents[0]))[0]
145 m1node = self.repo.changelog.read(bin(parents[0]))[0]
146 parent = parents[0]
146 parent = parents[0]
147
147
148 if len(parents) < 2: parents.append(nullid)
148 if len(parents) < 2: parents.append(nullid)
149 if len(parents) < 2: parents.append(nullid)
149 if len(parents) < 2: parents.append(nullid)
150 p2 = parents.pop(0)
150 p2 = parents.pop(0)
151
151
152 text = commit.desc
152 text = commit.desc
153 extra = commit.extra.copy()
153 extra = commit.extra.copy()
154 if self.branchnames and commit.branch:
154 if self.branchnames and commit.branch:
155 extra['branch'] = commit.branch
155 extra['branch'] = commit.branch
156 if commit.rev:
156 if commit.rev:
157 extra['convert_revision'] = commit.rev
157 extra['convert_revision'] = commit.rev
158
158
159 while parents:
159 while parents:
160 p1 = p2
160 p1 = p2
161 p2 = parents.pop(0)
161 p2 = parents.pop(0)
162 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(), getfilectx,
162 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(), getfilectx,
163 commit.author, commit.date, extra)
163 commit.author, commit.date, extra)
164 self.repo.commitctx(ctx)
164 self.repo.commitctx(ctx)
165 text = "(octopus merge fixup)\n"
165 text = "(octopus merge fixup)\n"
166 p2 = hex(self.repo.changelog.tip())
166 p2 = hex(self.repo.changelog.tip())
167
167
168 if self.filemapmode and nparents == 1:
168 if self.filemapmode and nparents == 1:
169 man = self.repo.manifest
169 man = self.repo.manifest
170 mnode = self.repo.changelog.read(bin(p2))[0]
170 mnode = self.repo.changelog.read(bin(p2))[0]
171 if not man.cmp(m1node, man.revision(mnode)):
171 if not man.cmp(m1node, man.revision(mnode)):
172 self.ui.status(_("filtering out empty revision\n"))
172 self.ui.status(_("filtering out empty revision\n"))
173 self.repo.rollback()
173 self.repo.rollback()
174 return parent
174 return parent
175 return p2
175 return p2
176
176
177 def puttags(self, tags):
177 def puttags(self, tags):
178 try:
178 try:
179 parentctx = self.repo[self.tagsbranch]
179 parentctx = self.repo[self.tagsbranch]
180 tagparent = parentctx.node()
180 tagparent = parentctx.node()
181 except error.RepoError:
181 except error.RepoError:
182 parentctx = None
182 parentctx = None
183 tagparent = nullid
183 tagparent = nullid
184
184
185 try:
185 try:
186 oldlines = sorted(parentctx['.hgtags'].data().splitlines(1))
186 oldlines = sorted(parentctx['.hgtags'].data().splitlines(1))
187 except:
187 except:
188 oldlines = []
188 oldlines = []
189
189
190 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
190 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
191 if newlines == oldlines:
191 if newlines == oldlines:
192 return None
192 return None, None
193 data = "".join(newlines)
193 data = "".join(newlines)
194 def getfilectx(repo, memctx, f):
194 def getfilectx(repo, memctx, f):
195 return context.memfilectx(f, data, False, False, None)
195 return context.memfilectx(f, data, False, False, None)
196
196
197 self.ui.status(_("updating tags\n"))
197 self.ui.status(_("updating tags\n"))
198 date = "%s 0" % int(time.mktime(time.gmtime()))
198 date = "%s 0" % int(time.mktime(time.gmtime()))
199 extra = {'branch': self.tagsbranch}
199 extra = {'branch': self.tagsbranch}
200 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
200 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
201 [".hgtags"], getfilectx, "convert-repo", date,
201 [".hgtags"], getfilectx, "convert-repo", date,
202 extra)
202 extra)
203 self.repo.commitctx(ctx)
203 self.repo.commitctx(ctx)
204 return hex(self.repo.changelog.tip())
204 return hex(self.repo.changelog.tip()), hex(tagparent)
205
205
206 def setfilemapmode(self, active):
206 def setfilemapmode(self, active):
207 self.filemapmode = active
207 self.filemapmode = active
208
208
209 class mercurial_source(converter_source):
209 class mercurial_source(converter_source):
210 def __init__(self, ui, path, rev=None):
210 def __init__(self, ui, path, rev=None):
211 converter_source.__init__(self, ui, path, rev)
211 converter_source.__init__(self, ui, path, rev)
212 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
212 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
213 self.ignored = set()
213 self.ignored = set()
214 self.saverev = ui.configbool('convert', 'hg.saverev', False)
214 self.saverev = ui.configbool('convert', 'hg.saverev', False)
215 try:
215 try:
216 self.repo = hg.repository(self.ui, path)
216 self.repo = hg.repository(self.ui, path)
217 # try to provoke an exception if this isn't really a hg
217 # try to provoke an exception if this isn't really a hg
218 # repo, but some other bogus compatible-looking url
218 # repo, but some other bogus compatible-looking url
219 if not self.repo.local():
219 if not self.repo.local():
220 raise error.RepoError()
220 raise error.RepoError()
221 except error.RepoError:
221 except error.RepoError:
222 ui.traceback()
222 ui.traceback()
223 raise NoRepo("%s is not a local Mercurial repo" % path)
223 raise NoRepo("%s is not a local Mercurial repo" % path)
224 self.lastrev = None
224 self.lastrev = None
225 self.lastctx = None
225 self.lastctx = None
226 self._changescache = None
226 self._changescache = None
227 self.convertfp = None
227 self.convertfp = None
228 # Restrict converted revisions to startrev descendants
228 # Restrict converted revisions to startrev descendants
229 startnode = ui.config('convert', 'hg.startrev')
229 startnode = ui.config('convert', 'hg.startrev')
230 if startnode is not None:
230 if startnode is not None:
231 try:
231 try:
232 startnode = self.repo.lookup(startnode)
232 startnode = self.repo.lookup(startnode)
233 except error.RepoError:
233 except error.RepoError:
234 raise util.Abort(_('%s is not a valid start revision')
234 raise util.Abort(_('%s is not a valid start revision')
235 % startnode)
235 % startnode)
236 startrev = self.repo.changelog.rev(startnode)
236 startrev = self.repo.changelog.rev(startnode)
237 children = {startnode: 1}
237 children = {startnode: 1}
238 for rev in self.repo.changelog.descendants(startrev):
238 for rev in self.repo.changelog.descendants(startrev):
239 children[self.repo.changelog.node(rev)] = 1
239 children[self.repo.changelog.node(rev)] = 1
240 self.keep = children.__contains__
240 self.keep = children.__contains__
241 else:
241 else:
242 self.keep = util.always
242 self.keep = util.always
243
243
244 def changectx(self, rev):
244 def changectx(self, rev):
245 if self.lastrev != rev:
245 if self.lastrev != rev:
246 self.lastctx = self.repo[rev]
246 self.lastctx = self.repo[rev]
247 self.lastrev = rev
247 self.lastrev = rev
248 return self.lastctx
248 return self.lastctx
249
249
250 def parents(self, ctx):
250 def parents(self, ctx):
251 return [p.node() for p in ctx.parents()
251 return [p.node() for p in ctx.parents()
252 if p and self.keep(p.node())]
252 if p and self.keep(p.node())]
253
253
254 def getheads(self):
254 def getheads(self):
255 if self.rev:
255 if self.rev:
256 heads = [self.repo[self.rev].node()]
256 heads = [self.repo[self.rev].node()]
257 else:
257 else:
258 heads = self.repo.heads()
258 heads = self.repo.heads()
259 return [hex(h) for h in heads if self.keep(h)]
259 return [hex(h) for h in heads if self.keep(h)]
260
260
261 def getfile(self, name, rev):
261 def getfile(self, name, rev):
262 try:
262 try:
263 return self.changectx(rev)[name].data()
263 return self.changectx(rev)[name].data()
264 except error.LookupError, err:
264 except error.LookupError, err:
265 raise IOError(err)
265 raise IOError(err)
266
266
267 def getmode(self, name, rev):
267 def getmode(self, name, rev):
268 return self.changectx(rev).manifest().flags(name)
268 return self.changectx(rev).manifest().flags(name)
269
269
270 def getchanges(self, rev):
270 def getchanges(self, rev):
271 ctx = self.changectx(rev)
271 ctx = self.changectx(rev)
272 parents = self.parents(ctx)
272 parents = self.parents(ctx)
273 if not parents:
273 if not parents:
274 files = sorted(ctx.manifest())
274 files = sorted(ctx.manifest())
275 if self.ignoreerrors:
275 if self.ignoreerrors:
276 # calling getcopies() is a simple way to detect missing
276 # calling getcopies() is a simple way to detect missing
277 # revlogs and populate self.ignored
277 # revlogs and populate self.ignored
278 self.getcopies(ctx, files)
278 self.getcopies(ctx, files)
279 return [(f, rev) for f in files if f not in self.ignored], {}
279 return [(f, rev) for f in files if f not in self.ignored], {}
280 if self._changescache and self._changescache[0] == rev:
280 if self._changescache and self._changescache[0] == rev:
281 m, a, r = self._changescache[1]
281 m, a, r = self._changescache[1]
282 else:
282 else:
283 m, a, r = self.repo.status(parents[0], ctx.node())[:3]
283 m, a, r = self.repo.status(parents[0], ctx.node())[:3]
284 # getcopies() detects missing revlogs early, run it before
284 # getcopies() detects missing revlogs early, run it before
285 # filtering the changes.
285 # filtering the changes.
286 copies = self.getcopies(ctx, m + a)
286 copies = self.getcopies(ctx, m + a)
287 changes = [(name, rev) for name in m + a + r
287 changes = [(name, rev) for name in m + a + r
288 if name not in self.ignored]
288 if name not in self.ignored]
289 return sorted(changes), copies
289 return sorted(changes), copies
290
290
291 def getcopies(self, ctx, files):
291 def getcopies(self, ctx, files):
292 copies = {}
292 copies = {}
293 for name in files:
293 for name in files:
294 if name in self.ignored:
294 if name in self.ignored:
295 continue
295 continue
296 try:
296 try:
297 copysource, copynode = ctx.filectx(name).renamed()
297 copysource, copynode = ctx.filectx(name).renamed()
298 if copysource in self.ignored or not self.keep(copynode):
298 if copysource in self.ignored or not self.keep(copynode):
299 continue
299 continue
300 copies[name] = copysource
300 copies[name] = copysource
301 except TypeError:
301 except TypeError:
302 pass
302 pass
303 except error.LookupError, e:
303 except error.LookupError, e:
304 if not self.ignoreerrors:
304 if not self.ignoreerrors:
305 raise
305 raise
306 self.ignored.add(name)
306 self.ignored.add(name)
307 self.ui.warn(_('ignoring: %s\n') % e)
307 self.ui.warn(_('ignoring: %s\n') % e)
308 return copies
308 return copies
309
309
310 def getcommit(self, rev):
310 def getcommit(self, rev):
311 ctx = self.changectx(rev)
311 ctx = self.changectx(rev)
312 parents = [hex(p) for p in self.parents(ctx)]
312 parents = [hex(p) for p in self.parents(ctx)]
313 if self.saverev:
313 if self.saverev:
314 crev = rev
314 crev = rev
315 else:
315 else:
316 crev = None
316 crev = None
317 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
317 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
318 desc=ctx.description(), rev=crev, parents=parents,
318 desc=ctx.description(), rev=crev, parents=parents,
319 branch=ctx.branch(), extra=ctx.extra(),
319 branch=ctx.branch(), extra=ctx.extra(),
320 sortkey=ctx.rev())
320 sortkey=ctx.rev())
321
321
322 def gettags(self):
322 def gettags(self):
323 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
323 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
324 return dict([(name, hex(node)) for name, node in tags
324 return dict([(name, hex(node)) for name, node in tags
325 if self.keep(node)])
325 if self.keep(node)])
326
326
327 def getchangedfiles(self, rev, i):
327 def getchangedfiles(self, rev, i):
328 ctx = self.changectx(rev)
328 ctx = self.changectx(rev)
329 parents = self.parents(ctx)
329 parents = self.parents(ctx)
330 if not parents and i is None:
330 if not parents and i is None:
331 i = 0
331 i = 0
332 changes = [], ctx.manifest().keys(), []
332 changes = [], ctx.manifest().keys(), []
333 else:
333 else:
334 i = i or 0
334 i = i or 0
335 changes = self.repo.status(parents[i], ctx.node())[:3]
335 changes = self.repo.status(parents[i], ctx.node())[:3]
336 changes = [[f for f in l if f not in self.ignored] for l in changes]
336 changes = [[f for f in l if f not in self.ignored] for l in changes]
337
337
338 if i == 0:
338 if i == 0:
339 self._changescache = (rev, changes)
339 self._changescache = (rev, changes)
340
340
341 return changes[0] + changes[1] + changes[2]
341 return changes[0] + changes[1] + changes[2]
342
342
343 def converted(self, rev, destrev):
343 def converted(self, rev, destrev):
344 if self.convertfp is None:
344 if self.convertfp is None:
345 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
345 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
346 'a')
346 'a')
347 self.convertfp.write('%s %s\n' % (destrev, rev))
347 self.convertfp.write('%s %s\n' % (destrev, rev))
348 self.convertfp.flush()
348 self.convertfp.flush()
349
349
350 def before(self):
350 def before(self):
351 self.ui.debug(_('run hg source pre-conversion action\n'))
351 self.ui.debug(_('run hg source pre-conversion action\n'))
352
352
353 def after(self):
353 def after(self):
354 self.ui.debug(_('run hg source post-conversion action\n'))
354 self.ui.debug(_('run hg source post-conversion action\n'))
355
355
356 def hasnativeorder(self):
356 def hasnativeorder(self):
357 return True
357 return True
358
358
359 def lookuprev(self, rev):
359 def lookuprev(self, rev):
360 try:
360 try:
361 return hex(self.repo.lookup(rev))
361 return hex(self.repo.lookup(rev))
362 except error.RepoError:
362 except error.RepoError:
363 return None
363 return None
@@ -1,29 +1,29 b''
1 marked working directory as branch branch0
1 marked working directory as branch branch0
2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3 (branch merge, don't forget to commit)
3 (branch merge, don't forget to commit)
4 % convert
4 % convert
5 3 adda
5 3 adda
6 2 changea
6 2 changea
7 1 addb
7 1 addb
8 pulling from default into branch0
8 pulling from default into branch0
9 1 changesets found
9 1 changesets found
10 0 mergeab
10 0 mergeab
11 pulling from default into branch0
11 pulling from default into branch0
12 1 changesets found
12 1 changesets found
13 marked working directory as branch branch1
13 marked working directory as branch branch1
14 marked working directory as branch branch2
14 marked working directory as branch branch2
15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 (branch merge, don't forget to commit)
16 (branch merge, don't forget to commit)
17 marked working directory as branch branch3
17 marked working directory as branch branch3
18 % incremental conversion
18 % incremental conversion
19 2 c1
19 2 c1
20 pulling from branch0 into branch1
20 pulling from branch0 into branch1
21 2 changesets found
21 4 changesets found
22 1 c2
22 1 c2
23 pulling from branch0 into branch2
23 pulling from branch0 into branch2
24 2 changesets found
24 4 changesets found
25 0 c3
25 0 c3
26 pulling from branch2 into branch3
26 pulling from branch2 into branch3
27 3 changesets found
27 5 changesets found
28 pulling from branch1 into branch3
28 pulling from branch1 into branch3
29 1 changesets found
29 1 changesets found
General Comments 0
You need to be logged in to leave comments. Login now