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