50 import svn.core |
50 import svn.core |
51 import svn.ra |
51 import svn.ra |
52 import svn.delta |
52 import svn.delta |
53 from . import transport |
53 from . import transport |
54 import warnings |
54 import warnings |
55 warnings.filterwarnings('ignore', |
55 |
56 module='svn.core', |
56 warnings.filterwarnings( |
57 category=DeprecationWarning) |
57 'ignore', module='svn.core', category=DeprecationWarning |
58 svn.core.SubversionException # trigger import to catch error |
58 ) |
|
59 svn.core.SubversionException # trigger import to catch error |
59 |
60 |
60 except ImportError: |
61 except ImportError: |
61 svn = None |
62 svn = None |
62 |
63 |
|
64 |
63 class SvnPathNotFound(Exception): |
65 class SvnPathNotFound(Exception): |
64 pass |
66 pass |
|
67 |
65 |
68 |
66 def revsplit(rev): |
69 def revsplit(rev): |
67 """Parse a revision string and return (uuid, path, revnum). |
70 """Parse a revision string and return (uuid, path, revnum). |
68 >>> revsplit(b'svn:a2147622-4a9f-4db4-a8d3-13562ff547b2' |
71 >>> revsplit(b'svn:a2147622-4a9f-4db4-a8d3-13562ff547b2' |
69 ... b'/proj%20B/mytrunk/mytrunk@1') |
72 ... b'/proj%20B/mytrunk/mytrunk@1') |
87 if len(parts) > 1 and parts[0].startswith('svn:'): |
90 if len(parts) > 1 and parts[0].startswith('svn:'): |
88 uuid = parts[0][4:] |
91 uuid = parts[0][4:] |
89 mod = '/' + parts[1] |
92 mod = '/' + parts[1] |
90 return uuid, mod, revnum |
93 return uuid, mod, revnum |
91 |
94 |
|
95 |
92 def quote(s): |
96 def quote(s): |
93 # As of svn 1.7, many svn calls expect "canonical" paths. In |
97 # As of svn 1.7, many svn calls expect "canonical" paths. In |
94 # theory, we should call svn.core.*canonicalize() on all paths |
98 # theory, we should call svn.core.*canonicalize() on all paths |
95 # before passing them to the API. Instead, we assume the base url |
99 # before passing them to the API. Instead, we assume the base url |
96 # is canonical and copy the behaviour of svn URL encoding function |
100 # is canonical and copy the behaviour of svn URL encoding function |
97 # so we can extend it safely with new components. The "safe" |
101 # so we can extend it safely with new components. The "safe" |
98 # characters were taken from the "svn_uri__char_validity" table in |
102 # characters were taken from the "svn_uri__char_validity" table in |
99 # libsvn_subr/path.c. |
103 # libsvn_subr/path.c. |
100 return urlreq.quote(s, "!$&'()*+,-./:=@_~") |
104 return urlreq.quote(s, "!$&'()*+,-./:=@_~") |
|
105 |
101 |
106 |
102 def geturl(path): |
107 def geturl(path): |
103 try: |
108 try: |
104 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path)) |
109 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path)) |
105 except svn.core.SubversionException: |
110 except svn.core.SubversionException: |
113 # by svn API, which is UTF-8. |
118 # by svn API, which is UTF-8. |
114 path = encoding.tolocal(path) |
119 path = encoding.tolocal(path) |
115 path = 'file://%s' % quote(path) |
120 path = 'file://%s' % quote(path) |
116 return svn.core.svn_path_canonicalize(path) |
121 return svn.core.svn_path_canonicalize(path) |
117 |
122 |
|
123 |
118 def optrev(number): |
124 def optrev(number): |
119 optrev = svn.core.svn_opt_revision_t() |
125 optrev = svn.core.svn_opt_revision_t() |
120 optrev.kind = svn.core.svn_opt_revision_number |
126 optrev.kind = svn.core.svn_opt_revision_number |
121 optrev.value.number = number |
127 optrev.value.number = number |
122 return optrev |
128 return optrev |
123 |
129 |
|
130 |
124 class changedpath(object): |
131 class changedpath(object): |
125 def __init__(self, p): |
132 def __init__(self, p): |
126 self.copyfrom_path = p.copyfrom_path |
133 self.copyfrom_path = p.copyfrom_path |
127 self.copyfrom_rev = p.copyfrom_rev |
134 self.copyfrom_rev = p.copyfrom_rev |
128 self.action = p.action |
135 self.action = p.action |
129 |
136 |
130 def get_log_child(fp, url, paths, start, end, limit=0, |
137 |
131 discover_changed_paths=True, strict_node_history=False): |
138 def get_log_child( |
|
139 fp, |
|
140 url, |
|
141 paths, |
|
142 start, |
|
143 end, |
|
144 limit=0, |
|
145 discover_changed_paths=True, |
|
146 strict_node_history=False, |
|
147 ): |
132 protocol = -1 |
148 protocol = -1 |
|
149 |
133 def receiver(orig_paths, revnum, author, date, message, pool): |
150 def receiver(orig_paths, revnum, author, date, message, pool): |
134 paths = {} |
151 paths = {} |
135 if orig_paths is not None: |
152 if orig_paths is not None: |
136 for k, v in orig_paths.iteritems(): |
153 for k, v in orig_paths.iteritems(): |
137 paths[k] = changedpath(v) |
154 paths[k] = changedpath(v) |
138 pickle.dump((paths, revnum, author, date, message), |
155 pickle.dump((paths, revnum, author, date, message), fp, protocol) |
139 fp, protocol) |
|
140 |
156 |
141 try: |
157 try: |
142 # Use an ra of our own so that our parent can consume |
158 # Use an ra of our own so that our parent can consume |
143 # our results without confusing the server. |
159 # our results without confusing the server. |
144 t = transport.SvnRaTransport(url=url) |
160 t = transport.SvnRaTransport(url=url) |
145 svn.ra.get_log(t.ra, paths, start, end, limit, |
161 svn.ra.get_log( |
146 discover_changed_paths, |
162 t.ra, |
147 strict_node_history, |
163 paths, |
148 receiver) |
164 start, |
|
165 end, |
|
166 limit, |
|
167 discover_changed_paths, |
|
168 strict_node_history, |
|
169 receiver, |
|
170 ) |
149 except IOError: |
171 except IOError: |
150 # Caller may interrupt the iteration |
172 # Caller may interrupt the iteration |
151 pickle.dump(None, fp, protocol) |
173 pickle.dump(None, fp, protocol) |
152 except Exception as inst: |
174 except Exception as inst: |
153 pickle.dump(stringutil.forcebytestr(inst), fp, protocol) |
175 pickle.dump(stringutil.forcebytestr(inst), fp, protocol) |
157 # With large history, cleanup process goes crazy and suddenly |
179 # With large history, cleanup process goes crazy and suddenly |
158 # consumes *huge* amount of memory. The output file being closed, |
180 # consumes *huge* amount of memory. The output file being closed, |
159 # there is no need for clean termination. |
181 # there is no need for clean termination. |
160 os._exit(0) |
182 os._exit(0) |
161 |
183 |
|
184 |
162 def debugsvnlog(ui, **opts): |
185 def debugsvnlog(ui, **opts): |
163 """Fetch SVN log in a subprocess and channel them back to parent to |
186 """Fetch SVN log in a subprocess and channel them back to parent to |
164 avoid memory collection issues. |
187 avoid memory collection issues. |
165 """ |
188 """ |
166 if svn is None: |
189 if svn is None: |
167 raise error.Abort(_('debugsvnlog could not load Subversion python ' |
190 raise error.Abort( |
168 'bindings')) |
191 _('debugsvnlog could not load Subversion python ' 'bindings') |
|
192 ) |
169 |
193 |
170 args = decodeargs(ui.fin.read()) |
194 args = decodeargs(ui.fin.read()) |
171 get_log_child(ui.fout, *args) |
195 get_log_child(ui.fout, *args) |
172 |
196 |
|
197 |
173 class logstream(object): |
198 class logstream(object): |
174 """Interruptible revision log iterator.""" |
199 """Interruptible revision log iterator.""" |
|
200 |
175 def __init__(self, stdout): |
201 def __init__(self, stdout): |
176 self._stdout = stdout |
202 self._stdout = stdout |
177 |
203 |
178 def __iter__(self): |
204 def __iter__(self): |
179 while True: |
205 while True: |
180 try: |
206 try: |
181 entry = pickle.load(self._stdout) |
207 entry = pickle.load(self._stdout) |
182 except EOFError: |
208 except EOFError: |
183 raise error.Abort(_('Mercurial failed to run itself, check' |
209 raise error.Abort( |
184 ' hg executable is in PATH')) |
210 _( |
|
211 'Mercurial failed to run itself, check' |
|
212 ' hg executable is in PATH' |
|
213 ) |
|
214 ) |
185 try: |
215 try: |
186 orig_paths, revnum, author, date, message = entry |
216 orig_paths, revnum, author, date, message = entry |
187 except (TypeError, ValueError): |
217 except (TypeError, ValueError): |
188 if entry is None: |
218 if entry is None: |
189 break |
219 break |
193 def close(self): |
223 def close(self): |
194 if self._stdout: |
224 if self._stdout: |
195 self._stdout.close() |
225 self._stdout.close() |
196 self._stdout = None |
226 self._stdout = None |
197 |
227 |
|
228 |
198 class directlogstream(list): |
229 class directlogstream(list): |
199 """Direct revision log iterator. |
230 """Direct revision log iterator. |
200 This can be used for debugging and development but it will probably leak |
231 This can be used for debugging and development but it will probably leak |
201 memory and is not suitable for real conversions.""" |
232 memory and is not suitable for real conversions.""" |
202 def __init__(self, url, paths, start, end, limit=0, |
233 |
203 discover_changed_paths=True, strict_node_history=False): |
234 def __init__( |
204 |
235 self, |
|
236 url, |
|
237 paths, |
|
238 start, |
|
239 end, |
|
240 limit=0, |
|
241 discover_changed_paths=True, |
|
242 strict_node_history=False, |
|
243 ): |
205 def receiver(orig_paths, revnum, author, date, message, pool): |
244 def receiver(orig_paths, revnum, author, date, message, pool): |
206 paths = {} |
245 paths = {} |
207 if orig_paths is not None: |
246 if orig_paths is not None: |
208 for k, v in orig_paths.iteritems(): |
247 for k, v in orig_paths.iteritems(): |
209 paths[k] = changedpath(v) |
248 paths[k] = changedpath(v) |
210 self.append((paths, revnum, author, date, message)) |
249 self.append((paths, revnum, author, date, message)) |
211 |
250 |
212 # Use an ra of our own so that our parent can consume |
251 # Use an ra of our own so that our parent can consume |
213 # our results without confusing the server. |
252 # our results without confusing the server. |
214 t = transport.SvnRaTransport(url=url) |
253 t = transport.SvnRaTransport(url=url) |
215 svn.ra.get_log(t.ra, paths, start, end, limit, |
254 svn.ra.get_log( |
216 discover_changed_paths, |
255 t.ra, |
217 strict_node_history, |
256 paths, |
218 receiver) |
257 start, |
|
258 end, |
|
259 limit, |
|
260 discover_changed_paths, |
|
261 strict_node_history, |
|
262 receiver, |
|
263 ) |
219 |
264 |
220 def close(self): |
265 def close(self): |
221 pass |
266 pass |
|
267 |
222 |
268 |
223 # Check to see if the given path is a local Subversion repo. Verify this by |
269 # Check to see if the given path is a local Subversion repo. Verify this by |
224 # looking for several svn-specific files and directories in the given |
270 # looking for several svn-specific files and directories in the given |
225 # directory. |
271 # directory. |
226 def filecheck(ui, path, proto): |
272 def filecheck(ui, path, proto): |
227 for x in ('locks', 'hooks', 'format', 'db'): |
273 for x in ('locks', 'hooks', 'format', 'db'): |
228 if not os.path.exists(os.path.join(path, x)): |
274 if not os.path.exists(os.path.join(path, x)): |
229 return False |
275 return False |
230 return True |
276 return True |
|
277 |
231 |
278 |
232 # Check to see if a given path is the root of an svn repo over http. We verify |
279 # Check to see if a given path is the root of an svn repo over http. We verify |
233 # this by requesting a version-controlled URL we know can't exist and looking |
280 # this by requesting a version-controlled URL we know can't exist and looking |
234 # for the svn-specific "not found" XML. |
281 # for the svn-specific "not found" XML. |
235 def httpcheck(ui, path, proto): |
282 def httpcheck(ui, path, proto): |
238 rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path), 'rb') |
285 rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path), 'rb') |
239 data = rsp.read() |
286 data = rsp.read() |
240 except urlerr.httperror as inst: |
287 except urlerr.httperror as inst: |
241 if inst.code != 404: |
288 if inst.code != 404: |
242 # Except for 404 we cannot know for sure this is not an svn repo |
289 # Except for 404 we cannot know for sure this is not an svn repo |
243 ui.warn(_('svn: cannot probe remote repository, assume it could ' |
290 ui.warn( |
244 'be a subversion repository. Use --source-type if you ' |
291 _( |
245 'know better.\n')) |
292 'svn: cannot probe remote repository, assume it could ' |
|
293 'be a subversion repository. Use --source-type if you ' |
|
294 'know better.\n' |
|
295 ) |
|
296 ) |
246 return True |
297 return True |
247 data = inst.fp.read() |
298 data = inst.fp.read() |
248 except Exception: |
299 except Exception: |
249 # Could be urlerr.urlerror if the URL is invalid or anything else. |
300 # Could be urlerr.urlerror if the URL is invalid or anything else. |
250 return False |
301 return False |
251 return '<m:human-readable errcode="160013">' in data |
302 return '<m:human-readable errcode="160013">' in data |
252 |
303 |
253 protomap = {'http': httpcheck, |
304 |
254 'https': httpcheck, |
305 protomap = { |
255 'file': filecheck, |
306 'http': httpcheck, |
256 } |
307 'https': httpcheck, |
|
308 'file': filecheck, |
|
309 } |
|
310 |
|
311 |
257 def issvnurl(ui, url): |
312 def issvnurl(ui, url): |
258 try: |
313 try: |
259 proto, path = url.split('://', 1) |
314 proto, path = url.split('://', 1) |
260 if proto == 'file': |
315 if proto == 'file': |
261 if (pycompat.iswindows and path[:1] == '/' |
316 if ( |
262 and path[1:2].isalpha() and path[2:6].lower() == '%3a/'): |
317 pycompat.iswindows |
|
318 and path[:1] == '/' |
|
319 and path[1:2].isalpha() |
|
320 and path[2:6].lower() == '%3a/' |
|
321 ): |
263 path = path[:2] + ':/' + path[6:] |
322 path = path[:2] + ':/' + path[6:] |
264 path = urlreq.url2pathname(path) |
323 path = urlreq.url2pathname(path) |
265 except ValueError: |
324 except ValueError: |
266 proto = 'file' |
325 proto = 'file' |
267 path = os.path.abspath(url) |
326 path = os.path.abspath(url) |
290 # |
350 # |
291 class svn_source(converter_source): |
351 class svn_source(converter_source): |
292 def __init__(self, ui, repotype, url, revs=None): |
352 def __init__(self, ui, repotype, url, revs=None): |
293 super(svn_source, self).__init__(ui, repotype, url, revs=revs) |
353 super(svn_source, self).__init__(ui, repotype, url, revs=revs) |
294 |
354 |
295 if not (url.startswith('svn://') or url.startswith('svn+ssh://') or |
355 if not ( |
296 (os.path.exists(url) and |
356 url.startswith('svn://') |
297 os.path.exists(os.path.join(url, '.svn'))) or |
357 or url.startswith('svn+ssh://') |
298 issvnurl(ui, url)): |
358 or ( |
299 raise NoRepo(_("%s does not look like a Subversion repository") |
359 os.path.exists(url) |
300 % url) |
360 and os.path.exists(os.path.join(url, '.svn')) |
|
361 ) |
|
362 or issvnurl(ui, url) |
|
363 ): |
|
364 raise NoRepo( |
|
365 _("%s does not look like a Subversion repository") % url |
|
366 ) |
301 if svn is None: |
367 if svn is None: |
302 raise MissingTool(_('could not load Subversion python bindings')) |
368 raise MissingTool(_('could not load Subversion python bindings')) |
303 |
369 |
304 try: |
370 try: |
305 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR |
371 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR |
306 if version < (1, 4): |
372 if version < (1, 4): |
307 raise MissingTool(_('Subversion python bindings %d.%d found, ' |
373 raise MissingTool( |
308 '1.4 or later required') % version) |
374 _( |
|
375 'Subversion python bindings %d.%d found, ' |
|
376 '1.4 or later required' |
|
377 ) |
|
378 % version |
|
379 ) |
309 except AttributeError: |
380 except AttributeError: |
310 raise MissingTool(_('Subversion python bindings are too old, 1.4 ' |
381 raise MissingTool( |
311 'or later required')) |
382 _( |
|
383 'Subversion python bindings are too old, 1.4 ' |
|
384 'or later required' |
|
385 ) |
|
386 ) |
312 |
387 |
313 self.lastrevs = {} |
388 self.lastrevs = {} |
314 |
389 |
315 latest = None |
390 latest = None |
316 try: |
391 try: |
317 # Support file://path@rev syntax. Useful e.g. to convert |
392 # Support file://path@rev syntax. Useful e.g. to convert |
318 # deleted branches. |
393 # deleted branches. |
319 at = url.rfind('@') |
394 at = url.rfind('@') |
320 if at >= 0: |
395 if at >= 0: |
321 latest = int(url[at + 1:]) |
396 latest = int(url[at + 1 :]) |
322 url = url[:at] |
397 url = url[:at] |
323 except ValueError: |
398 except ValueError: |
324 pass |
399 pass |
325 self.url = geturl(url) |
400 self.url = geturl(url) |
326 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8 |
401 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8 |
327 try: |
402 try: |
328 self.transport = transport.SvnRaTransport(url=self.url) |
403 self.transport = transport.SvnRaTransport(url=self.url) |
329 self.ra = self.transport.ra |
404 self.ra = self.transport.ra |
330 self.ctx = self.transport.client |
405 self.ctx = self.transport.client |
331 self.baseurl = svn.ra.get_repos_root(self.ra) |
406 self.baseurl = svn.ra.get_repos_root(self.ra) |
332 # Module is either empty or a repository path starting with |
407 # Module is either empty or a repository path starting with |
333 # a slash and not ending with a slash. |
408 # a slash and not ending with a slash. |
334 self.module = urlreq.unquote(self.url[len(self.baseurl):]) |
409 self.module = urlreq.unquote(self.url[len(self.baseurl) :]) |
335 self.prevmodule = None |
410 self.prevmodule = None |
336 self.rootmodule = self.module |
411 self.rootmodule = self.module |
337 self.commits = {} |
412 self.commits = {} |
338 self.paths = {} |
413 self.paths = {} |
339 self.uuid = svn.ra.get_uuid(self.ra) |
414 self.uuid = svn.ra.get_uuid(self.ra) |
340 except svn.core.SubversionException: |
415 except svn.core.SubversionException: |
341 ui.traceback() |
416 ui.traceback() |
342 svnversion = '%d.%d.%d' % (svn.core.SVN_VER_MAJOR, |
417 svnversion = '%d.%d.%d' % ( |
343 svn.core.SVN_VER_MINOR, |
418 svn.core.SVN_VER_MAJOR, |
344 svn.core.SVN_VER_MICRO) |
419 svn.core.SVN_VER_MINOR, |
345 raise NoRepo(_("%s does not look like a Subversion repository " |
420 svn.core.SVN_VER_MICRO, |
346 "to libsvn version %s") |
421 ) |
347 % (self.url, svnversion)) |
422 raise NoRepo( |
|
423 _( |
|
424 "%s does not look like a Subversion repository " |
|
425 "to libsvn version %s" |
|
426 ) |
|
427 % (self.url, svnversion) |
|
428 ) |
348 |
429 |
349 if revs: |
430 if revs: |
350 if len(revs) > 1: |
431 if len(revs) > 1: |
351 raise error.Abort(_('subversion source does not support ' |
432 raise error.Abort( |
352 'specifying multiple revisions')) |
433 _( |
|
434 'subversion source does not support ' |
|
435 'specifying multiple revisions' |
|
436 ) |
|
437 ) |
353 try: |
438 try: |
354 latest = int(revs[0]) |
439 latest = int(revs[0]) |
355 except ValueError: |
440 except ValueError: |
356 raise error.Abort(_('svn: revision %s is not an integer') % |
441 raise error.Abort( |
357 revs[0]) |
442 _('svn: revision %s is not an integer') % revs[0] |
|
443 ) |
358 |
444 |
359 trunkcfg = self.ui.config('convert', 'svn.trunk') |
445 trunkcfg = self.ui.config('convert', 'svn.trunk') |
360 if trunkcfg is None: |
446 if trunkcfg is None: |
361 trunkcfg = 'trunk' |
447 trunkcfg = 'trunk' |
362 self.trunkname = trunkcfg.strip('/') |
448 self.trunkname = trunkcfg.strip('/') |
364 try: |
450 try: |
365 self.startrev = int(self.startrev) |
451 self.startrev = int(self.startrev) |
366 if self.startrev < 0: |
452 if self.startrev < 0: |
367 self.startrev = 0 |
453 self.startrev = 0 |
368 except ValueError: |
454 except ValueError: |
369 raise error.Abort(_('svn: start revision %s is not an integer') |
455 raise error.Abort( |
370 % self.startrev) |
456 _('svn: start revision %s is not an integer') % self.startrev |
|
457 ) |
371 |
458 |
372 try: |
459 try: |
373 self.head = self.latest(self.module, latest) |
460 self.head = self.latest(self.module, latest) |
374 except SvnPathNotFound: |
461 except SvnPathNotFound: |
375 self.head = None |
462 self.head = None |
376 if not self.head: |
463 if not self.head: |
377 raise error.Abort(_('no revision found in module %s') |
464 raise error.Abort(_('no revision found in module %s') % self.module) |
378 % self.module) |
|
379 self.last_changed = self.revnum(self.head) |
465 self.last_changed = self.revnum(self.head) |
380 |
466 |
381 self._changescache = (None, None) |
467 self._changescache = (None, None) |
382 |
468 |
383 if os.path.exists(os.path.join(url, '.svn/entries')): |
469 if os.path.exists(os.path.join(url, '.svn/entries')): |
395 lastrevs[module] = revnum |
481 lastrevs[module] = revnum |
396 self.lastrevs = lastrevs |
482 self.lastrevs = lastrevs |
397 |
483 |
398 def exists(self, path, optrev): |
484 def exists(self, path, optrev): |
399 try: |
485 try: |
400 svn.client.ls(self.url.rstrip('/') + '/' + quote(path), |
486 svn.client.ls( |
401 optrev, False, self.ctx) |
487 self.url.rstrip('/') + '/' + quote(path), |
|
488 optrev, |
|
489 False, |
|
490 self.ctx, |
|
491 ) |
402 return True |
492 return True |
403 except svn.core.SubversionException: |
493 except svn.core.SubversionException: |
404 return False |
494 return False |
405 |
495 |
406 def getheads(self): |
496 def getheads(self): |
407 |
|
408 def isdir(path, revnum): |
497 def isdir(path, revnum): |
409 kind = self._checkpath(path, revnum) |
498 kind = self._checkpath(path, revnum) |
410 return kind == svn.core.svn_node_dir |
499 return kind == svn.core.svn_node_dir |
411 |
500 |
412 def getcfgpath(name, rev): |
501 def getcfgpath(name, rev): |
436 if trunk: |
527 if trunk: |
437 oldmodule = self.module or '' |
528 oldmodule = self.module or '' |
438 self.module += '/' + trunk |
529 self.module += '/' + trunk |
439 self.head = self.latest(self.module, self.last_changed) |
530 self.head = self.latest(self.module, self.last_changed) |
440 if not self.head: |
531 if not self.head: |
441 raise error.Abort(_('no revision found in module %s') |
532 raise error.Abort( |
442 % self.module) |
533 _('no revision found in module %s') % self.module |
|
534 ) |
443 |
535 |
444 # First head in the list is the module's head |
536 # First head in the list is the module's head |
445 self.heads = [self.head] |
537 self.heads = [self.head] |
446 if self.tags is not None: |
538 if self.tags is not None: |
447 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags')) |
539 self.tags = '%s/%s' % (oldmodule, (self.tags or 'tags')) |
448 |
540 |
449 # Check if branches bring a few more heads to the list |
541 # Check if branches bring a few more heads to the list |
450 if branches: |
542 if branches: |
451 rpath = self.url.strip('/') |
543 rpath = self.url.strip('/') |
452 branchnames = svn.client.ls(rpath + '/' + quote(branches), |
544 branchnames = svn.client.ls( |
453 rev, False, self.ctx) |
545 rpath + '/' + quote(branches), rev, False, self.ctx |
|
546 ) |
454 for branch in sorted(branchnames): |
547 for branch in sorted(branchnames): |
455 module = '%s/%s/%s' % (oldmodule, branches, branch) |
548 module = '%s/%s/%s' % (oldmodule, branches, branch) |
456 if not isdir(module, self.last_changed): |
549 if not isdir(module, self.last_changed): |
457 continue |
550 continue |
458 brevid = self.latest(module, self.last_changed) |
551 brevid = self.latest(module, self.last_changed) |
459 if not brevid: |
552 if not brevid: |
460 self.ui.note(_('ignoring empty branch %s\n') % branch) |
553 self.ui.note(_('ignoring empty branch %s\n') % branch) |
461 continue |
554 continue |
462 self.ui.note(_('found branch %s at %d\n') % |
555 self.ui.note( |
463 (branch, self.revnum(brevid))) |
556 _('found branch %s at %d\n') % (branch, self.revnum(brevid)) |
|
557 ) |
464 self.heads.append(brevid) |
558 self.heads.append(brevid) |
465 |
559 |
466 if self.startrev and self.heads: |
560 if self.startrev and self.heads: |
467 if len(self.heads) > 1: |
561 if len(self.heads) > 1: |
468 raise error.Abort(_('svn: start revision is not supported ' |
562 raise error.Abort( |
469 'with more than one branch')) |
563 _( |
|
564 'svn: start revision is not supported ' |
|
565 'with more than one branch' |
|
566 ) |
|
567 ) |
470 revnum = self.revnum(self.heads[0]) |
568 revnum = self.revnum(self.heads[0]) |
471 if revnum < self.startrev: |
569 if revnum < self.startrev: |
472 raise error.Abort( |
570 raise error.Abort( |
473 _('svn: no revision found after start revision %d') |
571 _('svn: no revision found after start revision %d') |
474 % self.startrev) |
572 % self.startrev |
|
573 ) |
475 |
574 |
476 return self.heads |
575 return self.heads |
477 |
576 |
478 def _getchanges(self, rev, full): |
577 def _getchanges(self, rev, full): |
479 (paths, parents) = self.paths[rev] |
578 (paths, parents) = self.paths[rev] |
481 if parents: |
580 if parents: |
482 files, self.removed, copies = self.expandpaths(rev, paths, parents) |
581 files, self.removed, copies = self.expandpaths(rev, paths, parents) |
483 if full or not parents: |
582 if full or not parents: |
484 # Perform a full checkout on roots |
583 # Perform a full checkout on roots |
485 uuid, module, revnum = revsplit(rev) |
584 uuid, module, revnum = revsplit(rev) |
486 entries = svn.client.ls(self.baseurl + quote(module), |
585 entries = svn.client.ls( |
487 optrev(revnum), True, self.ctx) |
586 self.baseurl + quote(module), optrev(revnum), True, self.ctx |
488 files = [n for n, e in entries.iteritems() |
587 ) |
489 if e.kind == svn.core.svn_node_file] |
588 files = [ |
|
589 n |
|
590 for n, e in entries.iteritems() |
|
591 if e.kind == svn.core.svn_node_file |
|
592 ] |
490 self.removed = set() |
593 self.removed = set() |
491 |
594 |
492 files.sort() |
595 files.sort() |
493 files = zip(files, [rev] * len(files)) |
596 files = zip(files, [rev] * len(files)) |
494 return (files, copies) |
597 return (files, copies) |
531 del self.commits[rev] |
634 del self.commits[rev] |
532 return revcommit |
635 return revcommit |
533 |
636 |
534 def checkrevformat(self, revstr, mapname='splicemap'): |
637 def checkrevformat(self, revstr, mapname='splicemap'): |
535 """ fails if revision format does not match the correct format""" |
638 """ fails if revision format does not match the correct format""" |
536 if not re.match(r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-' |
639 if not re.match( |
537 r'[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]' |
640 r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-' |
538 r'{12,12}(.*)\@[0-9]+$',revstr): |
641 r'[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]' |
539 raise error.Abort(_('%s entry %s is not a valid revision' |
642 r'{12,12}(.*)\@[0-9]+$', |
540 ' identifier') % (mapname, revstr)) |
643 revstr, |
|
644 ): |
|
645 raise error.Abort( |
|
646 _('%s entry %s is not a valid revision' ' identifier') |
|
647 % (mapname, revstr) |
|
648 ) |
541 |
649 |
542 def numcommits(self): |
650 def numcommits(self): |
543 return int(self.head.rsplit('@', 1)[1]) - self.startrev |
651 return int(self.head.rsplit('@', 1)[1]) - self.startrev |
544 |
652 |
545 def gettags(self): |
653 def gettags(self): |
565 try: |
673 try: |
566 for entry in stream: |
674 for entry in stream: |
567 origpaths, revnum, author, date, message = entry |
675 origpaths, revnum, author, date, message = entry |
568 if not origpaths: |
676 if not origpaths: |
569 origpaths = [] |
677 origpaths = [] |
570 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e |
678 copies = [ |
571 in origpaths.iteritems() if e.copyfrom_path] |
679 (e.copyfrom_path, e.copyfrom_rev, p) |
|
680 for p, e in origpaths.iteritems() |
|
681 if e.copyfrom_path |
|
682 ] |
572 # Apply moves/copies from more specific to general |
683 # Apply moves/copies from more specific to general |
573 copies.sort(reverse=True) |
684 copies.sort(reverse=True) |
574 |
685 |
575 srctagspath = tagspath |
686 srctagspath = tagspath |
576 if copies and copies[-1][2] == tagspath: |
687 if copies and copies[-1][2] == tagspath: |
593 # /tags/tag.1 (from /trunk:10) |
704 # /tags/tag.1 (from /trunk:10) |
594 # /tags/tag.1/foo (from /branches/foo:12) |
705 # /tags/tag.1/foo (from /branches/foo:12) |
595 # Here/tags/tag.1 discarded as well as its children. |
706 # Here/tags/tag.1 discarded as well as its children. |
596 # It happens with tools like cvs2svn. Such tags cannot |
707 # It happens with tools like cvs2svn. Such tags cannot |
597 # be represented in mercurial. |
708 # be represented in mercurial. |
598 addeds = dict((p, e.copyfrom_path) for p, e |
709 addeds = dict( |
599 in origpaths.iteritems() |
710 (p, e.copyfrom_path) |
600 if e.action == 'A' and e.copyfrom_path) |
711 for p, e in origpaths.iteritems() |
|
712 if e.action == 'A' and e.copyfrom_path |
|
713 ) |
601 badroots = set() |
714 badroots = set() |
602 for destroot in addeds: |
715 for destroot in addeds: |
603 for source, sourcerev, dest in pendings: |
716 for source, sourcerev, dest in pendings: |
604 if (not dest.startswith(destroot + '/') |
717 if not dest.startswith( |
605 or source.startswith(addeds[destroot] + '/')): |
718 destroot + '/' |
|
719 ) or source.startswith(addeds[destroot] + '/'): |
606 continue |
720 continue |
607 badroots.add(destroot) |
721 badroots.add(destroot) |
608 break |
722 break |
609 |
723 |
610 for badroot in badroots: |
724 for badroot in badroots: |
611 pendings = [p for p in pendings if p[2] != badroot |
725 pendings = [ |
612 and not p[2].startswith(badroot + '/')] |
726 p |
|
727 for p in pendings |
|
728 if p[2] != badroot |
|
729 and not p[2].startswith(badroot + '/') |
|
730 ] |
613 |
731 |
614 # Tell tag renamings from tag creations |
732 # Tell tag renamings from tag creations |
615 renamings = [] |
733 renamings = [] |
616 for source, sourcerev, dest in pendings: |
734 for source, sourcerev, dest in pendings: |
617 tagname = dest.split('/')[-1] |
735 tagname = dest.split('/')[-1] |
640 |
758 |
641 def converted(self, rev, destrev): |
759 def converted(self, rev, destrev): |
642 if not self.wc: |
760 if not self.wc: |
643 return |
761 return |
644 if self.convertfp is None: |
762 if self.convertfp is None: |
645 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'), |
763 self.convertfp = open( |
646 'ab') |
764 os.path.join(self.wc, '.svn', 'hg-shamap'), 'ab' |
647 self.convertfp.write(util.tonativeeol('%s %d\n' |
765 ) |
648 % (destrev, self.revnum(rev)))) |
766 self.convertfp.write( |
|
767 util.tonativeeol('%s %d\n' % (destrev, self.revnum(rev))) |
|
768 ) |
649 self.convertfp.flush() |
769 self.convertfp.flush() |
650 |
770 |
651 def revid(self, revnum, module=None): |
771 def revid(self, revnum, module=None): |
652 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum) |
772 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum) |
653 |
773 |
660 revision. It may return a revision in a different module, |
780 revision. It may return a revision in a different module, |
661 since a branch may be moved without a change being |
781 since a branch may be moved without a change being |
662 reported. Return None if computed module does not belong to |
782 reported. Return None if computed module does not belong to |
663 rootmodule subtree. |
783 rootmodule subtree. |
664 """ |
784 """ |
|
785 |
665 def findchanges(path, start, stop=None): |
786 def findchanges(path, start, stop=None): |
666 stream = self._getlog([path], start, stop or 1) |
787 stream = self._getlog([path], start, stop or 1) |
667 try: |
788 try: |
668 for entry in stream: |
789 for entry in stream: |
669 paths, revnum, author, date, message = entry |
790 paths, revnum, author, date, message = entry |
701 dirent = svn.ra.stat(self.ra, path.strip('/'), stop) |
823 dirent = svn.ra.stat(self.ra, path.strip('/'), stop) |
702 self.reparent(prevmodule) |
824 self.reparent(prevmodule) |
703 except svn.core.SubversionException: |
825 except svn.core.SubversionException: |
704 dirent = None |
826 dirent = None |
705 if not dirent: |
827 if not dirent: |
706 raise SvnPathNotFound(_('%s not found up to revision %d') |
828 raise SvnPathNotFound( |
707 % (path, stop)) |
829 _('%s not found up to revision %d') % (path, stop) |
|
830 ) |
708 |
831 |
709 # stat() gives us the previous revision on this line of |
832 # stat() gives us the previous revision on this line of |
710 # development, but it might be in *another module*. Fetch the |
833 # development, but it might be in *another module*. Fetch the |
711 # log and detect renames down to the latest revision. |
834 # log and detect renames down to the latest revision. |
712 revnum, realpath = findchanges(path, stop, dirent.created_rev) |
835 revnum, realpath = findchanges(path, stop, dirent.created_rev) |
748 new_module, revnum = revsplit(rev)[1:] |
871 new_module, revnum = revsplit(rev)[1:] |
749 if new_module != self.module: |
872 if new_module != self.module: |
750 self.module = new_module |
873 self.module = new_module |
751 self.reparent(self.module) |
874 self.reparent(self.module) |
752 |
875 |
753 progress = self.ui.makeprogress(_('scanning paths'), unit=_('paths'), |
876 progress = self.ui.makeprogress( |
754 total=len(paths)) |
877 _('scanning paths'), unit=_('paths'), total=len(paths) |
|
878 ) |
755 for i, (path, ent) in enumerate(paths): |
879 for i, (path, ent) in enumerate(paths): |
756 progress.update(i, item=path) |
880 progress.update(i, item=path) |
757 entrypath = self.getrelpath(path) |
881 entrypath = self.getrelpath(path) |
758 |
882 |
759 kind = self._checkpath(entrypath, revnum) |
883 kind = self._checkpath(entrypath, revnum) |
767 if ent.copyfrom_rev < prevnum: |
891 if ent.copyfrom_rev < prevnum: |
768 continue |
892 continue |
769 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule) |
893 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule) |
770 if not copyfrom_path: |
894 if not copyfrom_path: |
771 continue |
895 continue |
772 self.ui.debug("copied to %s from %s@%s\n" % |
896 self.ui.debug( |
773 (entrypath, copyfrom_path, ent.copyfrom_rev)) |
897 "copied to %s from %s@%s\n" |
|
898 % (entrypath, copyfrom_path, ent.copyfrom_rev) |
|
899 ) |
774 copies[self.recode(entrypath)] = self.recode(copyfrom_path) |
900 copies[self.recode(entrypath)] = self.recode(copyfrom_path) |
775 elif kind == 0: # gone, but had better be a deleted *file* |
901 elif kind == 0: # gone, but had better be a deleted *file* |
776 self.ui.debug("gone from %s\n" % ent.copyfrom_rev) |
902 self.ui.debug("gone from %s\n" % ent.copyfrom_rev) |
777 pmodule, prevnum = revsplit(parents[0])[1:] |
903 pmodule, prevnum = revsplit(parents[0])[1:] |
778 parentpath = pmodule + "/" + entrypath |
904 parentpath = pmodule + "/" + entrypath |
779 fromkind = self._checkpath(entrypath, prevnum, pmodule) |
905 fromkind = self._checkpath(entrypath, prevnum, pmodule) |
780 |
906 |
788 childpath = childpath.replace(oroot, nroot) |
914 childpath = childpath.replace(oroot, nroot) |
789 childpath = self.getrelpath("/" + childpath, pmodule) |
915 childpath = self.getrelpath("/" + childpath, pmodule) |
790 if childpath: |
916 if childpath: |
791 removed.add(self.recode(childpath)) |
917 removed.add(self.recode(childpath)) |
792 else: |
918 else: |
793 self.ui.debug('unknown path in revision %d: %s\n' % |
919 self.ui.debug( |
794 (revnum, path)) |
920 'unknown path in revision %d: %s\n' % (revnum, path) |
|
921 ) |
795 elif kind == svn.core.svn_node_dir: |
922 elif kind == svn.core.svn_node_dir: |
796 if ent.action == 'M': |
923 if ent.action == 'M': |
797 # If the directory just had a prop change, |
924 # If the directory just had a prop change, |
798 # then we shouldn't need to look for its children. |
925 # then we shouldn't need to look for its children. |
799 continue |
926 continue |
826 if ent.copyfrom_rev < prevnum: |
953 if ent.copyfrom_rev < prevnum: |
827 continue |
954 continue |
828 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule) |
955 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule) |
829 if not copyfrompath: |
956 if not copyfrompath: |
830 continue |
957 continue |
831 self.ui.debug("mark %s came from %s:%d\n" |
958 self.ui.debug( |
832 % (path, copyfrompath, ent.copyfrom_rev)) |
959 "mark %s came from %s:%d\n" |
|
960 % (path, copyfrompath, ent.copyfrom_rev) |
|
961 ) |
833 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev) |
962 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev) |
834 for childpath in children: |
963 for childpath in children: |
835 childpath = self.getrelpath("/" + childpath, pmodule) |
964 childpath = self.getrelpath("/" + childpath, pmodule) |
836 if not childpath: |
965 if not childpath: |
837 continue |
966 continue |
838 copytopath = path + childpath[len(copyfrompath):] |
967 copytopath = path + childpath[len(copyfrompath) :] |
839 copytopath = self.getrelpath(copytopath) |
968 copytopath = self.getrelpath(copytopath) |
840 copies[self.recode(copytopath)] = self.recode(childpath) |
969 copies[self.recode(copytopath)] = self.recode(childpath) |
841 |
970 |
842 progress.complete() |
971 progress.complete() |
843 changed.update(removed) |
972 changed.update(removed) |
865 |
995 |
866 parents = [] |
996 parents = [] |
867 # check whether this revision is the start of a branch or part |
997 # check whether this revision is the start of a branch or part |
868 # of a branch renaming |
998 # of a branch renaming |
869 orig_paths = sorted(orig_paths.iteritems()) |
999 orig_paths = sorted(orig_paths.iteritems()) |
870 root_paths = [(p, e) for p, e in orig_paths |
1000 root_paths = [ |
871 if self.module.startswith(p)] |
1001 (p, e) for p, e in orig_paths if self.module.startswith(p) |
|
1002 ] |
872 if root_paths: |
1003 if root_paths: |
873 path, ent = root_paths[-1] |
1004 path, ent = root_paths[-1] |
874 if ent.copyfrom_path: |
1005 if ent.copyfrom_path: |
875 branched = True |
1006 branched = True |
876 newpath = ent.copyfrom_path + self.module[len(path):] |
1007 newpath = ent.copyfrom_path + self.module[len(path) :] |
877 # ent.copyfrom_rev may not be the actual last revision |
1008 # ent.copyfrom_rev may not be the actual last revision |
878 previd = self.latest(newpath, ent.copyfrom_rev) |
1009 previd = self.latest(newpath, ent.copyfrom_rev) |
879 if previd is not None: |
1010 if previd is not None: |
880 prevmodule, prevnum = revsplit(previd)[1:] |
1011 prevmodule, prevnum = revsplit(previd)[1:] |
881 if prevnum >= self.startrev: |
1012 if prevnum >= self.startrev: |
882 parents = [previd] |
1013 parents = [previd] |
883 self.ui.note( |
1014 self.ui.note( |
884 _('found parent of branch %s at %d: %s\n') % |
1015 _('found parent of branch %s at %d: %s\n') |
885 (self.module, prevnum, prevmodule)) |
1016 % (self.module, prevnum, prevmodule) |
|
1017 ) |
886 else: |
1018 else: |
887 self.ui.debug("no copyfrom path, don't know what to do.\n") |
1019 self.ui.debug("no copyfrom path, don't know what to do.\n") |
888 |
1020 |
889 paths = [] |
1021 paths = [] |
890 # filter out unrelated paths |
1022 # filter out unrelated paths |
915 if branch == self.trunkname: |
1047 if branch == self.trunkname: |
916 branch = None |
1048 branch = None |
917 except IndexError: |
1049 except IndexError: |
918 branch = None |
1050 branch = None |
919 |
1051 |
920 cset = commit(author=author, |
1052 cset = commit( |
921 date=dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'), |
1053 author=author, |
922 desc=log, |
1054 date=dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'), |
923 parents=parents, |
1055 desc=log, |
924 branch=branch, |
1056 parents=parents, |
925 rev=rev) |
1057 branch=branch, |
|
1058 rev=rev, |
|
1059 ) |
926 |
1060 |
927 self.commits[rev] = cset |
1061 self.commits[rev] = cset |
928 # The parents list is *shared* among self.paths and the |
1062 # The parents list is *shared* among self.paths and the |
929 # commit object. Both will be updated below. |
1063 # commit object. Both will be updated below. |
930 self.paths[rev] = (paths, cset.parents) |
1064 self.paths[rev] = (paths, cset.parents) |
931 if self.child_cset and not self.child_cset.parents: |
1065 if self.child_cset and not self.child_cset.parents: |
932 self.child_cset.parents[:] = [rev] |
1066 self.child_cset.parents[:] = [rev] |
933 self.child_cset = cset |
1067 self.child_cset = cset |
934 return cset, branched |
1068 return cset, branched |
935 |
1069 |
936 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') % |
1070 self.ui.note( |
937 (self.module, from_revnum, to_revnum)) |
1071 _('fetching revision log for "%s" from %d to %d\n') |
|
1072 % (self.module, from_revnum, to_revnum) |
|
1073 ) |
938 |
1074 |
939 try: |
1075 try: |
940 firstcset = None |
1076 firstcset = None |
941 lastonbranch = False |
1077 lastonbranch = False |
942 stream = self._getlog([self.module], from_revnum, to_revnum) |
1078 stream = self._getlog([self.module], from_revnum, to_revnum) |
998 if isinstance(info, list): |
1136 if isinstance(info, list): |
999 info = info[-1] |
1137 info = info[-1] |
1000 mode = ("svn:executable" in info) and 'x' or '' |
1138 mode = ("svn:executable" in info) and 'x' or '' |
1001 mode = ("svn:special" in info) and 'l' or mode |
1139 mode = ("svn:special" in info) and 'l' or mode |
1002 except svn.core.SubversionException as e: |
1140 except svn.core.SubversionException as e: |
1003 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND, |
1141 notfound = ( |
1004 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND) |
1142 svn.core.SVN_ERR_FS_NOT_FOUND, |
1005 if e.apr_err in notfound: # File not found |
1143 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND, |
|
1144 ) |
|
1145 if e.apr_err in notfound: # File not found |
1006 return None, None |
1146 return None, None |
1007 raise |
1147 raise |
1008 if mode == 'l': |
1148 if mode == 'l': |
1009 link_prefix = "link " |
1149 link_prefix = "link " |
1010 if data.startswith(link_prefix): |
1150 if data.startswith(link_prefix): |
1011 data = data[len(link_prefix):] |
1151 data = data[len(link_prefix) :] |
1012 return data, mode |
1152 return data, mode |
1013 |
1153 |
1014 def _iterfiles(self, path, revnum): |
1154 def _iterfiles(self, path, revnum): |
1015 """Enumerate all files in path at revnum, recursively.""" |
1155 """Enumerate all files in path at revnum, recursively.""" |
1016 path = path.strip('/') |
1156 path = path.strip('/') |
1017 pool = svn.core.Pool() |
1157 pool = svn.core.Pool() |
1018 rpath = '/'.join([self.baseurl, quote(path)]).strip('/') |
1158 rpath = '/'.join([self.baseurl, quote(path)]).strip('/') |
1019 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool) |
1159 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool) |
1020 if path: |
1160 if path: |
1021 path += '/' |
1161 path += '/' |
1022 return ((path + p) for p, e in entries.iteritems() |
1162 return ( |
1023 if e.kind == svn.core.svn_node_file) |
1163 (path + p) |
|
1164 for p, e in entries.iteritems() |
|
1165 if e.kind == svn.core.svn_node_file |
|
1166 ) |
1024 |
1167 |
1025 def getrelpath(self, path, module=None): |
1168 def getrelpath(self, path, module=None): |
1026 if module is None: |
1169 if module is None: |
1027 module = self.module |
1170 module = self.module |
1028 # Given the repository url of this wc, say |
1171 # Given the repository url of this wc, say |
1052 return svn.ra.check_path(self.ra, path.strip('/'), revnum) |
1195 return svn.ra.check_path(self.ra, path.strip('/'), revnum) |
1053 finally: |
1196 finally: |
1054 if module is not None: |
1197 if module is not None: |
1055 self.reparent(prevmodule) |
1198 self.reparent(prevmodule) |
1056 |
1199 |
1057 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True, |
1200 def _getlog( |
1058 strict_node_history=False): |
1201 self, |
|
1202 paths, |
|
1203 start, |
|
1204 end, |
|
1205 limit=0, |
|
1206 discover_changed_paths=True, |
|
1207 strict_node_history=False, |
|
1208 ): |
1059 # Normalize path names, svn >= 1.5 only wants paths relative to |
1209 # Normalize path names, svn >= 1.5 only wants paths relative to |
1060 # supplied URL |
1210 # supplied URL |
1061 relpaths = [] |
1211 relpaths = [] |
1062 for p in paths: |
1212 for p in paths: |
1063 if not p.startswith('/'): |
1213 if not p.startswith('/'): |
1064 p = self.module + '/' + p |
1214 p = self.module + '/' + p |
1065 relpaths.append(p.strip('/')) |
1215 relpaths.append(p.strip('/')) |
1066 args = [self.baseurl, relpaths, start, end, limit, |
1216 args = [ |
1067 discover_changed_paths, strict_node_history] |
1217 self.baseurl, |
|
1218 relpaths, |
|
1219 start, |
|
1220 end, |
|
1221 limit, |
|
1222 discover_changed_paths, |
|
1223 strict_node_history, |
|
1224 ] |
1068 # developer config: convert.svn.debugsvnlog |
1225 # developer config: convert.svn.debugsvnlog |
1069 if not self.ui.configbool('convert', 'svn.debugsvnlog'): |
1226 if not self.ui.configbool('convert', 'svn.debugsvnlog'): |
1070 return directlogstream(*args) |
1227 return directlogstream(*args) |
1071 arg = encodeargs(args) |
1228 arg = encodeargs(args) |
1072 hgexe = procutil.hgexecutable() |
1229 hgexe = procutil.hgexecutable() |
1135 else: |
1298 else: |
1136 if not re.search(br'^(file|http|https|svn|svn\+ssh)\://', path): |
1299 if not re.search(br'^(file|http|https|svn|svn\+ssh)\://', path): |
1137 path = os.path.realpath(path) |
1300 path = os.path.realpath(path) |
1138 if os.path.isdir(os.path.dirname(path)): |
1301 if os.path.isdir(os.path.dirname(path)): |
1139 if not os.path.exists(os.path.join(path, 'db', 'fs-type')): |
1302 if not os.path.exists(os.path.join(path, 'db', 'fs-type')): |
1140 ui.status(_("initializing svn repository '%s'\n") % |
1303 ui.status( |
1141 os.path.basename(path)) |
1304 _("initializing svn repository '%s'\n") |
|
1305 % os.path.basename(path) |
|
1306 ) |
1142 commandline(ui, 'svnadmin').run0('create', path) |
1307 commandline(ui, 'svnadmin').run0('create', path) |
1143 created = path |
1308 created = path |
1144 path = util.normpath(path) |
1309 path = util.normpath(path) |
1145 if not path.startswith('/'): |
1310 if not path.startswith('/'): |
1146 path = '/' + path |
1311 path = '/' + path |
1147 path = 'file://' + path |
1312 path = 'file://' + path |
1148 |
1313 |
1149 wcpath = os.path.join(encoding.getcwd(), os.path.basename(path) + |
1314 wcpath = os.path.join( |
1150 '-wc') |
1315 encoding.getcwd(), os.path.basename(path) + '-wc' |
1151 ui.status(_("initializing svn working copy '%s'\n") |
1316 ) |
1152 % os.path.basename(wcpath)) |
1317 ui.status( |
|
1318 _("initializing svn working copy '%s'\n") |
|
1319 % os.path.basename(wcpath) |
|
1320 ) |
1153 self.run0('checkout', path, wcpath) |
1321 self.run0('checkout', path, wcpath) |
1154 |
1322 |
1155 self.wc = wcpath |
1323 self.wc = wcpath |
1156 self.opener = vfsmod.vfs(self.wc) |
1324 self.opener = vfsmod.vfs(self.wc) |
1157 self.wopener = vfsmod.vfs(self.wc) |
1325 self.wopener = vfsmod.vfs(self.wc) |
1184 doc = xml.dom.minidom.parseString(output) |
1352 doc = xml.dom.minidom.parseString(output) |
1185 for e in doc.getElementsByTagName(r'entry'): |
1353 for e in doc.getElementsByTagName(r'entry'): |
1186 for n in e.childNodes: |
1354 for n in e.childNodes: |
1187 if n.nodeType != n.ELEMENT_NODE or n.tagName != r'name': |
1355 if n.nodeType != n.ELEMENT_NODE or n.tagName != r'name': |
1188 continue |
1356 continue |
1189 name = r''.join(c.data for c in n.childNodes |
1357 name = r''.join( |
1190 if c.nodeType == c.TEXT_NODE) |
1358 c.data for c in n.childNodes if c.nodeType == c.TEXT_NODE |
|
1359 ) |
1191 # Entries are compared with names coming from |
1360 # Entries are compared with names coming from |
1192 # mercurial, so bytes with undefined encoding. Our |
1361 # mercurial, so bytes with undefined encoding. Our |
1193 # best bet is to assume they are in local |
1362 # best bet is to assume they are in local |
1194 # encoding. They will be passed to command line calls |
1363 # encoding. They will be passed to command line calls |
1195 # later anyway, so they better be. |
1364 # later anyway, so they better be. |
1323 fd, messagefile = pycompat.mkstemp(prefix='hg-convert-') |
1495 fd, messagefile = pycompat.mkstemp(prefix='hg-convert-') |
1324 fp = os.fdopen(fd, r'wb') |
1496 fp = os.fdopen(fd, r'wb') |
1325 fp.write(util.tonativeeol(commit.desc)) |
1497 fp.write(util.tonativeeol(commit.desc)) |
1326 fp.close() |
1498 fp.close() |
1327 try: |
1499 try: |
1328 output = self.run0('commit', |
1500 output = self.run0( |
1329 username=stringutil.shortuser(commit.author), |
1501 'commit', |
1330 file=messagefile, |
1502 username=stringutil.shortuser(commit.author), |
1331 encoding='utf-8') |
1503 file=messagefile, |
|
1504 encoding='utf-8', |
|
1505 ) |
1332 try: |
1506 try: |
1333 rev = self.commit_re.search(output).group(1) |
1507 rev = self.commit_re.search(output).group(1) |
1334 except AttributeError: |
1508 except AttributeError: |
1335 if not files: |
1509 if not files: |
1336 return parents[0] if parents else 'None' |
1510 return parents[0] if parents else 'None' |
1337 self.ui.warn(_('unexpected svn output:\n')) |
1511 self.ui.warn(_('unexpected svn output:\n')) |
1338 self.ui.warn(output) |
1512 self.ui.warn(output) |
1339 raise error.Abort(_('unable to cope with svn output')) |
1513 raise error.Abort(_('unable to cope with svn output')) |
1340 if commit.rev: |
1514 if commit.rev: |
1341 self.run('propset', 'hg:convert-rev', commit.rev, |
1515 self.run( |
1342 revprop=True, revision=rev) |
1516 'propset', |
|
1517 'hg:convert-rev', |
|
1518 commit.rev, |
|
1519 revprop=True, |
|
1520 revision=rev, |
|
1521 ) |
1343 if commit.branch and commit.branch != 'default': |
1522 if commit.branch and commit.branch != 'default': |
1344 self.run('propset', 'hg:convert-branch', commit.branch, |
1523 self.run( |
1345 revprop=True, revision=rev) |
1524 'propset', |
|
1525 'hg:convert-branch', |
|
1526 commit.branch, |
|
1527 revprop=True, |
|
1528 revision=rev, |
|
1529 ) |
1346 for parent in parents: |
1530 for parent in parents: |
1347 self.addchild(parent, rev) |
1531 self.addchild(parent, rev) |
1348 return self.revid(rev) |
1532 return self.revid(rev) |
1349 finally: |
1533 finally: |
1350 os.unlink(messagefile) |
1534 os.unlink(messagefile) |