##// END OF EJS Templates
convert: always track the hg source revision in the internal commit object...
Matt Harbison -
r25570:7cc1d33f default
parent child Browse files
Show More
@@ -1,469 +1,470 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import base64, errno, subprocess, os, datetime, re
8 import base64, errno, subprocess, os, datetime, re
9 import cPickle as pickle
9 import cPickle as pickle
10 from mercurial import util
10 from mercurial import util
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 def encodeargs(args):
15 def encodeargs(args):
16 def encodearg(s):
16 def encodearg(s):
17 lines = base64.encodestring(s)
17 lines = base64.encodestring(s)
18 lines = [l.splitlines()[0] for l in lines]
18 lines = [l.splitlines()[0] for l in lines]
19 return ''.join(lines)
19 return ''.join(lines)
20
20
21 s = pickle.dumps(args)
21 s = pickle.dumps(args)
22 return encodearg(s)
22 return encodearg(s)
23
23
24 def decodeargs(s):
24 def decodeargs(s):
25 s = base64.decodestring(s)
25 s = base64.decodestring(s)
26 return pickle.loads(s)
26 return pickle.loads(s)
27
27
28 class MissingTool(Exception):
28 class MissingTool(Exception):
29 pass
29 pass
30
30
31 def checktool(exe, name=None, abort=True):
31 def checktool(exe, name=None, abort=True):
32 name = name or exe
32 name = name or exe
33 if not util.findexe(exe):
33 if not util.findexe(exe):
34 if abort:
34 if abort:
35 exc = util.Abort
35 exc = util.Abort
36 else:
36 else:
37 exc = MissingTool
37 exc = MissingTool
38 raise exc(_('cannot find required "%s" tool') % name)
38 raise exc(_('cannot find required "%s" tool') % name)
39
39
40 class NoRepo(Exception):
40 class NoRepo(Exception):
41 pass
41 pass
42
42
43 SKIPREV = 'SKIP'
43 SKIPREV = 'SKIP'
44
44
45 class commit(object):
45 class commit(object):
46 def __init__(self, author, date, desc, parents, branch=None, rev=None,
46 def __init__(self, author, date, desc, parents, branch=None, rev=None,
47 extra={}, sortkey=None):
47 extra={}, sortkey=None, saverev=True):
48 self.author = author or 'unknown'
48 self.author = author or 'unknown'
49 self.date = date or '0 0'
49 self.date = date or '0 0'
50 self.desc = desc
50 self.desc = desc
51 self.parents = parents
51 self.parents = parents
52 self.branch = branch
52 self.branch = branch
53 self.rev = rev
53 self.rev = rev
54 self.extra = extra
54 self.extra = extra
55 self.sortkey = sortkey
55 self.sortkey = sortkey
56 self.saverev = saverev
56
57
57 class converter_source(object):
58 class converter_source(object):
58 """Conversion source interface"""
59 """Conversion source interface"""
59
60
60 def __init__(self, ui, path=None, rev=None):
61 def __init__(self, ui, path=None, rev=None):
61 """Initialize conversion source (or raise NoRepo("message")
62 """Initialize conversion source (or raise NoRepo("message")
62 exception if path is not a valid repository)"""
63 exception if path is not a valid repository)"""
63 self.ui = ui
64 self.ui = ui
64 self.path = path
65 self.path = path
65 self.rev = rev
66 self.rev = rev
66
67
67 self.encoding = 'utf-8'
68 self.encoding = 'utf-8'
68
69
69 def checkhexformat(self, revstr, mapname='splicemap'):
70 def checkhexformat(self, revstr, mapname='splicemap'):
70 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
71 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
71 such format for their revision numbering
72 such format for their revision numbering
72 """
73 """
73 if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
74 if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
74 raise util.Abort(_('%s entry %s is not a valid revision'
75 raise util.Abort(_('%s entry %s is not a valid revision'
75 ' identifier') % (mapname, revstr))
76 ' identifier') % (mapname, revstr))
76
77
77 def before(self):
78 def before(self):
78 pass
79 pass
79
80
80 def after(self):
81 def after(self):
81 pass
82 pass
82
83
83 def setrevmap(self, revmap):
84 def setrevmap(self, revmap):
84 """set the map of already-converted revisions"""
85 """set the map of already-converted revisions"""
85 pass
86 pass
86
87
87 def getheads(self):
88 def getheads(self):
88 """Return a list of this repository's heads"""
89 """Return a list of this repository's heads"""
89 raise NotImplementedError
90 raise NotImplementedError
90
91
91 def getfile(self, name, rev):
92 def getfile(self, name, rev):
92 """Return a pair (data, mode) where data is the file content
93 """Return a pair (data, mode) where data is the file content
93 as a string and mode one of '', 'x' or 'l'. rev is the
94 as a string and mode one of '', 'x' or 'l'. rev is the
94 identifier returned by a previous call to getchanges().
95 identifier returned by a previous call to getchanges().
95 Data is None if file is missing/deleted in rev.
96 Data is None if file is missing/deleted in rev.
96 """
97 """
97 raise NotImplementedError
98 raise NotImplementedError
98
99
99 def getchanges(self, version, full):
100 def getchanges(self, version, full):
100 """Returns a tuple of (files, copies, cleanp2).
101 """Returns a tuple of (files, copies, cleanp2).
101
102
102 files is a sorted list of (filename, id) tuples for all files
103 files is a sorted list of (filename, id) tuples for all files
103 changed between version and its first parent returned by
104 changed between version and its first parent returned by
104 getcommit(). If full, all files in that revision is returned.
105 getcommit(). If full, all files in that revision is returned.
105 id is the source revision id of the file.
106 id is the source revision id of the file.
106
107
107 copies is a dictionary of dest: source
108 copies is a dictionary of dest: source
108
109
109 cleanp2 is the set of files filenames that are clean against p2.
110 cleanp2 is the set of files filenames that are clean against p2.
110 (Files that are clean against p1 are already not in files (unless
111 (Files that are clean against p1 are already not in files (unless
111 full). This makes it possible to handle p2 clean files similarly.)
112 full). This makes it possible to handle p2 clean files similarly.)
112 """
113 """
113 raise NotImplementedError
114 raise NotImplementedError
114
115
115 def getcommit(self, version):
116 def getcommit(self, version):
116 """Return the commit object for version"""
117 """Return the commit object for version"""
117 raise NotImplementedError
118 raise NotImplementedError
118
119
119 def numcommits(self):
120 def numcommits(self):
120 """Return the number of commits in this source.
121 """Return the number of commits in this source.
121
122
122 If unknown, return None.
123 If unknown, return None.
123 """
124 """
124 return None
125 return None
125
126
126 def gettags(self):
127 def gettags(self):
127 """Return the tags as a dictionary of name: revision
128 """Return the tags as a dictionary of name: revision
128
129
129 Tag names must be UTF-8 strings.
130 Tag names must be UTF-8 strings.
130 """
131 """
131 raise NotImplementedError
132 raise NotImplementedError
132
133
133 def recode(self, s, encoding=None):
134 def recode(self, s, encoding=None):
134 if not encoding:
135 if not encoding:
135 encoding = self.encoding or 'utf-8'
136 encoding = self.encoding or 'utf-8'
136
137
137 if isinstance(s, unicode):
138 if isinstance(s, unicode):
138 return s.encode("utf-8")
139 return s.encode("utf-8")
139 try:
140 try:
140 return s.decode(encoding).encode("utf-8")
141 return s.decode(encoding).encode("utf-8")
141 except UnicodeError:
142 except UnicodeError:
142 try:
143 try:
143 return s.decode("latin-1").encode("utf-8")
144 return s.decode("latin-1").encode("utf-8")
144 except UnicodeError:
145 except UnicodeError:
145 return s.decode(encoding, "replace").encode("utf-8")
146 return s.decode(encoding, "replace").encode("utf-8")
146
147
147 def getchangedfiles(self, rev, i):
148 def getchangedfiles(self, rev, i):
148 """Return the files changed by rev compared to parent[i].
149 """Return the files changed by rev compared to parent[i].
149
150
150 i is an index selecting one of the parents of rev. The return
151 i is an index selecting one of the parents of rev. The return
151 value should be the list of files that are different in rev and
152 value should be the list of files that are different in rev and
152 this parent.
153 this parent.
153
154
154 If rev has no parents, i is None.
155 If rev has no parents, i is None.
155
156
156 This function is only needed to support --filemap
157 This function is only needed to support --filemap
157 """
158 """
158 raise NotImplementedError
159 raise NotImplementedError
159
160
160 def converted(self, rev, sinkrev):
161 def converted(self, rev, sinkrev):
161 '''Notify the source that a revision has been converted.'''
162 '''Notify the source that a revision has been converted.'''
162 pass
163 pass
163
164
164 def hasnativeorder(self):
165 def hasnativeorder(self):
165 """Return true if this source has a meaningful, native revision
166 """Return true if this source has a meaningful, native revision
166 order. For instance, Mercurial revisions are store sequentially
167 order. For instance, Mercurial revisions are store sequentially
167 while there is no such global ordering with Darcs.
168 while there is no such global ordering with Darcs.
168 """
169 """
169 return False
170 return False
170
171
171 def hasnativeclose(self):
172 def hasnativeclose(self):
172 """Return true if this source has ability to close branch.
173 """Return true if this source has ability to close branch.
173 """
174 """
174 return False
175 return False
175
176
176 def lookuprev(self, rev):
177 def lookuprev(self, rev):
177 """If rev is a meaningful revision reference in source, return
178 """If rev is a meaningful revision reference in source, return
178 the referenced identifier in the same format used by getcommit().
179 the referenced identifier in the same format used by getcommit().
179 return None otherwise.
180 return None otherwise.
180 """
181 """
181 return None
182 return None
182
183
183 def getbookmarks(self):
184 def getbookmarks(self):
184 """Return the bookmarks as a dictionary of name: revision
185 """Return the bookmarks as a dictionary of name: revision
185
186
186 Bookmark names are to be UTF-8 strings.
187 Bookmark names are to be UTF-8 strings.
187 """
188 """
188 return {}
189 return {}
189
190
190 def checkrevformat(self, revstr, mapname='splicemap'):
191 def checkrevformat(self, revstr, mapname='splicemap'):
191 """revstr is a string that describes a revision in the given
192 """revstr is a string that describes a revision in the given
192 source control system. Return true if revstr has correct
193 source control system. Return true if revstr has correct
193 format.
194 format.
194 """
195 """
195 return True
196 return True
196
197
197 class converter_sink(object):
198 class converter_sink(object):
198 """Conversion sink (target) interface"""
199 """Conversion sink (target) interface"""
199
200
200 def __init__(self, ui, path):
201 def __init__(self, ui, path):
201 """Initialize conversion sink (or raise NoRepo("message")
202 """Initialize conversion sink (or raise NoRepo("message")
202 exception if path is not a valid repository)
203 exception if path is not a valid repository)
203
204
204 created is a list of paths to remove if a fatal error occurs
205 created is a list of paths to remove if a fatal error occurs
205 later"""
206 later"""
206 self.ui = ui
207 self.ui = ui
207 self.path = path
208 self.path = path
208 self.created = []
209 self.created = []
209
210
210 def revmapfile(self):
211 def revmapfile(self):
211 """Path to a file that will contain lines
212 """Path to a file that will contain lines
212 source_rev_id sink_rev_id
213 source_rev_id sink_rev_id
213 mapping equivalent revision identifiers for each system."""
214 mapping equivalent revision identifiers for each system."""
214 raise NotImplementedError
215 raise NotImplementedError
215
216
216 def authorfile(self):
217 def authorfile(self):
217 """Path to a file that will contain lines
218 """Path to a file that will contain lines
218 srcauthor=dstauthor
219 srcauthor=dstauthor
219 mapping equivalent authors identifiers for each system."""
220 mapping equivalent authors identifiers for each system."""
220 return None
221 return None
221
222
222 def putcommit(self, files, copies, parents, commit, source, revmap, full,
223 def putcommit(self, files, copies, parents, commit, source, revmap, full,
223 cleanp2):
224 cleanp2):
224 """Create a revision with all changed files listed in 'files'
225 """Create a revision with all changed files listed in 'files'
225 and having listed parents. 'commit' is a commit object
226 and having listed parents. 'commit' is a commit object
226 containing at a minimum the author, date, and message for this
227 containing at a minimum the author, date, and message for this
227 changeset. 'files' is a list of (path, version) tuples,
228 changeset. 'files' is a list of (path, version) tuples,
228 'copies' is a dictionary mapping destinations to sources,
229 'copies' is a dictionary mapping destinations to sources,
229 'source' is the source repository, and 'revmap' is a mapfile
230 'source' is the source repository, and 'revmap' is a mapfile
230 of source revisions to converted revisions. Only getfile() and
231 of source revisions to converted revisions. Only getfile() and
231 lookuprev() should be called on 'source'. 'full' means that 'files'
232 lookuprev() should be called on 'source'. 'full' means that 'files'
232 is complete and all other files should be removed.
233 is complete and all other files should be removed.
233 'cleanp2' is a set of the filenames that are unchanged from p2
234 'cleanp2' is a set of the filenames that are unchanged from p2
234 (only in the common merge case where there two parents).
235 (only in the common merge case where there two parents).
235
236
236 Note that the sink repository is not told to update itself to
237 Note that the sink repository is not told to update itself to
237 a particular revision (or even what that revision would be)
238 a particular revision (or even what that revision would be)
238 before it receives the file data.
239 before it receives the file data.
239 """
240 """
240 raise NotImplementedError
241 raise NotImplementedError
241
242
242 def puttags(self, tags):
243 def puttags(self, tags):
243 """Put tags into sink.
244 """Put tags into sink.
244
245
245 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
246 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
246 Return a pair (tag_revision, tag_parent_revision), or (None, None)
247 Return a pair (tag_revision, tag_parent_revision), or (None, None)
247 if nothing was changed.
248 if nothing was changed.
248 """
249 """
249 raise NotImplementedError
250 raise NotImplementedError
250
251
251 def setbranch(self, branch, pbranches):
252 def setbranch(self, branch, pbranches):
252 """Set the current branch name. Called before the first putcommit
253 """Set the current branch name. Called before the first putcommit
253 on the branch.
254 on the branch.
254 branch: branch name for subsequent commits
255 branch: branch name for subsequent commits
255 pbranches: (converted parent revision, parent branch) tuples"""
256 pbranches: (converted parent revision, parent branch) tuples"""
256 pass
257 pass
257
258
258 def setfilemapmode(self, active):
259 def setfilemapmode(self, active):
259 """Tell the destination that we're using a filemap
260 """Tell the destination that we're using a filemap
260
261
261 Some converter_sources (svn in particular) can claim that a file
262 Some converter_sources (svn in particular) can claim that a file
262 was changed in a revision, even if there was no change. This method
263 was changed in a revision, even if there was no change. This method
263 tells the destination that we're using a filemap and that it should
264 tells the destination that we're using a filemap and that it should
264 filter empty revisions.
265 filter empty revisions.
265 """
266 """
266 pass
267 pass
267
268
268 def before(self):
269 def before(self):
269 pass
270 pass
270
271
271 def after(self):
272 def after(self):
272 pass
273 pass
273
274
274 def putbookmarks(self, bookmarks):
275 def putbookmarks(self, bookmarks):
275 """Put bookmarks into sink.
276 """Put bookmarks into sink.
276
277
277 bookmarks: {bookmarkname: sink_rev_id, ...}
278 bookmarks: {bookmarkname: sink_rev_id, ...}
278 where bookmarkname is an UTF-8 string.
279 where bookmarkname is an UTF-8 string.
279 """
280 """
280 pass
281 pass
281
282
282 def hascommitfrommap(self, rev):
283 def hascommitfrommap(self, rev):
283 """Return False if a rev mentioned in a filemap is known to not be
284 """Return False if a rev mentioned in a filemap is known to not be
284 present."""
285 present."""
285 raise NotImplementedError
286 raise NotImplementedError
286
287
287 def hascommitforsplicemap(self, rev):
288 def hascommitforsplicemap(self, rev):
288 """This method is for the special needs for splicemap handling and not
289 """This method is for the special needs for splicemap handling and not
289 for general use. Returns True if the sink contains rev, aborts on some
290 for general use. Returns True if the sink contains rev, aborts on some
290 special cases."""
291 special cases."""
291 raise NotImplementedError
292 raise NotImplementedError
292
293
293 class commandline(object):
294 class commandline(object):
294 def __init__(self, ui, command):
295 def __init__(self, ui, command):
295 self.ui = ui
296 self.ui = ui
296 self.command = command
297 self.command = command
297
298
298 def prerun(self):
299 def prerun(self):
299 pass
300 pass
300
301
301 def postrun(self):
302 def postrun(self):
302 pass
303 pass
303
304
304 def _cmdline(self, cmd, *args, **kwargs):
305 def _cmdline(self, cmd, *args, **kwargs):
305 cmdline = [self.command, cmd] + list(args)
306 cmdline = [self.command, cmd] + list(args)
306 for k, v in kwargs.iteritems():
307 for k, v in kwargs.iteritems():
307 if len(k) == 1:
308 if len(k) == 1:
308 cmdline.append('-' + k)
309 cmdline.append('-' + k)
309 else:
310 else:
310 cmdline.append('--' + k.replace('_', '-'))
311 cmdline.append('--' + k.replace('_', '-'))
311 try:
312 try:
312 if len(k) == 1:
313 if len(k) == 1:
313 cmdline.append('' + v)
314 cmdline.append('' + v)
314 else:
315 else:
315 cmdline[-1] += '=' + v
316 cmdline[-1] += '=' + v
316 except TypeError:
317 except TypeError:
317 pass
318 pass
318 cmdline = [util.shellquote(arg) for arg in cmdline]
319 cmdline = [util.shellquote(arg) for arg in cmdline]
319 if not self.ui.debugflag:
320 if not self.ui.debugflag:
320 cmdline += ['2>', os.devnull]
321 cmdline += ['2>', os.devnull]
321 cmdline = ' '.join(cmdline)
322 cmdline = ' '.join(cmdline)
322 return cmdline
323 return cmdline
323
324
324 def _run(self, cmd, *args, **kwargs):
325 def _run(self, cmd, *args, **kwargs):
325 def popen(cmdline):
326 def popen(cmdline):
326 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
327 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
327 close_fds=util.closefds,
328 close_fds=util.closefds,
328 stdout=subprocess.PIPE)
329 stdout=subprocess.PIPE)
329 return p
330 return p
330 return self._dorun(popen, cmd, *args, **kwargs)
331 return self._dorun(popen, cmd, *args, **kwargs)
331
332
332 def _run2(self, cmd, *args, **kwargs):
333 def _run2(self, cmd, *args, **kwargs):
333 return self._dorun(util.popen2, cmd, *args, **kwargs)
334 return self._dorun(util.popen2, cmd, *args, **kwargs)
334
335
335 def _dorun(self, openfunc, cmd, *args, **kwargs):
336 def _dorun(self, openfunc, cmd, *args, **kwargs):
336 cmdline = self._cmdline(cmd, *args, **kwargs)
337 cmdline = self._cmdline(cmd, *args, **kwargs)
337 self.ui.debug('running: %s\n' % (cmdline,))
338 self.ui.debug('running: %s\n' % (cmdline,))
338 self.prerun()
339 self.prerun()
339 try:
340 try:
340 return openfunc(cmdline)
341 return openfunc(cmdline)
341 finally:
342 finally:
342 self.postrun()
343 self.postrun()
343
344
344 def run(self, cmd, *args, **kwargs):
345 def run(self, cmd, *args, **kwargs):
345 p = self._run(cmd, *args, **kwargs)
346 p = self._run(cmd, *args, **kwargs)
346 output = p.communicate()[0]
347 output = p.communicate()[0]
347 self.ui.debug(output)
348 self.ui.debug(output)
348 return output, p.returncode
349 return output, p.returncode
349
350
350 def runlines(self, cmd, *args, **kwargs):
351 def runlines(self, cmd, *args, **kwargs):
351 p = self._run(cmd, *args, **kwargs)
352 p = self._run(cmd, *args, **kwargs)
352 output = p.stdout.readlines()
353 output = p.stdout.readlines()
353 p.wait()
354 p.wait()
354 self.ui.debug(''.join(output))
355 self.ui.debug(''.join(output))
355 return output, p.returncode
356 return output, p.returncode
356
357
357 def checkexit(self, status, output=''):
358 def checkexit(self, status, output=''):
358 if status:
359 if status:
359 if output:
360 if output:
360 self.ui.warn(_('%s error:\n') % self.command)
361 self.ui.warn(_('%s error:\n') % self.command)
361 self.ui.warn(output)
362 self.ui.warn(output)
362 msg = util.explainexit(status)[0]
363 msg = util.explainexit(status)[0]
363 raise util.Abort('%s %s' % (self.command, msg))
364 raise util.Abort('%s %s' % (self.command, msg))
364
365
365 def run0(self, cmd, *args, **kwargs):
366 def run0(self, cmd, *args, **kwargs):
366 output, status = self.run(cmd, *args, **kwargs)
367 output, status = self.run(cmd, *args, **kwargs)
367 self.checkexit(status, output)
368 self.checkexit(status, output)
368 return output
369 return output
369
370
370 def runlines0(self, cmd, *args, **kwargs):
371 def runlines0(self, cmd, *args, **kwargs):
371 output, status = self.runlines(cmd, *args, **kwargs)
372 output, status = self.runlines(cmd, *args, **kwargs)
372 self.checkexit(status, ''.join(output))
373 self.checkexit(status, ''.join(output))
373 return output
374 return output
374
375
375 @propertycache
376 @propertycache
376 def argmax(self):
377 def argmax(self):
377 # POSIX requires at least 4096 bytes for ARG_MAX
378 # POSIX requires at least 4096 bytes for ARG_MAX
378 argmax = 4096
379 argmax = 4096
379 try:
380 try:
380 argmax = os.sysconf("SC_ARG_MAX")
381 argmax = os.sysconf("SC_ARG_MAX")
381 except (AttributeError, ValueError):
382 except (AttributeError, ValueError):
382 pass
383 pass
383
384
384 # Windows shells impose their own limits on command line length,
385 # Windows shells impose their own limits on command line length,
385 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
386 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
386 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
387 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
387 # details about cmd.exe limitations.
388 # details about cmd.exe limitations.
388
389
389 # Since ARG_MAX is for command line _and_ environment, lower our limit
390 # Since ARG_MAX is for command line _and_ environment, lower our limit
390 # (and make happy Windows shells while doing this).
391 # (and make happy Windows shells while doing this).
391 return argmax // 2 - 1
392 return argmax // 2 - 1
392
393
393 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
394 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
394 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
395 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
395 limit = self.argmax - cmdlen
396 limit = self.argmax - cmdlen
396 bytes = 0
397 bytes = 0
397 fl = []
398 fl = []
398 for fn in arglist:
399 for fn in arglist:
399 b = len(fn) + 3
400 b = len(fn) + 3
400 if bytes + b < limit or len(fl) == 0:
401 if bytes + b < limit or len(fl) == 0:
401 fl.append(fn)
402 fl.append(fn)
402 bytes += b
403 bytes += b
403 else:
404 else:
404 yield fl
405 yield fl
405 fl = [fn]
406 fl = [fn]
406 bytes = b
407 bytes = b
407 if fl:
408 if fl:
408 yield fl
409 yield fl
409
410
410 def xargs(self, arglist, cmd, *args, **kwargs):
411 def xargs(self, arglist, cmd, *args, **kwargs):
411 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
412 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
412 self.run0(cmd, *(list(args) + l), **kwargs)
413 self.run0(cmd, *(list(args) + l), **kwargs)
413
414
414 class mapfile(dict):
415 class mapfile(dict):
415 def __init__(self, ui, path):
416 def __init__(self, ui, path):
416 super(mapfile, self).__init__()
417 super(mapfile, self).__init__()
417 self.ui = ui
418 self.ui = ui
418 self.path = path
419 self.path = path
419 self.fp = None
420 self.fp = None
420 self.order = []
421 self.order = []
421 self._read()
422 self._read()
422
423
423 def _read(self):
424 def _read(self):
424 if not self.path:
425 if not self.path:
425 return
426 return
426 try:
427 try:
427 fp = open(self.path, 'r')
428 fp = open(self.path, 'r')
428 except IOError, err:
429 except IOError, err:
429 if err.errno != errno.ENOENT:
430 if err.errno != errno.ENOENT:
430 raise
431 raise
431 return
432 return
432 for i, line in enumerate(fp):
433 for i, line in enumerate(fp):
433 line = line.splitlines()[0].rstrip()
434 line = line.splitlines()[0].rstrip()
434 if not line:
435 if not line:
435 # Ignore blank lines
436 # Ignore blank lines
436 continue
437 continue
437 try:
438 try:
438 key, value = line.rsplit(' ', 1)
439 key, value = line.rsplit(' ', 1)
439 except ValueError:
440 except ValueError:
440 raise util.Abort(
441 raise util.Abort(
441 _('syntax error in %s(%d): key/value pair expected')
442 _('syntax error in %s(%d): key/value pair expected')
442 % (self.path, i + 1))
443 % (self.path, i + 1))
443 if key not in self:
444 if key not in self:
444 self.order.append(key)
445 self.order.append(key)
445 super(mapfile, self).__setitem__(key, value)
446 super(mapfile, self).__setitem__(key, value)
446 fp.close()
447 fp.close()
447
448
448 def __setitem__(self, key, value):
449 def __setitem__(self, key, value):
449 if self.fp is None:
450 if self.fp is None:
450 try:
451 try:
451 self.fp = open(self.path, 'a')
452 self.fp = open(self.path, 'a')
452 except IOError, err:
453 except IOError, err:
453 raise util.Abort(_('could not open map file %r: %s') %
454 raise util.Abort(_('could not open map file %r: %s') %
454 (self.path, err.strerror))
455 (self.path, err.strerror))
455 self.fp.write('%s %s\n' % (key, value))
456 self.fp.write('%s %s\n' % (key, value))
456 self.fp.flush()
457 self.fp.flush()
457 super(mapfile, self).__setitem__(key, value)
458 super(mapfile, self).__setitem__(key, value)
458
459
459 def close(self):
460 def close(self):
460 if self.fp:
461 if self.fp:
461 self.fp.close()
462 self.fp.close()
462 self.fp = None
463 self.fp = None
463
464
464 def makedatetimestamp(t):
465 def makedatetimestamp(t):
465 """Like util.makedate() but for time t instead of current time"""
466 """Like util.makedate() but for time t instead of current time"""
466 delta = (datetime.datetime.utcfromtimestamp(t) -
467 delta = (datetime.datetime.utcfromtimestamp(t) -
467 datetime.datetime.fromtimestamp(t))
468 datetime.datetime.fromtimestamp(t))
468 tz = delta.days * 86400 + delta.seconds
469 tz = delta.days * 86400 + delta.seconds
469 return t, tz
470 return t, tz
@@ -1,538 +1,536 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 or any later version.
6 # GNU General Public License version 2 or any later version.
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, bookmarks, error, scmutil, exchange
24 from mercurial import hg, util, context, bookmarks, error, scmutil, exchange
25
25
26 from common import NoRepo, commit, converter_source, converter_sink, mapfile
26 from common import NoRepo, commit, converter_source, converter_sink, mapfile
27
27
28 import re
28 import re
29 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
29 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
30
30
31 class mercurial_sink(converter_sink):
31 class mercurial_sink(converter_sink):
32 def __init__(self, ui, path):
32 def __init__(self, ui, path):
33 converter_sink.__init__(self, ui, path)
33 converter_sink.__init__(self, ui, path)
34 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
34 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
35 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
35 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
36 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
36 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
37 self.lastbranch = None
37 self.lastbranch = None
38 if os.path.isdir(path) and len(os.listdir(path)) > 0:
38 if os.path.isdir(path) and len(os.listdir(path)) > 0:
39 try:
39 try:
40 self.repo = hg.repository(self.ui, path)
40 self.repo = hg.repository(self.ui, path)
41 if not self.repo.local():
41 if not self.repo.local():
42 raise NoRepo(_('%s is not a local Mercurial repository')
42 raise NoRepo(_('%s is not a local Mercurial repository')
43 % path)
43 % path)
44 except error.RepoError, err:
44 except error.RepoError, err:
45 ui.traceback()
45 ui.traceback()
46 raise NoRepo(err.args[0])
46 raise NoRepo(err.args[0])
47 else:
47 else:
48 try:
48 try:
49 ui.status(_('initializing destination %s repository\n') % path)
49 ui.status(_('initializing destination %s repository\n') % path)
50 self.repo = hg.repository(self.ui, path, create=True)
50 self.repo = hg.repository(self.ui, path, create=True)
51 if not self.repo.local():
51 if not self.repo.local():
52 raise NoRepo(_('%s is not a local Mercurial repository')
52 raise NoRepo(_('%s is not a local Mercurial repository')
53 % path)
53 % path)
54 self.created.append(path)
54 self.created.append(path)
55 except error.RepoError:
55 except error.RepoError:
56 ui.traceback()
56 ui.traceback()
57 raise NoRepo(_("could not create hg repository %s as sink")
57 raise NoRepo(_("could not create hg repository %s as sink")
58 % path)
58 % path)
59 self.lock = None
59 self.lock = None
60 self.wlock = None
60 self.wlock = None
61 self.filemapmode = False
61 self.filemapmode = False
62 self.subrevmaps = {}
62 self.subrevmaps = {}
63
63
64 def before(self):
64 def before(self):
65 self.ui.debug('run hg sink pre-conversion action\n')
65 self.ui.debug('run hg sink pre-conversion action\n')
66 self.wlock = self.repo.wlock()
66 self.wlock = self.repo.wlock()
67 self.lock = self.repo.lock()
67 self.lock = self.repo.lock()
68
68
69 def after(self):
69 def after(self):
70 self.ui.debug('run hg sink post-conversion action\n')
70 self.ui.debug('run hg sink post-conversion action\n')
71 if self.lock:
71 if self.lock:
72 self.lock.release()
72 self.lock.release()
73 if self.wlock:
73 if self.wlock:
74 self.wlock.release()
74 self.wlock.release()
75
75
76 def revmapfile(self):
76 def revmapfile(self):
77 return self.repo.join("shamap")
77 return self.repo.join("shamap")
78
78
79 def authorfile(self):
79 def authorfile(self):
80 return self.repo.join("authormap")
80 return self.repo.join("authormap")
81
81
82 def setbranch(self, branch, pbranches):
82 def setbranch(self, branch, pbranches):
83 if not self.clonebranches:
83 if not self.clonebranches:
84 return
84 return
85
85
86 setbranch = (branch != self.lastbranch)
86 setbranch = (branch != self.lastbranch)
87 self.lastbranch = branch
87 self.lastbranch = branch
88 if not branch:
88 if not branch:
89 branch = 'default'
89 branch = 'default'
90 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
90 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
91 if pbranches:
91 if pbranches:
92 pbranch = pbranches[0][1]
92 pbranch = pbranches[0][1]
93 else:
93 else:
94 pbranch = 'default'
94 pbranch = 'default'
95
95
96 branchpath = os.path.join(self.path, branch)
96 branchpath = os.path.join(self.path, branch)
97 if setbranch:
97 if setbranch:
98 self.after()
98 self.after()
99 try:
99 try:
100 self.repo = hg.repository(self.ui, branchpath)
100 self.repo = hg.repository(self.ui, branchpath)
101 except Exception:
101 except Exception:
102 self.repo = hg.repository(self.ui, branchpath, create=True)
102 self.repo = hg.repository(self.ui, branchpath, create=True)
103 self.before()
103 self.before()
104
104
105 # pbranches may bring revisions from other branches (merge parents)
105 # pbranches may bring revisions from other branches (merge parents)
106 # Make sure we have them, or pull them.
106 # Make sure we have them, or pull them.
107 missings = {}
107 missings = {}
108 for b in pbranches:
108 for b in pbranches:
109 try:
109 try:
110 self.repo.lookup(b[0])
110 self.repo.lookup(b[0])
111 except Exception:
111 except Exception:
112 missings.setdefault(b[1], []).append(b[0])
112 missings.setdefault(b[1], []).append(b[0])
113
113
114 if missings:
114 if missings:
115 self.after()
115 self.after()
116 for pbranch, heads in sorted(missings.iteritems()):
116 for pbranch, heads in sorted(missings.iteritems()):
117 pbranchpath = os.path.join(self.path, pbranch)
117 pbranchpath = os.path.join(self.path, pbranch)
118 prepo = hg.peer(self.ui, {}, pbranchpath)
118 prepo = hg.peer(self.ui, {}, pbranchpath)
119 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
119 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
120 exchange.pull(self.repo, prepo,
120 exchange.pull(self.repo, prepo,
121 [prepo.lookup(h) for h in heads])
121 [prepo.lookup(h) for h in heads])
122 self.before()
122 self.before()
123
123
124 def _rewritetags(self, source, revmap, data):
124 def _rewritetags(self, source, revmap, data):
125 fp = cStringIO.StringIO()
125 fp = cStringIO.StringIO()
126 for line in data.splitlines():
126 for line in data.splitlines():
127 s = line.split(' ', 1)
127 s = line.split(' ', 1)
128 if len(s) != 2:
128 if len(s) != 2:
129 continue
129 continue
130 revid = revmap.get(source.lookuprev(s[0]))
130 revid = revmap.get(source.lookuprev(s[0]))
131 if not revid:
131 if not revid:
132 if s[0] == hex(nullid):
132 if s[0] == hex(nullid):
133 revid = s[0]
133 revid = s[0]
134 else:
134 else:
135 continue
135 continue
136 fp.write('%s %s\n' % (revid, s[1]))
136 fp.write('%s %s\n' % (revid, s[1]))
137 return fp.getvalue()
137 return fp.getvalue()
138
138
139 def _rewritesubstate(self, source, data):
139 def _rewritesubstate(self, source, data):
140 fp = cStringIO.StringIO()
140 fp = cStringIO.StringIO()
141 for line in data.splitlines():
141 for line in data.splitlines():
142 s = line.split(' ', 1)
142 s = line.split(' ', 1)
143 if len(s) != 2:
143 if len(s) != 2:
144 continue
144 continue
145
145
146 revid = s[0]
146 revid = s[0]
147 subpath = s[1]
147 subpath = s[1]
148 if revid != hex(nullid):
148 if revid != hex(nullid):
149 revmap = self.subrevmaps.get(subpath)
149 revmap = self.subrevmaps.get(subpath)
150 if revmap is None:
150 if revmap is None:
151 revmap = mapfile(self.ui,
151 revmap = mapfile(self.ui,
152 self.repo.wjoin(subpath, '.hg/shamap'))
152 self.repo.wjoin(subpath, '.hg/shamap'))
153 self.subrevmaps[subpath] = revmap
153 self.subrevmaps[subpath] = revmap
154
154
155 # It is reasonable that one or more of the subrepos don't
155 # It is reasonable that one or more of the subrepos don't
156 # need to be converted, in which case they can be cloned
156 # need to be converted, in which case they can be cloned
157 # into place instead of converted. Therefore, only warn
157 # into place instead of converted. Therefore, only warn
158 # once.
158 # once.
159 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
159 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
160 if len(revmap) == 0:
160 if len(revmap) == 0:
161 sub = self.repo.wvfs.reljoin(subpath, '.hg')
161 sub = self.repo.wvfs.reljoin(subpath, '.hg')
162
162
163 if self.repo.wvfs.exists(sub):
163 if self.repo.wvfs.exists(sub):
164 self.ui.warn(msg % subpath)
164 self.ui.warn(msg % subpath)
165
165
166 newid = revmap.get(revid)
166 newid = revmap.get(revid)
167 if not newid:
167 if not newid:
168 if len(revmap) > 0:
168 if len(revmap) > 0:
169 self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
169 self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
170 (revid, subpath))
170 (revid, subpath))
171 else:
171 else:
172 revid = newid
172 revid = newid
173
173
174 fp.write('%s %s\n' % (revid, subpath))
174 fp.write('%s %s\n' % (revid, subpath))
175
175
176 return fp.getvalue()
176 return fp.getvalue()
177
177
178 def putcommit(self, files, copies, parents, commit, source, revmap, full,
178 def putcommit(self, files, copies, parents, commit, source, revmap, full,
179 cleanp2):
179 cleanp2):
180 files = dict(files)
180 files = dict(files)
181
181
182 def getfilectx(repo, memctx, f):
182 def getfilectx(repo, memctx, f):
183 if p2ctx and f in cleanp2 and f not in copies:
183 if p2ctx and f in cleanp2 and f not in copies:
184 self.ui.debug('reusing %s from p2\n' % f)
184 self.ui.debug('reusing %s from p2\n' % f)
185 return p2ctx[f]
185 return p2ctx[f]
186 try:
186 try:
187 v = files[f]
187 v = files[f]
188 except KeyError:
188 except KeyError:
189 return None
189 return None
190 data, mode = source.getfile(f, v)
190 data, mode = source.getfile(f, v)
191 if data is None:
191 if data is None:
192 return None
192 return None
193 if f == '.hgtags':
193 if f == '.hgtags':
194 data = self._rewritetags(source, revmap, data)
194 data = self._rewritetags(source, revmap, data)
195 if f == '.hgsubstate':
195 if f == '.hgsubstate':
196 data = self._rewritesubstate(source, data)
196 data = self._rewritesubstate(source, data)
197 return context.memfilectx(self.repo, f, data, 'l' in mode,
197 return context.memfilectx(self.repo, f, data, 'l' in mode,
198 'x' in mode, copies.get(f))
198 'x' in mode, copies.get(f))
199
199
200 pl = []
200 pl = []
201 for p in parents:
201 for p in parents:
202 if p not in pl:
202 if p not in pl:
203 pl.append(p)
203 pl.append(p)
204 parents = pl
204 parents = pl
205 nparents = len(parents)
205 nparents = len(parents)
206 if self.filemapmode and nparents == 1:
206 if self.filemapmode and nparents == 1:
207 m1node = self.repo.changelog.read(bin(parents[0]))[0]
207 m1node = self.repo.changelog.read(bin(parents[0]))[0]
208 parent = parents[0]
208 parent = parents[0]
209
209
210 if len(parents) < 2:
210 if len(parents) < 2:
211 parents.append(nullid)
211 parents.append(nullid)
212 if len(parents) < 2:
212 if len(parents) < 2:
213 parents.append(nullid)
213 parents.append(nullid)
214 p2 = parents.pop(0)
214 p2 = parents.pop(0)
215
215
216 text = commit.desc
216 text = commit.desc
217
217
218 sha1s = re.findall(sha1re, text)
218 sha1s = re.findall(sha1re, text)
219 for sha1 in sha1s:
219 for sha1 in sha1s:
220 oldrev = source.lookuprev(sha1)
220 oldrev = source.lookuprev(sha1)
221 newrev = revmap.get(oldrev)
221 newrev = revmap.get(oldrev)
222 if newrev is not None:
222 if newrev is not None:
223 text = text.replace(sha1, newrev[:len(sha1)])
223 text = text.replace(sha1, newrev[:len(sha1)])
224
224
225 extra = commit.extra.copy()
225 extra = commit.extra.copy()
226
226
227 for label in ('source', 'transplant_source', 'rebase_source'):
227 for label in ('source', 'transplant_source', 'rebase_source'):
228 node = extra.get(label)
228 node = extra.get(label)
229
229
230 if node is None:
230 if node is None:
231 continue
231 continue
232
232
233 # Only transplant stores its reference in binary
233 # Only transplant stores its reference in binary
234 if label == 'transplant_source':
234 if label == 'transplant_source':
235 node = hex(node)
235 node = hex(node)
236
236
237 newrev = revmap.get(node)
237 newrev = revmap.get(node)
238 if newrev is not None:
238 if newrev is not None:
239 if label == 'transplant_source':
239 if label == 'transplant_source':
240 newrev = bin(newrev)
240 newrev = bin(newrev)
241
241
242 extra[label] = newrev
242 extra[label] = newrev
243
243
244 if self.branchnames and commit.branch:
244 if self.branchnames and commit.branch:
245 extra['branch'] = commit.branch
245 extra['branch'] = commit.branch
246 if commit.rev:
246 if commit.rev and commit.saverev:
247 extra['convert_revision'] = commit.rev
247 extra['convert_revision'] = commit.rev
248
248
249 while parents:
249 while parents:
250 p1 = p2
250 p1 = p2
251 p2 = parents.pop(0)
251 p2 = parents.pop(0)
252 p2ctx = None
252 p2ctx = None
253 if p2 != nullid:
253 if p2 != nullid:
254 p2ctx = self.repo[p2]
254 p2ctx = self.repo[p2]
255 fileset = set(files)
255 fileset = set(files)
256 if full:
256 if full:
257 fileset.update(self.repo[p1])
257 fileset.update(self.repo[p1])
258 fileset.update(self.repo[p2])
258 fileset.update(self.repo[p2])
259 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
259 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
260 getfilectx, commit.author, commit.date, extra)
260 getfilectx, commit.author, commit.date, extra)
261 self.repo.commitctx(ctx)
261 self.repo.commitctx(ctx)
262 text = "(octopus merge fixup)\n"
262 text = "(octopus merge fixup)\n"
263 p2 = hex(self.repo.changelog.tip())
263 p2 = hex(self.repo.changelog.tip())
264
264
265 if self.filemapmode and nparents == 1:
265 if self.filemapmode and nparents == 1:
266 man = self.repo.manifest
266 man = self.repo.manifest
267 mnode = self.repo.changelog.read(bin(p2))[0]
267 mnode = self.repo.changelog.read(bin(p2))[0]
268 closed = 'close' in commit.extra
268 closed = 'close' in commit.extra
269 if not closed and not man.cmp(m1node, man.revision(mnode)):
269 if not closed and not man.cmp(m1node, man.revision(mnode)):
270 self.ui.status(_("filtering out empty revision\n"))
270 self.ui.status(_("filtering out empty revision\n"))
271 self.repo.rollback(force=True)
271 self.repo.rollback(force=True)
272 return parent
272 return parent
273 return p2
273 return p2
274
274
275 def puttags(self, tags):
275 def puttags(self, tags):
276 try:
276 try:
277 parentctx = self.repo[self.tagsbranch]
277 parentctx = self.repo[self.tagsbranch]
278 tagparent = parentctx.node()
278 tagparent = parentctx.node()
279 except error.RepoError:
279 except error.RepoError:
280 parentctx = None
280 parentctx = None
281 tagparent = nullid
281 tagparent = nullid
282
282
283 oldlines = set()
283 oldlines = set()
284 for branch, heads in self.repo.branchmap().iteritems():
284 for branch, heads in self.repo.branchmap().iteritems():
285 for h in heads:
285 for h in heads:
286 if '.hgtags' in self.repo[h]:
286 if '.hgtags' in self.repo[h]:
287 oldlines.update(
287 oldlines.update(
288 set(self.repo[h]['.hgtags'].data().splitlines(True)))
288 set(self.repo[h]['.hgtags'].data().splitlines(True)))
289 oldlines = sorted(list(oldlines))
289 oldlines = sorted(list(oldlines))
290
290
291 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
291 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
292 if newlines == oldlines:
292 if newlines == oldlines:
293 return None, None
293 return None, None
294
294
295 # if the old and new tags match, then there is nothing to update
295 # if the old and new tags match, then there is nothing to update
296 oldtags = set()
296 oldtags = set()
297 newtags = set()
297 newtags = set()
298 for line in oldlines:
298 for line in oldlines:
299 s = line.strip().split(' ', 1)
299 s = line.strip().split(' ', 1)
300 if len(s) != 2:
300 if len(s) != 2:
301 continue
301 continue
302 oldtags.add(s[1])
302 oldtags.add(s[1])
303 for line in newlines:
303 for line in newlines:
304 s = line.strip().split(' ', 1)
304 s = line.strip().split(' ', 1)
305 if len(s) != 2:
305 if len(s) != 2:
306 continue
306 continue
307 if s[1] not in oldtags:
307 if s[1] not in oldtags:
308 newtags.add(s[1].strip())
308 newtags.add(s[1].strip())
309
309
310 if not newtags:
310 if not newtags:
311 return None, None
311 return None, None
312
312
313 data = "".join(newlines)
313 data = "".join(newlines)
314 def getfilectx(repo, memctx, f):
314 def getfilectx(repo, memctx, f):
315 return context.memfilectx(repo, f, data, False, False, None)
315 return context.memfilectx(repo, f, data, False, False, None)
316
316
317 self.ui.status(_("updating tags\n"))
317 self.ui.status(_("updating tags\n"))
318 date = "%s 0" % int(time.mktime(time.gmtime()))
318 date = "%s 0" % int(time.mktime(time.gmtime()))
319 extra = {'branch': self.tagsbranch}
319 extra = {'branch': self.tagsbranch}
320 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
320 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
321 [".hgtags"], getfilectx, "convert-repo", date,
321 [".hgtags"], getfilectx, "convert-repo", date,
322 extra)
322 extra)
323 self.repo.commitctx(ctx)
323 self.repo.commitctx(ctx)
324 return hex(self.repo.changelog.tip()), hex(tagparent)
324 return hex(self.repo.changelog.tip()), hex(tagparent)
325
325
326 def setfilemapmode(self, active):
326 def setfilemapmode(self, active):
327 self.filemapmode = active
327 self.filemapmode = active
328
328
329 def putbookmarks(self, updatedbookmark):
329 def putbookmarks(self, updatedbookmark):
330 if not len(updatedbookmark):
330 if not len(updatedbookmark):
331 return
331 return
332
332
333 self.ui.status(_("updating bookmarks\n"))
333 self.ui.status(_("updating bookmarks\n"))
334 destmarks = self.repo._bookmarks
334 destmarks = self.repo._bookmarks
335 for bookmark in updatedbookmark:
335 for bookmark in updatedbookmark:
336 destmarks[bookmark] = bin(updatedbookmark[bookmark])
336 destmarks[bookmark] = bin(updatedbookmark[bookmark])
337 destmarks.write()
337 destmarks.write()
338
338
339 def hascommitfrommap(self, rev):
339 def hascommitfrommap(self, rev):
340 # the exact semantics of clonebranches is unclear so we can't say no
340 # the exact semantics of clonebranches is unclear so we can't say no
341 return rev in self.repo or self.clonebranches
341 return rev in self.repo or self.clonebranches
342
342
343 def hascommitforsplicemap(self, rev):
343 def hascommitforsplicemap(self, rev):
344 if rev not in self.repo and self.clonebranches:
344 if rev not in self.repo and self.clonebranches:
345 raise util.Abort(_('revision %s not found in destination '
345 raise util.Abort(_('revision %s not found in destination '
346 'repository (lookups with clonebranches=true '
346 'repository (lookups with clonebranches=true '
347 'are not implemented)') % rev)
347 'are not implemented)') % rev)
348 return rev in self.repo
348 return rev in self.repo
349
349
350 class mercurial_source(converter_source):
350 class mercurial_source(converter_source):
351 def __init__(self, ui, path, rev=None):
351 def __init__(self, ui, path, rev=None):
352 converter_source.__init__(self, ui, path, rev)
352 converter_source.__init__(self, ui, path, rev)
353 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
353 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
354 self.ignored = set()
354 self.ignored = set()
355 self.saverev = ui.configbool('convert', 'hg.saverev', False)
355 self.saverev = ui.configbool('convert', 'hg.saverev', False)
356 try:
356 try:
357 self.repo = hg.repository(self.ui, path)
357 self.repo = hg.repository(self.ui, path)
358 # try to provoke an exception if this isn't really a hg
358 # try to provoke an exception if this isn't really a hg
359 # repo, but some other bogus compatible-looking url
359 # repo, but some other bogus compatible-looking url
360 if not self.repo.local():
360 if not self.repo.local():
361 raise error.RepoError
361 raise error.RepoError
362 except error.RepoError:
362 except error.RepoError:
363 ui.traceback()
363 ui.traceback()
364 raise NoRepo(_("%s is not a local Mercurial repository") % path)
364 raise NoRepo(_("%s is not a local Mercurial repository") % path)
365 self.lastrev = None
365 self.lastrev = None
366 self.lastctx = None
366 self.lastctx = None
367 self._changescache = None, None
367 self._changescache = None, None
368 self.convertfp = None
368 self.convertfp = None
369 # Restrict converted revisions to startrev descendants
369 # Restrict converted revisions to startrev descendants
370 startnode = ui.config('convert', 'hg.startrev')
370 startnode = ui.config('convert', 'hg.startrev')
371 hgrevs = ui.config('convert', 'hg.revs')
371 hgrevs = ui.config('convert', 'hg.revs')
372 if hgrevs is None:
372 if hgrevs is None:
373 if startnode is not None:
373 if startnode is not None:
374 try:
374 try:
375 startnode = self.repo.lookup(startnode)
375 startnode = self.repo.lookup(startnode)
376 except error.RepoError:
376 except error.RepoError:
377 raise util.Abort(_('%s is not a valid start revision')
377 raise util.Abort(_('%s is not a valid start revision')
378 % startnode)
378 % startnode)
379 startrev = self.repo.changelog.rev(startnode)
379 startrev = self.repo.changelog.rev(startnode)
380 children = {startnode: 1}
380 children = {startnode: 1}
381 for r in self.repo.changelog.descendants([startrev]):
381 for r in self.repo.changelog.descendants([startrev]):
382 children[self.repo.changelog.node(r)] = 1
382 children[self.repo.changelog.node(r)] = 1
383 self.keep = children.__contains__
383 self.keep = children.__contains__
384 else:
384 else:
385 self.keep = util.always
385 self.keep = util.always
386 if rev:
386 if rev:
387 self._heads = [self.repo[rev].node()]
387 self._heads = [self.repo[rev].node()]
388 else:
388 else:
389 self._heads = self.repo.heads()
389 self._heads = self.repo.heads()
390 else:
390 else:
391 if rev or startnode is not None:
391 if rev or startnode is not None:
392 raise util.Abort(_('hg.revs cannot be combined with '
392 raise util.Abort(_('hg.revs cannot be combined with '
393 'hg.startrev or --rev'))
393 'hg.startrev or --rev'))
394 nodes = set()
394 nodes = set()
395 parents = set()
395 parents = set()
396 for r in scmutil.revrange(self.repo, [hgrevs]):
396 for r in scmutil.revrange(self.repo, [hgrevs]):
397 ctx = self.repo[r]
397 ctx = self.repo[r]
398 nodes.add(ctx.node())
398 nodes.add(ctx.node())
399 parents.update(p.node() for p in ctx.parents())
399 parents.update(p.node() for p in ctx.parents())
400 self.keep = nodes.__contains__
400 self.keep = nodes.__contains__
401 self._heads = nodes - parents
401 self._heads = nodes - parents
402
402
403 def changectx(self, rev):
403 def changectx(self, rev):
404 if self.lastrev != rev:
404 if self.lastrev != rev:
405 self.lastctx = self.repo[rev]
405 self.lastctx = self.repo[rev]
406 self.lastrev = rev
406 self.lastrev = rev
407 return self.lastctx
407 return self.lastctx
408
408
409 def parents(self, ctx):
409 def parents(self, ctx):
410 return [p for p in ctx.parents() if p and self.keep(p.node())]
410 return [p for p in ctx.parents() if p and self.keep(p.node())]
411
411
412 def getheads(self):
412 def getheads(self):
413 return [hex(h) for h in self._heads if self.keep(h)]
413 return [hex(h) for h in self._heads if self.keep(h)]
414
414
415 def getfile(self, name, rev):
415 def getfile(self, name, rev):
416 try:
416 try:
417 fctx = self.changectx(rev)[name]
417 fctx = self.changectx(rev)[name]
418 return fctx.data(), fctx.flags()
418 return fctx.data(), fctx.flags()
419 except error.LookupError:
419 except error.LookupError:
420 return None, None
420 return None, None
421
421
422 def getchanges(self, rev, full):
422 def getchanges(self, rev, full):
423 ctx = self.changectx(rev)
423 ctx = self.changectx(rev)
424 parents = self.parents(ctx)
424 parents = self.parents(ctx)
425 if full or not parents:
425 if full or not parents:
426 files = copyfiles = ctx.manifest()
426 files = copyfiles = ctx.manifest()
427 if parents:
427 if parents:
428 if self._changescache[0] == rev:
428 if self._changescache[0] == rev:
429 m, a, r = self._changescache[1]
429 m, a, r = self._changescache[1]
430 else:
430 else:
431 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
431 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
432 if not full:
432 if not full:
433 files = m + a + r
433 files = m + a + r
434 copyfiles = m + a
434 copyfiles = m + a
435 # getcopies() is also run for roots and before filtering so missing
435 # getcopies() is also run for roots and before filtering so missing
436 # revlogs are detected early
436 # revlogs are detected early
437 copies = self.getcopies(ctx, parents, copyfiles)
437 copies = self.getcopies(ctx, parents, copyfiles)
438 cleanp2 = set()
438 cleanp2 = set()
439 if len(parents) == 2:
439 if len(parents) == 2:
440 cleanp2.update(self.repo.status(parents[1].node(), ctx.node(),
440 cleanp2.update(self.repo.status(parents[1].node(), ctx.node(),
441 clean=True).clean)
441 clean=True).clean)
442 changes = [(f, rev) for f in files if f not in self.ignored]
442 changes = [(f, rev) for f in files if f not in self.ignored]
443 changes.sort()
443 changes.sort()
444 return changes, copies, cleanp2
444 return changes, copies, cleanp2
445
445
446 def getcopies(self, ctx, parents, files):
446 def getcopies(self, ctx, parents, files):
447 copies = {}
447 copies = {}
448 for name in files:
448 for name in files:
449 if name in self.ignored:
449 if name in self.ignored:
450 continue
450 continue
451 try:
451 try:
452 copysource, _copynode = ctx.filectx(name).renamed()
452 copysource, _copynode = ctx.filectx(name).renamed()
453 if copysource in self.ignored:
453 if copysource in self.ignored:
454 continue
454 continue
455 # Ignore copy sources not in parent revisions
455 # Ignore copy sources not in parent revisions
456 found = False
456 found = False
457 for p in parents:
457 for p in parents:
458 if copysource in p:
458 if copysource in p:
459 found = True
459 found = True
460 break
460 break
461 if not found:
461 if not found:
462 continue
462 continue
463 copies[name] = copysource
463 copies[name] = copysource
464 except TypeError:
464 except TypeError:
465 pass
465 pass
466 except error.LookupError, e:
466 except error.LookupError, e:
467 if not self.ignoreerrors:
467 if not self.ignoreerrors:
468 raise
468 raise
469 self.ignored.add(name)
469 self.ignored.add(name)
470 self.ui.warn(_('ignoring: %s\n') % e)
470 self.ui.warn(_('ignoring: %s\n') % e)
471 return copies
471 return copies
472
472
473 def getcommit(self, rev):
473 def getcommit(self, rev):
474 ctx = self.changectx(rev)
474 ctx = self.changectx(rev)
475 parents = [p.hex() for p in self.parents(ctx)]
475 parents = [p.hex() for p in self.parents(ctx)]
476 if self.saverev:
476 crev = rev
477 crev = rev
477
478 else:
479 crev = None
480 return commit(author=ctx.user(),
478 return commit(author=ctx.user(),
481 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
479 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
482 desc=ctx.description(), rev=crev, parents=parents,
480 desc=ctx.description(), rev=crev, parents=parents,
483 branch=ctx.branch(), extra=ctx.extra(),
481 branch=ctx.branch(), extra=ctx.extra(),
484 sortkey=ctx.rev())
482 sortkey=ctx.rev(), saverev=self.saverev)
485
483
486 def gettags(self):
484 def gettags(self):
487 # This will get written to .hgtags, filter non global tags out.
485 # This will get written to .hgtags, filter non global tags out.
488 tags = [t for t in self.repo.tagslist()
486 tags = [t for t in self.repo.tagslist()
489 if self.repo.tagtype(t[0]) == 'global']
487 if self.repo.tagtype(t[0]) == 'global']
490 return dict([(name, hex(node)) for name, node in tags
488 return dict([(name, hex(node)) for name, node in tags
491 if self.keep(node)])
489 if self.keep(node)])
492
490
493 def getchangedfiles(self, rev, i):
491 def getchangedfiles(self, rev, i):
494 ctx = self.changectx(rev)
492 ctx = self.changectx(rev)
495 parents = self.parents(ctx)
493 parents = self.parents(ctx)
496 if not parents and i is None:
494 if not parents and i is None:
497 i = 0
495 i = 0
498 changes = [], ctx.manifest().keys(), []
496 changes = [], ctx.manifest().keys(), []
499 else:
497 else:
500 i = i or 0
498 i = i or 0
501 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
499 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
502 changes = [[f for f in l if f not in self.ignored] for l in changes]
500 changes = [[f for f in l if f not in self.ignored] for l in changes]
503
501
504 if i == 0:
502 if i == 0:
505 self._changescache = (rev, changes)
503 self._changescache = (rev, changes)
506
504
507 return changes[0] + changes[1] + changes[2]
505 return changes[0] + changes[1] + changes[2]
508
506
509 def converted(self, rev, destrev):
507 def converted(self, rev, destrev):
510 if self.convertfp is None:
508 if self.convertfp is None:
511 self.convertfp = open(self.repo.join('shamap'), 'a')
509 self.convertfp = open(self.repo.join('shamap'), 'a')
512 self.convertfp.write('%s %s\n' % (destrev, rev))
510 self.convertfp.write('%s %s\n' % (destrev, rev))
513 self.convertfp.flush()
511 self.convertfp.flush()
514
512
515 def before(self):
513 def before(self):
516 self.ui.debug('run hg source pre-conversion action\n')
514 self.ui.debug('run hg source pre-conversion action\n')
517
515
518 def after(self):
516 def after(self):
519 self.ui.debug('run hg source post-conversion action\n')
517 self.ui.debug('run hg source post-conversion action\n')
520
518
521 def hasnativeorder(self):
519 def hasnativeorder(self):
522 return True
520 return True
523
521
524 def hasnativeclose(self):
522 def hasnativeclose(self):
525 return True
523 return True
526
524
527 def lookuprev(self, rev):
525 def lookuprev(self, rev):
528 try:
526 try:
529 return hex(self.repo.lookup(rev))
527 return hex(self.repo.lookup(rev))
530 except (error.RepoError, error.LookupError):
528 except (error.RepoError, error.LookupError):
531 return None
529 return None
532
530
533 def getbookmarks(self):
531 def getbookmarks(self):
534 return bookmarks.listbookmarks(self.repo)
532 return bookmarks.listbookmarks(self.repo)
535
533
536 def checkrevformat(self, revstr, mapname='splicemap'):
534 def checkrevformat(self, revstr, mapname='splicemap'):
537 """ Mercurial, revision string is a 40 byte hex """
535 """ Mercurial, revision string is a 40 byte hex """
538 self.checkhexformat(revstr, mapname)
536 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now