##// END OF EJS Templates
convert: transcode CVS log messages by specified encoding (issue5597)...
FUJIWARA Katsunori -
r33388:0823f098 default
parent child Browse files
Show More
@@ -1,503 +1,509 b''
1 1 # convert.py Foreign SCM converter
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''import revisions from foreign VCS repositories into Mercurial'''
9 9
10 10 from __future__ import absolute_import
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 registrar,
15 15 )
16 16
17 17 from . import (
18 18 convcmd,
19 19 cvsps,
20 20 subversion,
21 21 )
22 22
23 23 cmdtable = {}
24 24 command = registrar.command(cmdtable)
25 25 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
26 26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
27 27 # be specifying the version(s) of Mercurial they are tested with, or
28 28 # leave the attribute unspecified.
29 29 testedwith = 'ships-with-hg-core'
30 30
31 31 # Commands definition was moved elsewhere to ease demandload job.
32 32
33 33 @command('convert',
34 34 [('', 'authors', '',
35 35 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
36 36 _('FILE')),
37 37 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
38 38 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
39 39 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
40 40 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
41 41 ('', 'filemap', '', _('remap file names using contents of file'),
42 42 _('FILE')),
43 43 ('', 'full', None,
44 44 _('apply filemap changes by converting all files again')),
45 45 ('', 'splicemap', '', _('splice synthesized history into place'),
46 46 _('FILE')),
47 47 ('', 'branchmap', '', _('change branch names while converting'),
48 48 _('FILE')),
49 49 ('', 'branchsort', None, _('try to sort changesets by branches')),
50 50 ('', 'datesort', None, _('try to sort changesets by date')),
51 51 ('', 'sourcesort', None, _('preserve source changesets order')),
52 52 ('', 'closesort', None, _('try to reorder closed revisions'))],
53 53 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
54 54 norepo=True)
55 55 def convert(ui, src, dest=None, revmapfile=None, **opts):
56 56 """convert a foreign SCM repository to a Mercurial one.
57 57
58 58 Accepted source formats [identifiers]:
59 59
60 60 - Mercurial [hg]
61 61 - CVS [cvs]
62 62 - Darcs [darcs]
63 63 - git [git]
64 64 - Subversion [svn]
65 65 - Monotone [mtn]
66 66 - GNU Arch [gnuarch]
67 67 - Bazaar [bzr]
68 68 - Perforce [p4]
69 69
70 70 Accepted destination formats [identifiers]:
71 71
72 72 - Mercurial [hg]
73 73 - Subversion [svn] (history on branches is not preserved)
74 74
75 75 If no revision is given, all revisions will be converted.
76 76 Otherwise, convert will only import up to the named revision
77 77 (given in a format understood by the source).
78 78
79 79 If no destination directory name is specified, it defaults to the
80 80 basename of the source with ``-hg`` appended. If the destination
81 81 repository doesn't exist, it will be created.
82 82
83 83 By default, all sources except Mercurial will use --branchsort.
84 84 Mercurial uses --sourcesort to preserve original revision numbers
85 85 order. Sort modes have the following effects:
86 86
87 87 --branchsort convert from parent to child revision when possible,
88 88 which means branches are usually converted one after
89 89 the other. It generates more compact repositories.
90 90
91 91 --datesort sort revisions by date. Converted repositories have
92 92 good-looking changelogs but are often an order of
93 93 magnitude larger than the same ones generated by
94 94 --branchsort.
95 95
96 96 --sourcesort try to preserve source revisions order, only
97 97 supported by Mercurial sources.
98 98
99 99 --closesort try to move closed revisions as close as possible
100 100 to parent branches, only supported by Mercurial
101 101 sources.
102 102
103 103 If ``REVMAP`` isn't given, it will be put in a default location
104 104 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
105 105 text file that maps each source commit ID to the destination ID
106 106 for that revision, like so::
107 107
108 108 <source ID> <destination ID>
109 109
110 110 If the file doesn't exist, it's automatically created. It's
111 111 updated on each commit copied, so :hg:`convert` can be interrupted
112 112 and can be run repeatedly to copy new commits.
113 113
114 114 The authormap is a simple text file that maps each source commit
115 115 author to a destination commit author. It is handy for source SCMs
116 116 that use unix logins to identify authors (e.g.: CVS). One line per
117 117 author mapping and the line format is::
118 118
119 119 source author = destination author
120 120
121 121 Empty lines and lines starting with a ``#`` are ignored.
122 122
123 123 The filemap is a file that allows filtering and remapping of files
124 124 and directories. Each line can contain one of the following
125 125 directives::
126 126
127 127 include path/to/file-or-dir
128 128
129 129 exclude path/to/file-or-dir
130 130
131 131 rename path/to/source path/to/destination
132 132
133 133 Comment lines start with ``#``. A specified path matches if it
134 134 equals the full relative name of a file or one of its parent
135 135 directories. The ``include`` or ``exclude`` directive with the
136 136 longest matching path applies, so line order does not matter.
137 137
138 138 The ``include`` directive causes a file, or all files under a
139 139 directory, to be included in the destination repository. The default
140 140 if there are no ``include`` statements is to include everything.
141 141 If there are any ``include`` statements, nothing else is included.
142 142 The ``exclude`` directive causes files or directories to
143 143 be omitted. The ``rename`` directive renames a file or directory if
144 144 it is converted. To rename from a subdirectory into the root of
145 145 the repository, use ``.`` as the path to rename to.
146 146
147 147 ``--full`` will make sure the converted changesets contain exactly
148 148 the right files with the right content. It will make a full
149 149 conversion of all files, not just the ones that have
150 150 changed. Files that already are correct will not be changed. This
151 151 can be used to apply filemap changes when converting
152 152 incrementally. This is currently only supported for Mercurial and
153 153 Subversion.
154 154
155 155 The splicemap is a file that allows insertion of synthetic
156 156 history, letting you specify the parents of a revision. This is
157 157 useful if you want to e.g. give a Subversion merge two parents, or
158 158 graft two disconnected series of history together. Each entry
159 159 contains a key, followed by a space, followed by one or two
160 160 comma-separated values::
161 161
162 162 key parent1, parent2
163 163
164 164 The key is the revision ID in the source
165 165 revision control system whose parents should be modified (same
166 166 format as a key in .hg/shamap). The values are the revision IDs
167 167 (in either the source or destination revision control system) that
168 168 should be used as the new parents for that node. For example, if
169 169 you have merged "release-1.0" into "trunk", then you should
170 170 specify the revision on "trunk" as the first parent and the one on
171 171 the "release-1.0" branch as the second.
172 172
173 173 The branchmap is a file that allows you to rename a branch when it is
174 174 being brought in from whatever external repository. When used in
175 175 conjunction with a splicemap, it allows for a powerful combination
176 176 to help fix even the most badly mismanaged repositories and turn them
177 177 into nicely structured Mercurial repositories. The branchmap contains
178 178 lines of the form::
179 179
180 180 original_branch_name new_branch_name
181 181
182 182 where "original_branch_name" is the name of the branch in the
183 183 source repository, and "new_branch_name" is the name of the branch
184 184 is the destination repository. No whitespace is allowed in the new
185 185 branch name. This can be used to (for instance) move code in one
186 186 repository from "default" to a named branch.
187 187
188 188 Mercurial Source
189 189 ################
190 190
191 191 The Mercurial source recognizes the following configuration
192 192 options, which you can set on the command line with ``--config``:
193 193
194 194 :convert.hg.ignoreerrors: ignore integrity errors when reading.
195 195 Use it to fix Mercurial repositories with missing revlogs, by
196 196 converting from and to Mercurial. Default is False.
197 197
198 198 :convert.hg.saverev: store original revision ID in changeset
199 199 (forces target IDs to change). It takes a boolean argument and
200 200 defaults to False.
201 201
202 202 :convert.hg.startrev: specify the initial Mercurial revision.
203 203 The default is 0.
204 204
205 205 :convert.hg.revs: revset specifying the source revisions to convert.
206 206
207 207 CVS Source
208 208 ##########
209 209
210 210 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
211 211 to indicate the starting point of what will be converted. Direct
212 212 access to the repository files is not needed, unless of course the
213 213 repository is ``:local:``. The conversion uses the top level
214 214 directory in the sandbox to find the CVS repository, and then uses
215 215 CVS rlog commands to find files to convert. This means that unless
216 216 a filemap is given, all files under the starting directory will be
217 217 converted, and that any directory reorganization in the CVS
218 218 sandbox is ignored.
219 219
220 220 The following options can be used with ``--config``:
221 221
222 222 :convert.cvsps.cache: Set to False to disable remote log caching,
223 223 for testing and debugging purposes. Default is True.
224 224
225 225 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
226 226 allowed between commits with identical user and log message in
227 227 a single changeset. When very large files were checked in as
228 228 part of a changeset then the default may not be long enough.
229 229 The default is 60.
230 230
231 :convert.cvsps.logencoding: Specify encoding name to be used for
232 transcoding CVS log messages. Multiple encoding names can be
233 specified as a list (see :hg:`help config.Syntax`), but only
234 the first acceptable encoding in the list is used per CVS log
235 entries. This transcoding is executed before cvslog hook below.
236
231 237 :convert.cvsps.mergeto: Specify a regular expression to which
232 238 commit log messages are matched. If a match occurs, then the
233 239 conversion process will insert a dummy revision merging the
234 240 branch on which this log message occurs to the branch
235 241 indicated in the regex. Default is ``{{mergetobranch
236 242 ([-\\w]+)}}``
237 243
238 244 :convert.cvsps.mergefrom: Specify a regular expression to which
239 245 commit log messages are matched. If a match occurs, then the
240 246 conversion process will add the most recent revision on the
241 247 branch indicated in the regex as the second parent of the
242 248 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
243 249
244 250 :convert.localtimezone: use local time (as determined by the TZ
245 251 environment variable) for changeset date/times. The default
246 252 is False (use UTC).
247 253
248 254 :hooks.cvslog: Specify a Python function to be called at the end of
249 255 gathering the CVS log. The function is passed a list with the
250 256 log entries, and can modify the entries in-place, or add or
251 257 delete them.
252 258
253 259 :hooks.cvschangesets: Specify a Python function to be called after
254 260 the changesets are calculated from the CVS log. The
255 261 function is passed a list with the changeset entries, and can
256 262 modify the changesets in-place, or add or delete them.
257 263
258 264 An additional "debugcvsps" Mercurial command allows the builtin
259 265 changeset merging code to be run without doing a conversion. Its
260 266 parameters and output are similar to that of cvsps 2.1. Please see
261 267 the command help for more details.
262 268
263 269 Subversion Source
264 270 #################
265 271
266 272 Subversion source detects classical trunk/branches/tags layouts.
267 273 By default, the supplied ``svn://repo/path/`` source URL is
268 274 converted as a single branch. If ``svn://repo/path/trunk`` exists
269 275 it replaces the default branch. If ``svn://repo/path/branches``
270 276 exists, its subdirectories are listed as possible branches. If
271 277 ``svn://repo/path/tags`` exists, it is looked for tags referencing
272 278 converted branches. Default ``trunk``, ``branches`` and ``tags``
273 279 values can be overridden with following options. Set them to paths
274 280 relative to the source URL, or leave them blank to disable auto
275 281 detection.
276 282
277 283 The following options can be set with ``--config``:
278 284
279 285 :convert.svn.branches: specify the directory containing branches.
280 286 The default is ``branches``.
281 287
282 288 :convert.svn.tags: specify the directory containing tags. The
283 289 default is ``tags``.
284 290
285 291 :convert.svn.trunk: specify the name of the trunk branch. The
286 292 default is ``trunk``.
287 293
288 294 :convert.localtimezone: use local time (as determined by the TZ
289 295 environment variable) for changeset date/times. The default
290 296 is False (use UTC).
291 297
292 298 Source history can be retrieved starting at a specific revision,
293 299 instead of being integrally converted. Only single branch
294 300 conversions are supported.
295 301
296 302 :convert.svn.startrev: specify start Subversion revision number.
297 303 The default is 0.
298 304
299 305 Git Source
300 306 ##########
301 307
302 308 The Git importer converts commits from all reachable branches (refs
303 309 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
304 310 Branches are converted to bookmarks with the same name, with the
305 311 leading 'refs/heads' stripped. Git submodules are converted to Git
306 312 subrepos in Mercurial.
307 313
308 314 The following options can be set with ``--config``:
309 315
310 316 :convert.git.similarity: specify how similar files modified in a
311 317 commit must be to be imported as renames or copies, as a
312 318 percentage between ``0`` (disabled) and ``100`` (files must be
313 319 identical). For example, ``90`` means that a delete/add pair will
314 320 be imported as a rename if more than 90% of the file hasn't
315 321 changed. The default is ``50``.
316 322
317 323 :convert.git.findcopiesharder: while detecting copies, look at all
318 324 files in the working copy instead of just changed ones. This
319 325 is very expensive for large projects, and is only effective when
320 326 ``convert.git.similarity`` is greater than 0. The default is False.
321 327
322 328 :convert.git.renamelimit: perform rename and copy detection up to this
323 329 many changed files in a commit. Increasing this will make rename
324 330 and copy detection more accurate but will significantly slow down
325 331 computation on large projects. The option is only relevant if
326 332 ``convert.git.similarity`` is greater than 0. The default is
327 333 ``400``.
328 334
329 335 :convert.git.committeractions: list of actions to take when processing
330 336 author and committer values.
331 337
332 338 Git commits have separate author (who wrote the commit) and committer
333 339 (who applied the commit) fields. Not all destinations support separate
334 340 author and committer fields (including Mercurial). This config option
335 341 controls what to do with these author and committer fields during
336 342 conversion.
337 343
338 344 A value of ``messagedifferent`` will append a ``committer: ...``
339 345 line to the commit message if the Git committer is different from the
340 346 author. The prefix of that line can be specified using the syntax
341 347 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
342 348 When a prefix is specified, a space will always be inserted between the
343 349 prefix and the value.
344 350
345 351 ``messagealways`` behaves like ``messagedifferent`` except it will
346 352 always result in a ``committer: ...`` line being appended to the commit
347 353 message. This value is mutually exclusive with ``messagedifferent``.
348 354
349 355 ``dropcommitter`` will remove references to the committer. Only
350 356 references to the author will remain. Actions that add references
351 357 to the committer will have no effect when this is set.
352 358
353 359 ``replaceauthor`` will replace the value of the author field with
354 360 the committer. Other actions that add references to the committer
355 361 will still take effect when this is set.
356 362
357 363 The default is ``messagedifferent``.
358 364
359 365 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
360 366 the destination. Some Git repositories store extra metadata in commits.
361 367 By default, this non-default metadata will be lost during conversion.
362 368 Setting this config option can retain that metadata. Some built-in
363 369 keys such as ``parent`` and ``branch`` are not allowed to be copied.
364 370
365 371 :convert.git.remoteprefix: remote refs are converted as bookmarks with
366 372 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
367 373 is 'remote'.
368 374
369 375 :convert.git.saverev: whether to store the original Git commit ID in the
370 376 metadata of the destination commit. The default is True.
371 377
372 378 :convert.git.skipsubmodules: does not convert root level .gitmodules files
373 379 or files with 160000 mode indicating a submodule. Default is False.
374 380
375 381 Perforce Source
376 382 ###############
377 383
378 384 The Perforce (P4) importer can be given a p4 depot path or a
379 385 client specification as source. It will convert all files in the
380 386 source to a flat Mercurial repository, ignoring labels, branches
381 387 and integrations. Note that when a depot path is given you then
382 388 usually should specify a target directory, because otherwise the
383 389 target may be named ``...-hg``.
384 390
385 391 The following options can be set with ``--config``:
386 392
387 393 :convert.p4.encoding: specify the encoding to use when decoding standard
388 394 output of the Perforce command line tool. The default is default system
389 395 encoding.
390 396
391 397 :convert.p4.startrev: specify initial Perforce revision (a
392 398 Perforce changelist number).
393 399
394 400 Mercurial Destination
395 401 #####################
396 402
397 403 The Mercurial destination will recognize Mercurial subrepositories in the
398 404 destination directory, and update the .hgsubstate file automatically if the
399 405 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
400 406 Converting a repository with subrepositories requires converting a single
401 407 repository at a time, from the bottom up.
402 408
403 409 .. container:: verbose
404 410
405 411 An example showing how to convert a repository with subrepositories::
406 412
407 413 # so convert knows the type when it sees a non empty destination
408 414 $ hg init converted
409 415
410 416 $ hg convert orig/sub1 converted/sub1
411 417 $ hg convert orig/sub2 converted/sub2
412 418 $ hg convert orig converted
413 419
414 420 The following options are supported:
415 421
416 422 :convert.hg.clonebranches: dispatch source branches in separate
417 423 clones. The default is False.
418 424
419 425 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
420 426 ``default``.
421 427
422 428 :convert.hg.usebranchnames: preserve branch names. The default is
423 429 True.
424 430
425 431 :convert.hg.sourcename: records the given string as a 'convert_source' extra
426 432 value on each commit made in the target repository. The default is None.
427 433
428 434 All Destinations
429 435 ################
430 436
431 437 All destination types accept the following options:
432 438
433 439 :convert.skiptags: does not convert tags from the source repo to the target
434 440 repo. The default is False.
435 441 """
436 442 return convcmd.convert(ui, src, dest, revmapfile, **opts)
437 443
438 444 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
439 445 def debugsvnlog(ui, **opts):
440 446 return subversion.debugsvnlog(ui, **opts)
441 447
442 448 @command('debugcvsps',
443 449 [
444 450 # Main options shared with cvsps-2.1
445 451 ('b', 'branches', [], _('only return changes on specified branches')),
446 452 ('p', 'prefix', '', _('prefix to remove from file names')),
447 453 ('r', 'revisions', [],
448 454 _('only return changes after or between specified tags')),
449 455 ('u', 'update-cache', None, _("update cvs log cache")),
450 456 ('x', 'new-cache', None, _("create new cvs log cache")),
451 457 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
452 458 ('', 'root', '', _('specify cvsroot')),
453 459 # Options specific to builtin cvsps
454 460 ('', 'parents', '', _('show parent changesets')),
455 461 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
456 462 # Options that are ignored for compatibility with cvsps-2.1
457 463 ('A', 'cvs-direct', None, _('ignored for compatibility')),
458 464 ],
459 465 _('hg debugcvsps [OPTION]... [PATH]...'),
460 466 norepo=True)
461 467 def debugcvsps(ui, *args, **opts):
462 468 '''create changeset information from CVS
463 469
464 470 This command is intended as a debugging tool for the CVS to
465 471 Mercurial converter, and can be used as a direct replacement for
466 472 cvsps.
467 473
468 474 Hg debugcvsps reads the CVS rlog for current directory (or any
469 475 named directory) in the CVS repository, and converts the log to a
470 476 series of changesets based on matching commit log entries and
471 477 dates.'''
472 478 return cvsps.debugcvsps(ui, *args, **opts)
473 479
474 480 def kwconverted(ctx, name):
475 481 rev = ctx.extra().get('convert_revision', '')
476 482 if rev.startswith('svn:'):
477 483 if name == 'svnrev':
478 484 return str(subversion.revsplit(rev)[2])
479 485 elif name == 'svnpath':
480 486 return subversion.revsplit(rev)[1]
481 487 elif name == 'svnuuid':
482 488 return subversion.revsplit(rev)[0]
483 489 return rev
484 490
485 491 templatekeyword = registrar.templatekeyword()
486 492
487 493 @templatekeyword('svnrev')
488 494 def kwsvnrev(repo, ctx, **args):
489 495 """String. Converted subversion revision number."""
490 496 return kwconverted(ctx, 'svnrev')
491 497
492 498 @templatekeyword('svnpath')
493 499 def kwsvnpath(repo, ctx, **args):
494 500 """String. Converted subversion revision project path."""
495 501 return kwconverted(ctx, 'svnpath')
496 502
497 503 @templatekeyword('svnuuid')
498 504 def kwsvnuuid(repo, ctx, **args):
499 505 """String. Converted subversion revision repository identifier."""
500 506 return kwconverted(ctx, 'svnuuid')
501 507
502 508 # tell hggettext to extract docstrings from these functions:
503 509 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,921 +1,951 b''
1 1 # Mercurial built-in replacement for cvsps.
2 2 #
3 3 # Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 from __future__ import absolute_import
8 8
9 9 import os
10 10 import re
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 encoding,
15 error,
15 16 hook,
16 17 pycompat,
17 18 util,
18 19 )
19 20
20 21 pickle = util.pickle
21 22
22 23 class logentry(object):
23 24 '''Class logentry has the following attributes:
24 25 .author - author name as CVS knows it
25 26 .branch - name of branch this revision is on
26 27 .branches - revision tuple of branches starting at this revision
27 28 .comment - commit message
28 29 .commitid - CVS commitid or None
29 30 .date - the commit date as a (time, tz) tuple
30 31 .dead - true if file revision is dead
31 32 .file - Name of file
32 33 .lines - a tuple (+lines, -lines) or None
33 34 .parent - Previous revision of this entry
34 35 .rcs - name of file as returned from CVS
35 36 .revision - revision number as tuple
36 37 .tags - list of tags on the file
37 38 .synthetic - is this a synthetic "file ... added on ..." revision?
38 39 .mergepoint - the branch that has been merged from (if present in
39 40 rlog output) or None
40 41 .branchpoints - the branches that start at the current entry or empty
41 42 '''
42 43 def __init__(self, **entries):
43 44 self.synthetic = False
44 45 self.__dict__.update(entries)
45 46
46 47 def __repr__(self):
47 48 items = ("%s=%r"%(k, self.__dict__[k]) for k in sorted(self.__dict__))
48 49 return "%s(%s)"%(type(self).__name__, ", ".join(items))
49 50
50 51 class logerror(Exception):
51 52 pass
52 53
53 54 def getrepopath(cvspath):
54 55 """Return the repository path from a CVS path.
55 56
56 57 >>> getrepopath('/foo/bar')
57 58 '/foo/bar'
58 59 >>> getrepopath('c:/foo/bar')
59 60 '/foo/bar'
60 61 >>> getrepopath(':pserver:10/foo/bar')
61 62 '/foo/bar'
62 63 >>> getrepopath(':pserver:10c:/foo/bar')
63 64 '/foo/bar'
64 65 >>> getrepopath(':pserver:/foo/bar')
65 66 '/foo/bar'
66 67 >>> getrepopath(':pserver:c:/foo/bar')
67 68 '/foo/bar'
68 69 >>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
69 70 '/foo/bar'
70 71 >>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
71 72 '/foo/bar'
72 73 >>> getrepopath('user@server/path/to/repository')
73 74 '/path/to/repository'
74 75 """
75 76 # According to CVS manual, CVS paths are expressed like:
76 77 # [:method:][[user][:password]@]hostname[:[port]]/path/to/repository
77 78 #
78 79 # CVSpath is splitted into parts and then position of the first occurrence
79 80 # of the '/' char after the '@' is located. The solution is the rest of the
80 81 # string after that '/' sign including it
81 82
82 83 parts = cvspath.split(':')
83 84 atposition = parts[-1].find('@')
84 85 start = 0
85 86
86 87 if atposition != -1:
87 88 start = atposition
88 89
89 90 repopath = parts[-1][parts[-1].find('/', start):]
90 91 return repopath
91 92
92 93 def createlog(ui, directory=None, root="", rlog=True, cache=None):
93 94 '''Collect the CVS rlog'''
94 95
95 96 # Because we store many duplicate commit log messages, reusing strings
96 97 # saves a lot of memory and pickle storage space.
97 98 _scache = {}
98 99 def scache(s):
99 100 "return a shared version of a string"
100 101 return _scache.setdefault(s, s)
101 102
102 103 ui.status(_('collecting CVS rlog\n'))
103 104
104 105 log = [] # list of logentry objects containing the CVS state
105 106
106 107 # patterns to match in CVS (r)log output, by state of use
107 108 re_00 = re.compile('RCS file: (.+)$')
108 109 re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
109 110 re_02 = re.compile('cvs (r?log|server): (.+)\n$')
110 111 re_03 = re.compile("(Cannot access.+CVSROOT)|"
111 112 "(can't create temporary directory.+)$")
112 113 re_10 = re.compile('Working file: (.+)$')
113 114 re_20 = re.compile('symbolic names:')
114 115 re_30 = re.compile('\t(.+): ([\\d.]+)$')
115 116 re_31 = re.compile('----------------------------$')
116 117 re_32 = re.compile('======================================='
117 118 '======================================$')
118 119 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
119 120 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);'
120 121 r'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?'
121 122 r'(\s+commitid:\s+([^;]+);)?'
122 123 r'(.*mergepoint:\s+([^;]+);)?')
123 124 re_70 = re.compile('branches: (.+);$')
124 125
125 126 file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch')
126 127
127 128 prefix = '' # leading path to strip of what we get from CVS
128 129
129 130 if directory is None:
130 131 # Current working directory
131 132
132 133 # Get the real directory in the repository
133 134 try:
134 135 prefix = open(os.path.join('CVS','Repository')).read().strip()
135 136 directory = prefix
136 137 if prefix == ".":
137 138 prefix = ""
138 139 except IOError:
139 140 raise logerror(_('not a CVS sandbox'))
140 141
141 142 if prefix and not prefix.endswith(pycompat.ossep):
142 143 prefix += pycompat.ossep
143 144
144 145 # Use the Root file in the sandbox, if it exists
145 146 try:
146 147 root = open(os.path.join('CVS','Root')).read().strip()
147 148 except IOError:
148 149 pass
149 150
150 151 if not root:
151 152 root = encoding.environ.get('CVSROOT', '')
152 153
153 154 # read log cache if one exists
154 155 oldlog = []
155 156 date = None
156 157
157 158 if cache:
158 159 cachedir = os.path.expanduser('~/.hg.cvsps')
159 160 if not os.path.exists(cachedir):
160 161 os.mkdir(cachedir)
161 162
162 163 # The cvsps cache pickle needs a uniquified name, based on the
163 164 # repository location. The address may have all sort of nasties
164 165 # in it, slashes, colons and such. So here we take just the
165 166 # alphanumeric characters, concatenated in a way that does not
166 167 # mix up the various components, so that
167 168 # :pserver:user@server:/path
168 169 # and
169 170 # /pserver/user/server/path
170 171 # are mapped to different cache file names.
171 172 cachefile = root.split(":") + [directory, "cache"]
172 173 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
173 174 cachefile = os.path.join(cachedir,
174 175 '.'.join([s for s in cachefile if s]))
175 176
176 177 if cache == 'update':
177 178 try:
178 179 ui.note(_('reading cvs log cache %s\n') % cachefile)
179 180 oldlog = pickle.load(open(cachefile))
180 181 for e in oldlog:
181 182 if not (util.safehasattr(e, 'branchpoints') and
182 183 util.safehasattr(e, 'commitid') and
183 184 util.safehasattr(e, 'mergepoint')):
184 185 ui.status(_('ignoring old cache\n'))
185 186 oldlog = []
186 187 break
187 188
188 189 ui.note(_('cache has %d log entries\n') % len(oldlog))
189 190 except Exception as e:
190 191 ui.note(_('error reading cache: %r\n') % e)
191 192
192 193 if oldlog:
193 194 date = oldlog[-1].date # last commit date as a (time,tz) tuple
194 195 date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
195 196
196 197 # build the CVS commandline
197 198 cmd = ['cvs', '-q']
198 199 if root:
199 200 cmd.append('-d%s' % root)
200 201 p = util.normpath(getrepopath(root))
201 202 if not p.endswith('/'):
202 203 p += '/'
203 204 if prefix:
204 205 # looks like normpath replaces "" by "."
205 206 prefix = p + util.normpath(prefix)
206 207 else:
207 208 prefix = p
208 209 cmd.append(['log', 'rlog'][rlog])
209 210 if date:
210 211 # no space between option and date string
211 212 cmd.append('-d>%s' % date)
212 213 cmd.append(directory)
213 214
214 215 # state machine begins here
215 216 tags = {} # dictionary of revisions on current file with their tags
216 217 branchmap = {} # mapping between branch names and revision numbers
217 218 rcsmap = {}
218 219 state = 0
219 220 store = False # set when a new record can be appended
220 221
221 222 cmd = [util.shellquote(arg) for arg in cmd]
222 223 ui.note(_("running %s\n") % (' '.join(cmd)))
223 224 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
224 225
225 226 pfp = util.popen(' '.join(cmd))
226 227 peek = pfp.readline()
227 228 while True:
228 229 line = peek
229 230 if line == '':
230 231 break
231 232 peek = pfp.readline()
232 233 if line.endswith('\n'):
233 234 line = line[:-1]
234 235 #ui.debug('state=%d line=%r\n' % (state, line))
235 236
236 237 if state == 0:
237 238 # initial state, consume input until we see 'RCS file'
238 239 match = re_00.match(line)
239 240 if match:
240 241 rcs = match.group(1)
241 242 tags = {}
242 243 if rlog:
243 244 filename = util.normpath(rcs[:-2])
244 245 if filename.startswith(prefix):
245 246 filename = filename[len(prefix):]
246 247 if filename.startswith('/'):
247 248 filename = filename[1:]
248 249 if filename.startswith('Attic/'):
249 250 filename = filename[6:]
250 251 else:
251 252 filename = filename.replace('/Attic/', '/')
252 253 state = 2
253 254 continue
254 255 state = 1
255 256 continue
256 257 match = re_01.match(line)
257 258 if match:
258 259 raise logerror(match.group(1))
259 260 match = re_02.match(line)
260 261 if match:
261 262 raise logerror(match.group(2))
262 263 if re_03.match(line):
263 264 raise logerror(line)
264 265
265 266 elif state == 1:
266 267 # expect 'Working file' (only when using log instead of rlog)
267 268 match = re_10.match(line)
268 269 assert match, _('RCS file must be followed by working file')
269 270 filename = util.normpath(match.group(1))
270 271 state = 2
271 272
272 273 elif state == 2:
273 274 # expect 'symbolic names'
274 275 if re_20.match(line):
275 276 branchmap = {}
276 277 state = 3
277 278
278 279 elif state == 3:
279 280 # read the symbolic names and store as tags
280 281 match = re_30.match(line)
281 282 if match:
282 283 rev = [int(x) for x in match.group(2).split('.')]
283 284
284 285 # Convert magic branch number to an odd-numbered one
285 286 revn = len(rev)
286 287 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
287 288 rev = rev[:-2] + rev[-1:]
288 289 rev = tuple(rev)
289 290
290 291 if rev not in tags:
291 292 tags[rev] = []
292 293 tags[rev].append(match.group(1))
293 294 branchmap[match.group(1)] = match.group(2)
294 295
295 296 elif re_31.match(line):
296 297 state = 5
297 298 elif re_32.match(line):
298 299 state = 0
299 300
300 301 elif state == 4:
301 302 # expecting '------' separator before first revision
302 303 if re_31.match(line):
303 304 state = 5
304 305 else:
305 306 assert not re_32.match(line), _('must have at least '
306 307 'some revisions')
307 308
308 309 elif state == 5:
309 310 # expecting revision number and possibly (ignored) lock indication
310 311 # we create the logentry here from values stored in states 0 to 4,
311 312 # as this state is re-entered for subsequent revisions of a file.
312 313 match = re_50.match(line)
313 314 assert match, _('expected revision number')
314 315 e = logentry(rcs=scache(rcs),
315 316 file=scache(filename),
316 317 revision=tuple([int(x) for x in
317 318 match.group(1).split('.')]),
318 319 branches=[],
319 320 parent=None,
320 321 commitid=None,
321 322 mergepoint=None,
322 323 branchpoints=set())
323 324
324 325 state = 6
325 326
326 327 elif state == 6:
327 328 # expecting date, author, state, lines changed
328 329 match = re_60.match(line)
329 330 assert match, _('revision must be followed by date line')
330 331 d = match.group(1)
331 332 if d[2] == '/':
332 333 # Y2K
333 334 d = '19' + d
334 335
335 336 if len(d.split()) != 3:
336 337 # cvs log dates always in GMT
337 338 d = d + ' UTC'
338 339 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S',
339 340 '%Y/%m/%d %H:%M:%S',
340 341 '%Y-%m-%d %H:%M:%S'])
341 342 e.author = scache(match.group(2))
342 343 e.dead = match.group(3).lower() == 'dead'
343 344
344 345 if match.group(5):
345 346 if match.group(6):
346 347 e.lines = (int(match.group(5)), int(match.group(6)))
347 348 else:
348 349 e.lines = (int(match.group(5)), 0)
349 350 elif match.group(6):
350 351 e.lines = (0, int(match.group(6)))
351 352 else:
352 353 e.lines = None
353 354
354 355 if match.group(7): # cvs 1.12 commitid
355 356 e.commitid = match.group(8)
356 357
357 358 if match.group(9): # cvsnt mergepoint
358 359 myrev = match.group(10).split('.')
359 360 if len(myrev) == 2: # head
360 361 e.mergepoint = 'HEAD'
361 362 else:
362 363 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]])
363 364 branches = [b for b in branchmap if branchmap[b] == myrev]
364 365 assert len(branches) == 1, ('unknown branch: %s'
365 366 % e.mergepoint)
366 367 e.mergepoint = branches[0]
367 368
368 369 e.comment = []
369 370 state = 7
370 371
371 372 elif state == 7:
372 373 # read the revision numbers of branches that start at this revision
373 374 # or store the commit log message otherwise
374 375 m = re_70.match(line)
375 376 if m:
376 377 e.branches = [tuple([int(y) for y in x.strip().split('.')])
377 378 for x in m.group(1).split(';')]
378 379 state = 8
379 380 elif re_31.match(line) and re_50.match(peek):
380 381 state = 5
381 382 store = True
382 383 elif re_32.match(line):
383 384 state = 0
384 385 store = True
385 386 else:
386 387 e.comment.append(line)
387 388
388 389 elif state == 8:
389 390 # store commit log message
390 391 if re_31.match(line):
391 392 cpeek = peek
392 393 if cpeek.endswith('\n'):
393 394 cpeek = cpeek[:-1]
394 395 if re_50.match(cpeek):
395 396 state = 5
396 397 store = True
397 398 else:
398 399 e.comment.append(line)
399 400 elif re_32.match(line):
400 401 state = 0
401 402 store = True
402 403 else:
403 404 e.comment.append(line)
404 405
405 406 # When a file is added on a branch B1, CVS creates a synthetic
406 407 # dead trunk revision 1.1 so that the branch has a root.
407 408 # Likewise, if you merge such a file to a later branch B2 (one
408 409 # that already existed when the file was added on B1), CVS
409 410 # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop
410 411 # these revisions now, but mark them synthetic so
411 412 # createchangeset() can take care of them.
412 413 if (store and
413 414 e.dead and
414 415 e.revision[-1] == 1 and # 1.1 or 1.1.x.1
415 416 len(e.comment) == 1 and
416 417 file_added_re.match(e.comment[0])):
417 418 ui.debug('found synthetic revision in %s: %r\n'
418 419 % (e.rcs, e.comment[0]))
419 420 e.synthetic = True
420 421
421 422 if store:
422 423 # clean up the results and save in the log.
423 424 store = False
424 425 e.tags = sorted([scache(x) for x in tags.get(e.revision, [])])
425 426 e.comment = scache('\n'.join(e.comment))
426 427
427 428 revn = len(e.revision)
428 429 if revn > 3 and (revn % 2) == 0:
429 430 e.branch = tags.get(e.revision[:-1], [None])[0]
430 431 else:
431 432 e.branch = None
432 433
433 434 # find the branches starting from this revision
434 435 branchpoints = set()
435 436 for branch, revision in branchmap.iteritems():
436 437 revparts = tuple([int(i) for i in revision.split('.')])
437 438 if len(revparts) < 2: # bad tags
438 439 continue
439 440 if revparts[-2] == 0 and revparts[-1] % 2 == 0:
440 441 # normal branch
441 442 if revparts[:-2] == e.revision:
442 443 branchpoints.add(branch)
443 444 elif revparts == (1, 1, 1): # vendor branch
444 445 if revparts in e.branches:
445 446 branchpoints.add(branch)
446 447 e.branchpoints = branchpoints
447 448
448 449 log.append(e)
449 450
450 451 rcsmap[e.rcs.replace('/Attic/', '/')] = e.rcs
451 452
452 453 if len(log) % 100 == 0:
453 454 ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
454 455
455 456 log.sort(key=lambda x: (x.rcs, x.revision))
456 457
457 458 # find parent revisions of individual files
458 459 versions = {}
459 460 for e in sorted(oldlog, key=lambda x: (x.rcs, x.revision)):
460 461 rcs = e.rcs.replace('/Attic/', '/')
461 462 if rcs in rcsmap:
462 463 e.rcs = rcsmap[rcs]
463 464 branch = e.revision[:-1]
464 465 versions[(e.rcs, branch)] = e.revision
465 466
466 467 for e in log:
467 468 branch = e.revision[:-1]
468 469 p = versions.get((e.rcs, branch), None)
469 470 if p is None:
470 471 p = e.revision[:-2]
471 472 e.parent = p
472 473 versions[(e.rcs, branch)] = e.revision
473 474
474 475 # update the log cache
475 476 if cache:
476 477 if log:
477 478 # join up the old and new logs
478 479 log.sort(key=lambda x: x.date)
479 480
480 481 if oldlog and oldlog[-1].date >= log[0].date:
481 482 raise logerror(_('log cache overlaps with new log entries,'
482 483 ' re-run without cache.'))
483 484
484 485 log = oldlog + log
485 486
486 487 # write the new cachefile
487 488 ui.note(_('writing cvs log cache %s\n') % cachefile)
488 489 pickle.dump(log, open(cachefile, 'w'))
489 490 else:
490 491 log = oldlog
491 492
492 493 ui.status(_('%d log entries\n') % len(log))
493 494
495 encodings = ui.configlist('convert', 'cvsps.logencoding')
496 if encodings:
497 def revstr(r):
498 # this is needed, because logentry.revision is a tuple of "int"
499 # (e.g. (1, 2) for "1.2")
500 return '.'.join(pycompat.maplist(pycompat.bytestr, r))
501
502 for entry in log:
503 comment = entry.comment
504 for e in encodings:
505 try:
506 entry.comment = comment.decode(e).encode('utf-8')
507 if ui.debugflag:
508 ui.debug("transcoding by %s: %s of %s\n" %
509 (e, revstr(entry.revision), entry.file))
510 break
511 except UnicodeDecodeError:
512 pass # try next encoding
513 except LookupError as inst: # unknown encoding, maybe
514 raise error.Abort(inst,
515 hint=_('check convert.cvsps.logencoding'
516 ' configuration'))
517 else:
518 raise error.Abort(_("no encoding can transcode"
519 " CVS log message for %s of %s")
520 % (revstr(entry.revision), entry.file),
521 hint=_('check convert.cvsps.logencoding'
522 ' configuration'))
523
494 524 hook.hook(ui, None, "cvslog", True, log=log)
495 525
496 526 return log
497 527
498 528
499 529 class changeset(object):
500 530 '''Class changeset has the following attributes:
501 531 .id - integer identifying this changeset (list index)
502 532 .author - author name as CVS knows it
503 533 .branch - name of branch this changeset is on, or None
504 534 .comment - commit message
505 535 .commitid - CVS commitid or None
506 536 .date - the commit date as a (time,tz) tuple
507 537 .entries - list of logentry objects in this changeset
508 538 .parents - list of one or two parent changesets
509 539 .tags - list of tags on this changeset
510 540 .synthetic - from synthetic revision "file ... added on branch ..."
511 541 .mergepoint- the branch that has been merged from or None
512 542 .branchpoints- the branches that start at the current entry or empty
513 543 '''
514 544 def __init__(self, **entries):
515 545 self.id = None
516 546 self.synthetic = False
517 547 self.__dict__.update(entries)
518 548
519 549 def __repr__(self):
520 550 items = ("%s=%r"%(k, self.__dict__[k]) for k in sorted(self.__dict__))
521 551 return "%s(%s)"%(type(self).__name__, ", ".join(items))
522 552
523 553 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
524 554 '''Convert log into changesets.'''
525 555
526 556 ui.status(_('creating changesets\n'))
527 557
528 558 # try to order commitids by date
529 559 mindate = {}
530 560 for e in log:
531 561 if e.commitid:
532 562 mindate[e.commitid] = min(e.date, mindate.get(e.commitid))
533 563
534 564 # Merge changesets
535 565 log.sort(key=lambda x: (mindate.get(x.commitid), x.commitid, x.comment,
536 566 x.author, x.branch, x.date, x.branchpoints))
537 567
538 568 changesets = []
539 569 files = set()
540 570 c = None
541 571 for i, e in enumerate(log):
542 572
543 573 # Check if log entry belongs to the current changeset or not.
544 574
545 575 # Since CVS is file-centric, two different file revisions with
546 576 # different branchpoints should be treated as belonging to two
547 577 # different changesets (and the ordering is important and not
548 578 # honoured by cvsps at this point).
549 579 #
550 580 # Consider the following case:
551 581 # foo 1.1 branchpoints: [MYBRANCH]
552 582 # bar 1.1 branchpoints: [MYBRANCH, MYBRANCH2]
553 583 #
554 584 # Here foo is part only of MYBRANCH, but not MYBRANCH2, e.g. a
555 585 # later version of foo may be in MYBRANCH2, so foo should be the
556 586 # first changeset and bar the next and MYBRANCH and MYBRANCH2
557 587 # should both start off of the bar changeset. No provisions are
558 588 # made to ensure that this is, in fact, what happens.
559 589 if not (c and e.branchpoints == c.branchpoints and
560 590 (# cvs commitids
561 591 (e.commitid is not None and e.commitid == c.commitid) or
562 592 (# no commitids, use fuzzy commit detection
563 593 (e.commitid is None or c.commitid is None) and
564 594 e.comment == c.comment and
565 595 e.author == c.author and
566 596 e.branch == c.branch and
567 597 ((c.date[0] + c.date[1]) <=
568 598 (e.date[0] + e.date[1]) <=
569 599 (c.date[0] + c.date[1]) + fuzz) and
570 600 e.file not in files))):
571 601 c = changeset(comment=e.comment, author=e.author,
572 602 branch=e.branch, date=e.date,
573 603 entries=[], mergepoint=e.mergepoint,
574 604 branchpoints=e.branchpoints, commitid=e.commitid)
575 605 changesets.append(c)
576 606
577 607 files = set()
578 608 if len(changesets) % 100 == 0:
579 609 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
580 610 ui.status(util.ellipsis(t, 80) + '\n')
581 611
582 612 c.entries.append(e)
583 613 files.add(e.file)
584 614 c.date = e.date # changeset date is date of latest commit in it
585 615
586 616 # Mark synthetic changesets
587 617
588 618 for c in changesets:
589 619 # Synthetic revisions always get their own changeset, because
590 620 # the log message includes the filename. E.g. if you add file3
591 621 # and file4 on a branch, you get four log entries and three
592 622 # changesets:
593 623 # "File file3 was added on branch ..." (synthetic, 1 entry)
594 624 # "File file4 was added on branch ..." (synthetic, 1 entry)
595 625 # "Add file3 and file4 to fix ..." (real, 2 entries)
596 626 # Hence the check for 1 entry here.
597 627 c.synthetic = len(c.entries) == 1 and c.entries[0].synthetic
598 628
599 629 # Sort files in each changeset
600 630
601 631 def entitycompare(l, r):
602 632 'Mimic cvsps sorting order'
603 633 l = l.file.split('/')
604 634 r = r.file.split('/')
605 635 nl = len(l)
606 636 nr = len(r)
607 637 n = min(nl, nr)
608 638 for i in range(n):
609 639 if i + 1 == nl and nl < nr:
610 640 return -1
611 641 elif i + 1 == nr and nl > nr:
612 642 return +1
613 643 elif l[i] < r[i]:
614 644 return -1
615 645 elif l[i] > r[i]:
616 646 return +1
617 647 return 0
618 648
619 649 for c in changesets:
620 650 c.entries.sort(entitycompare)
621 651
622 652 # Sort changesets by date
623 653
624 654 odd = set()
625 655 def cscmp(l, r):
626 656 d = sum(l.date) - sum(r.date)
627 657 if d:
628 658 return d
629 659
630 660 # detect vendor branches and initial commits on a branch
631 661 le = {}
632 662 for e in l.entries:
633 663 le[e.rcs] = e.revision
634 664 re = {}
635 665 for e in r.entries:
636 666 re[e.rcs] = e.revision
637 667
638 668 d = 0
639 669 for e in l.entries:
640 670 if re.get(e.rcs, None) == e.parent:
641 671 assert not d
642 672 d = 1
643 673 break
644 674
645 675 for e in r.entries:
646 676 if le.get(e.rcs, None) == e.parent:
647 677 if d:
648 678 odd.add((l, r))
649 679 d = -1
650 680 break
651 681 # By this point, the changesets are sufficiently compared that
652 682 # we don't really care about ordering. However, this leaves
653 683 # some race conditions in the tests, so we compare on the
654 684 # number of files modified, the files contained in each
655 685 # changeset, and the branchpoints in the change to ensure test
656 686 # output remains stable.
657 687
658 688 # recommended replacement for cmp from
659 689 # https://docs.python.org/3.0/whatsnew/3.0.html
660 690 c = lambda x, y: (x > y) - (x < y)
661 691 # Sort bigger changes first.
662 692 if not d:
663 693 d = c(len(l.entries), len(r.entries))
664 694 # Try sorting by filename in the change.
665 695 if not d:
666 696 d = c([e.file for e in l.entries], [e.file for e in r.entries])
667 697 # Try and put changes without a branch point before ones with
668 698 # a branch point.
669 699 if not d:
670 700 d = c(len(l.branchpoints), len(r.branchpoints))
671 701 return d
672 702
673 703 changesets.sort(cscmp)
674 704
675 705 # Collect tags
676 706
677 707 globaltags = {}
678 708 for c in changesets:
679 709 for e in c.entries:
680 710 for tag in e.tags:
681 711 # remember which is the latest changeset to have this tag
682 712 globaltags[tag] = c
683 713
684 714 for c in changesets:
685 715 tags = set()
686 716 for e in c.entries:
687 717 tags.update(e.tags)
688 718 # remember tags only if this is the latest changeset to have it
689 719 c.tags = sorted(tag for tag in tags if globaltags[tag] is c)
690 720
691 721 # Find parent changesets, handle {{mergetobranch BRANCHNAME}}
692 722 # by inserting dummy changesets with two parents, and handle
693 723 # {{mergefrombranch BRANCHNAME}} by setting two parents.
694 724
695 725 if mergeto is None:
696 726 mergeto = r'{{mergetobranch ([-\w]+)}}'
697 727 if mergeto:
698 728 mergeto = re.compile(mergeto)
699 729
700 730 if mergefrom is None:
701 731 mergefrom = r'{{mergefrombranch ([-\w]+)}}'
702 732 if mergefrom:
703 733 mergefrom = re.compile(mergefrom)
704 734
705 735 versions = {} # changeset index where we saw any particular file version
706 736 branches = {} # changeset index where we saw a branch
707 737 n = len(changesets)
708 738 i = 0
709 739 while i < n:
710 740 c = changesets[i]
711 741
712 742 for f in c.entries:
713 743 versions[(f.rcs, f.revision)] = i
714 744
715 745 p = None
716 746 if c.branch in branches:
717 747 p = branches[c.branch]
718 748 else:
719 749 # first changeset on a new branch
720 750 # the parent is a changeset with the branch in its
721 751 # branchpoints such that it is the latest possible
722 752 # commit without any intervening, unrelated commits.
723 753
724 754 for candidate in xrange(i):
725 755 if c.branch not in changesets[candidate].branchpoints:
726 756 if p is not None:
727 757 break
728 758 continue
729 759 p = candidate
730 760
731 761 c.parents = []
732 762 if p is not None:
733 763 p = changesets[p]
734 764
735 765 # Ensure no changeset has a synthetic changeset as a parent.
736 766 while p.synthetic:
737 767 assert len(p.parents) <= 1, \
738 768 _('synthetic changeset cannot have multiple parents')
739 769 if p.parents:
740 770 p = p.parents[0]
741 771 else:
742 772 p = None
743 773 break
744 774
745 775 if p is not None:
746 776 c.parents.append(p)
747 777
748 778 if c.mergepoint:
749 779 if c.mergepoint == 'HEAD':
750 780 c.mergepoint = None
751 781 c.parents.append(changesets[branches[c.mergepoint]])
752 782
753 783 if mergefrom:
754 784 m = mergefrom.search(c.comment)
755 785 if m:
756 786 m = m.group(1)
757 787 if m == 'HEAD':
758 788 m = None
759 789 try:
760 790 candidate = changesets[branches[m]]
761 791 except KeyError:
762 792 ui.warn(_("warning: CVS commit message references "
763 793 "non-existent branch %r:\n%s\n")
764 794 % (m, c.comment))
765 795 if m in branches and c.branch != m and not candidate.synthetic:
766 796 c.parents.append(candidate)
767 797
768 798 if mergeto:
769 799 m = mergeto.search(c.comment)
770 800 if m:
771 801 if m.groups():
772 802 m = m.group(1)
773 803 if m == 'HEAD':
774 804 m = None
775 805 else:
776 806 m = None # if no group found then merge to HEAD
777 807 if m in branches and c.branch != m:
778 808 # insert empty changeset for merge
779 809 cc = changeset(
780 810 author=c.author, branch=m, date=c.date,
781 811 comment='convert-repo: CVS merge from branch %s'
782 812 % c.branch,
783 813 entries=[], tags=[],
784 814 parents=[changesets[branches[m]], c])
785 815 changesets.insert(i + 1, cc)
786 816 branches[m] = i + 1
787 817
788 818 # adjust our loop counters now we have inserted a new entry
789 819 n += 1
790 820 i += 2
791 821 continue
792 822
793 823 branches[c.branch] = i
794 824 i += 1
795 825
796 826 # Drop synthetic changesets (safe now that we have ensured no other
797 827 # changesets can have them as parents).
798 828 i = 0
799 829 while i < len(changesets):
800 830 if changesets[i].synthetic:
801 831 del changesets[i]
802 832 else:
803 833 i += 1
804 834
805 835 # Number changesets
806 836
807 837 for i, c in enumerate(changesets):
808 838 c.id = i + 1
809 839
810 840 if odd:
811 841 for l, r in odd:
812 842 if l.id is not None and r.id is not None:
813 843 ui.warn(_('changeset %d is both before and after %d\n')
814 844 % (l.id, r.id))
815 845
816 846 ui.status(_('%d changeset entries\n') % len(changesets))
817 847
818 848 hook.hook(ui, None, "cvschangesets", True, changesets=changesets)
819 849
820 850 return changesets
821 851
822 852
823 853 def debugcvsps(ui, *args, **opts):
824 854 '''Read CVS rlog for current directory or named path in
825 855 repository, and convert the log to changesets based on matching
826 856 commit log entries and dates.
827 857 '''
828 858 if opts["new_cache"]:
829 859 cache = "write"
830 860 elif opts["update_cache"]:
831 861 cache = "update"
832 862 else:
833 863 cache = None
834 864
835 865 revisions = opts["revisions"]
836 866
837 867 try:
838 868 if args:
839 869 log = []
840 870 for d in args:
841 871 log += createlog(ui, d, root=opts["root"], cache=cache)
842 872 else:
843 873 log = createlog(ui, root=opts["root"], cache=cache)
844 874 except logerror as e:
845 875 ui.write("%r\n"%e)
846 876 return
847 877
848 878 changesets = createchangeset(ui, log, opts["fuzz"])
849 879 del log
850 880
851 881 # Print changesets (optionally filtered)
852 882
853 883 off = len(revisions)
854 884 branches = {} # latest version number in each branch
855 885 ancestors = {} # parent branch
856 886 for cs in changesets:
857 887
858 888 if opts["ancestors"]:
859 889 if cs.branch not in branches and cs.parents and cs.parents[0].id:
860 890 ancestors[cs.branch] = (changesets[cs.parents[0].id - 1].branch,
861 891 cs.parents[0].id)
862 892 branches[cs.branch] = cs.id
863 893
864 894 # limit by branches
865 895 if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]:
866 896 continue
867 897
868 898 if not off:
869 899 # Note: trailing spaces on several lines here are needed to have
870 900 # bug-for-bug compatibility with cvsps.
871 901 ui.write('---------------------\n')
872 902 ui.write(('PatchSet %d \n' % cs.id))
873 903 ui.write(('Date: %s\n' % util.datestr(cs.date,
874 904 '%Y/%m/%d %H:%M:%S %1%2')))
875 905 ui.write(('Author: %s\n' % cs.author))
876 906 ui.write(('Branch: %s\n' % (cs.branch or 'HEAD')))
877 907 ui.write(('Tag%s: %s \n' % (['', 's'][len(cs.tags) > 1],
878 908 ','.join(cs.tags) or '(none)')))
879 909 if cs.branchpoints:
880 910 ui.write(('Branchpoints: %s \n') %
881 911 ', '.join(sorted(cs.branchpoints)))
882 912 if opts["parents"] and cs.parents:
883 913 if len(cs.parents) > 1:
884 914 ui.write(('Parents: %s\n' %
885 915 (','.join([str(p.id) for p in cs.parents]))))
886 916 else:
887 917 ui.write(('Parent: %d\n' % cs.parents[0].id))
888 918
889 919 if opts["ancestors"]:
890 920 b = cs.branch
891 921 r = []
892 922 while b:
893 923 b, c = ancestors[b]
894 924 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
895 925 if r:
896 926 ui.write(('Ancestors: %s\n' % (','.join(r))))
897 927
898 928 ui.write(('Log:\n'))
899 929 ui.write('%s\n\n' % cs.comment)
900 930 ui.write(('Members: \n'))
901 931 for f in cs.entries:
902 932 fn = f.file
903 933 if fn.startswith(opts["prefix"]):
904 934 fn = fn[len(opts["prefix"]):]
905 935 ui.write('\t%s:%s->%s%s \n' % (
906 936 fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
907 937 '.'.join([str(x) for x in f.revision]),
908 938 ['', '(DEAD)'][f.dead]))
909 939 ui.write('\n')
910 940
911 941 # have we seen the start tag?
912 942 if revisions and off:
913 943 if revisions[0] == str(cs.id) or \
914 944 revisions[0] in cs.tags:
915 945 off = False
916 946
917 947 # see if we reached the end tag
918 948 if len(revisions) > 1 and not off:
919 949 if revisions[1] == str(cs.id) or \
920 950 revisions[1] in cs.tags:
921 951 break
@@ -1,500 +1,654 b''
1 1 #require cvs
2 2
3 3 $ cvscall()
4 4 > {
5 5 > cvs -f "$@"
6 6 > }
7 7 $ hgcat()
8 8 > {
9 9 > hg --cwd src-hg cat -r tip "$1"
10 10 > }
11 11 $ echo "[extensions]" >> $HGRCPATH
12 12 $ echo "convert = " >> $HGRCPATH
13 13 $ cat > cvshooks.py <<EOF
14 14 > def cvslog(ui,repo,hooktype,log):
15 15 > print "%s hook: %d entries"%(hooktype,len(log))
16 16 >
17 17 > def cvschangesets(ui,repo,hooktype,changesets):
18 18 > print "%s hook: %d changesets"%(hooktype,len(changesets))
19 19 > EOF
20 20 $ hookpath=`pwd`
21 21 $ cat <<EOF >> $HGRCPATH
22 22 > [hooks]
23 23 > cvslog = python:$hookpath/cvshooks.py:cvslog
24 24 > cvschangesets = python:$hookpath/cvshooks.py:cvschangesets
25 25 > EOF
26 26
27 27 create cvs repository
28 28
29 29 $ mkdir cvsrepo
30 30 $ cd cvsrepo
31 31 $ CVSROOT=`pwd`
32 32 $ export CVSROOT
33 33 $ CVS_OPTIONS=-f
34 34 $ export CVS_OPTIONS
35 35 $ cd ..
36 36 $ rmdir cvsrepo
37 37 $ cvscall -q -d "$CVSROOT" init
38 38
39 39 create source directory
40 40
41 41 $ mkdir src-temp
42 42 $ cd src-temp
43 43 $ echo a > a
44 44 $ mkdir b
45 45 $ cd b
46 46 $ echo c > c
47 47 $ cd ..
48 48
49 49 import source directory
50 50
51 51 $ cvscall -q import -m import src INITIAL start
52 52 N src/a
53 53 N src/b/c
54 54
55 55 No conflicts created by this import
56 56
57 57 $ cd ..
58 58
59 59 checkout source directory
60 60
61 61 $ cvscall -q checkout src
62 62 U src/a
63 63 U src/b/c
64 64
65 65 commit a new revision changing b/c
66 66
67 67 $ cd src
68 68 $ sleep 1
69 69 $ echo c >> b/c
70 70 $ cvscall -q commit -mci0 . | grep '<--'
71 71 $TESTTMP/cvsrepo/src/b/c,v <-- *c (glob)
72 72 $ cd ..
73 73
74 74 convert fresh repo and also check localtimezone option
75 75
76 76 NOTE: This doesn't check all time zones -- it merely determines that
77 77 the configuration option is taking effect.
78 78
79 79 An arbitrary (U.S.) time zone is used here. TZ=US/Hawaii is selected
80 80 since it does not use DST (unlike other U.S. time zones) and is always
81 81 a fixed difference from UTC.
82 82
83 83 $ TZ=US/Hawaii hg convert --config convert.localtimezone=True src src-hg
84 84 initializing destination src-hg repository
85 85 connecting to $TESTTMP/cvsrepo
86 86 scanning source...
87 87 collecting CVS rlog
88 88 5 log entries
89 89 cvslog hook: 5 entries
90 90 creating changesets
91 91 3 changeset entries
92 92 cvschangesets hook: 3 changesets
93 93 sorting...
94 94 converting...
95 95 2 Initial revision
96 96 1 ci0
97 97 0 import
98 98 updating tags
99 99 $ hgcat a
100 100 a
101 101 $ hgcat b/c
102 102 c
103 103 c
104 104
105 105 convert fresh repo with --filemap
106 106
107 107 $ echo include b/c > filemap
108 108 $ hg convert --filemap filemap src src-filemap
109 109 initializing destination src-filemap repository
110 110 connecting to $TESTTMP/cvsrepo
111 111 scanning source...
112 112 collecting CVS rlog
113 113 5 log entries
114 114 cvslog hook: 5 entries
115 115 creating changesets
116 116 3 changeset entries
117 117 cvschangesets hook: 3 changesets
118 118 sorting...
119 119 converting...
120 120 2 Initial revision
121 121 1 ci0
122 122 0 import
123 123 filtering out empty revision
124 124 repository tip rolled back to revision 1 (undo convert)
125 125 updating tags
126 126 $ hgcat b/c
127 127 c
128 128 c
129 129 $ hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
130 130 2 update tags files: .hgtags
131 131 1 ci0 files: b/c
132 132 0 Initial revision files: b/c
133 133
134 134 convert full repository (issue1649)
135 135
136 136 $ cvscall -q -d "$CVSROOT" checkout -d srcfull "." | grep -v CVSROOT
137 137 U srcfull/src/a
138 138 U srcfull/src/b/c
139 139 $ ls srcfull
140 140 CVS
141 141 CVSROOT
142 142 src
143 143 $ hg convert srcfull srcfull-hg \
144 144 > | grep -v 'log entries' | grep -v 'hook:' \
145 145 > | grep -v '^[0-3] .*' # filter instable changeset order
146 146 initializing destination srcfull-hg repository
147 147 connecting to $TESTTMP/cvsrepo
148 148 scanning source...
149 149 collecting CVS rlog
150 150 creating changesets
151 151 4 changeset entries
152 152 sorting...
153 153 converting...
154 154 updating tags
155 155 $ hg cat -r tip --cwd srcfull-hg src/a
156 156 a
157 157 $ hg cat -r tip --cwd srcfull-hg src/b/c
158 158 c
159 159 c
160 160
161 161 commit new file revisions
162 162
163 163 $ cd src
164 164 $ echo a >> a
165 165 $ echo c >> b/c
166 166 $ cvscall -q commit -mci1 . | grep '<--'
167 167 $TESTTMP/cvsrepo/src/a,v <-- a
168 168 $TESTTMP/cvsrepo/src/b/c,v <-- *c (glob)
169 169 $ cd ..
170 170
171 171 convert again
172 172
173 173 $ TZ=US/Hawaii hg convert --config convert.localtimezone=True src src-hg
174 174 connecting to $TESTTMP/cvsrepo
175 175 scanning source...
176 176 collecting CVS rlog
177 177 7 log entries
178 178 cvslog hook: 7 entries
179 179 creating changesets
180 180 4 changeset entries
181 181 cvschangesets hook: 4 changesets
182 182 sorting...
183 183 converting...
184 184 0 ci1
185 185 $ hgcat a
186 186 a
187 187 a
188 188 $ hgcat b/c
189 189 c
190 190 c
191 191 c
192 192
193 193 convert again with --filemap
194 194
195 195 $ hg convert --filemap filemap src src-filemap
196 196 connecting to $TESTTMP/cvsrepo
197 197 scanning source...
198 198 collecting CVS rlog
199 199 7 log entries
200 200 cvslog hook: 7 entries
201 201 creating changesets
202 202 4 changeset entries
203 203 cvschangesets hook: 4 changesets
204 204 sorting...
205 205 converting...
206 206 0 ci1
207 207 $ hgcat b/c
208 208 c
209 209 c
210 210 c
211 211 $ hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
212 212 3 ci1 files: b/c
213 213 2 update tags files: .hgtags
214 214 1 ci0 files: b/c
215 215 0 Initial revision files: b/c
216 216
217 217 commit branch
218 218
219 219 $ cd src
220 220 $ cvs -q update -r1.1 b/c
221 221 U b/c
222 222 $ cvs -q tag -b branch
223 223 T a
224 224 T b/c
225 225 $ cvs -q update -r branch > /dev/null
226 226 $ sleep 1
227 227 $ echo d >> b/c
228 228 $ cvs -q commit -mci2 . | grep '<--'
229 229 $TESTTMP/cvsrepo/src/b/c,v <-- *c (glob)
230 230 $ cd ..
231 231
232 232 convert again
233 233
234 234 $ TZ=US/Hawaii hg convert --config convert.localtimezone=True src src-hg
235 235 connecting to $TESTTMP/cvsrepo
236 236 scanning source...
237 237 collecting CVS rlog
238 238 8 log entries
239 239 cvslog hook: 8 entries
240 240 creating changesets
241 241 5 changeset entries
242 242 cvschangesets hook: 5 changesets
243 243 sorting...
244 244 converting...
245 245 0 ci2
246 246 $ hgcat b/c
247 247 c
248 248 d
249 249
250 250 convert again with --filemap
251 251
252 252 $ TZ=US/Hawaii hg convert --config convert.localtimezone=True --filemap filemap src src-filemap
253 253 connecting to $TESTTMP/cvsrepo
254 254 scanning source...
255 255 collecting CVS rlog
256 256 8 log entries
257 257 cvslog hook: 8 entries
258 258 creating changesets
259 259 5 changeset entries
260 260 cvschangesets hook: 5 changesets
261 261 sorting...
262 262 converting...
263 263 0 ci2
264 264 $ hgcat b/c
265 265 c
266 266 d
267 267 $ hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
268 268 4 ci2 files: b/c
269 269 3 ci1 files: b/c
270 270 2 update tags files: .hgtags
271 271 1 ci0 files: b/c
272 272 0 Initial revision files: b/c
273 273
274 274 commit a new revision with funny log message
275 275
276 276 $ cd src
277 277 $ sleep 1
278 278 $ echo e >> a
279 279 $ cvscall -q commit -m'funny
280 280 > ----------------------------
281 281 > log message' . | grep '<--' |\
282 282 > sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
283 283 checking in src/a,v
284 284
285 285 commit new file revisions with some fuzz
286 286
287 287 $ sleep 1
288 288 $ echo f >> a
289 289 $ cvscall -q commit -mfuzzy . | grep '<--'
290 290 $TESTTMP/cvsrepo/src/a,v <-- a
291 291 $ sleep 4 # the two changes will be split if fuzz < 4
292 292 $ echo g >> b/c
293 293 $ cvscall -q commit -mfuzzy . | grep '<--'
294 294 $TESTTMP/cvsrepo/src/b/c,v <-- *c (glob)
295 295 $ cd ..
296 296
297 297 convert again
298 298
299 299 $ TZ=US/Hawaii hg convert --config convert.cvsps.fuzz=2 --config convert.localtimezone=True src src-hg
300 300 connecting to $TESTTMP/cvsrepo
301 301 scanning source...
302 302 collecting CVS rlog
303 303 11 log entries
304 304 cvslog hook: 11 entries
305 305 creating changesets
306 306 8 changeset entries
307 307 cvschangesets hook: 8 changesets
308 308 sorting...
309 309 converting...
310 310 2 funny
311 311 1 fuzzy
312 312 0 fuzzy
313 313 $ hg -R src-hg log -G --template '{rev} ({branches}) {desc} date: {date|date} files: {files}\n'
314 314 o 8 (branch) fuzzy date: * -1000 files: b/c (glob)
315 315 |
316 316 o 7 (branch) fuzzy date: * -1000 files: a (glob)
317 317 |
318 318 o 6 (branch) funny
319 319 | ----------------------------
320 320 | log message date: * -1000 files: a (glob)
321 321 o 5 (branch) ci2 date: * -1000 files: b/c (glob)
322 322
323 323 o 4 () ci1 date: * -1000 files: a b/c (glob)
324 324 |
325 325 o 3 () update tags date: * +0000 files: .hgtags (glob)
326 326 |
327 327 | o 2 (INITIAL) import date: * -1000 files: (glob)
328 328 | |
329 329 o | 1 () ci0 date: * -1000 files: b/c (glob)
330 330 |/
331 331 o 0 () Initial revision date: * -1000 files: a b/c (glob)
332 332
333 333
334 334 testing debugcvsps
335 335
336 336 $ cd src
337 337 $ hg debugcvsps --fuzz=2 -x >/dev/null
338 338
339 339 commit a new revision changing a and removing b/c
340 340
341 341 $ cvscall -q update -A
342 342 U a
343 343 U b/c
344 344 $ sleep 1
345 345 $ echo h >> a
346 346 $ cvscall -Q remove -f b/c
347 347 $ cvscall -q commit -mci | grep '<--'
348 348 $TESTTMP/cvsrepo/src/a,v <-- a
349 349 $TESTTMP/cvsrepo/src/b/c,v <-- *c (glob)
350 350
351 351 update and verify the cvsps cache
352 352
353 353 $ hg debugcvsps --fuzz=2 -u
354 354 collecting CVS rlog
355 355 13 log entries
356 356 cvslog hook: 13 entries
357 357 creating changesets
358 358 11 changeset entries
359 359 cvschangesets hook: 11 changesets
360 360 ---------------------
361 361 PatchSet 1
362 362 Date: * (glob)
363 363 Author: * (glob)
364 364 Branch: HEAD
365 365 Tag: (none)
366 366 Branchpoints: INITIAL
367 367 Log:
368 368 Initial revision
369 369
370 370 Members:
371 371 a:INITIAL->1.1
372 372
373 373 ---------------------
374 374 PatchSet 2
375 375 Date: * (glob)
376 376 Author: * (glob)
377 377 Branch: HEAD
378 378 Tag: (none)
379 379 Branchpoints: INITIAL, branch
380 380 Log:
381 381 Initial revision
382 382
383 383 Members:
384 384 b/c:INITIAL->1.1
385 385
386 386 ---------------------
387 387 PatchSet 3
388 388 Date: * (glob)
389 389 Author: * (glob)
390 390 Branch: INITIAL
391 391 Tag: start
392 392 Log:
393 393 import
394 394
395 395 Members:
396 396 a:1.1->1.1.1.1
397 397 b/c:1.1->1.1.1.1
398 398
399 399 ---------------------
400 400 PatchSet 4
401 401 Date: * (glob)
402 402 Author: * (glob)
403 403 Branch: HEAD
404 404 Tag: (none)
405 405 Log:
406 406 ci0
407 407
408 408 Members:
409 409 b/c:1.1->1.2
410 410
411 411 ---------------------
412 412 PatchSet 5
413 413 Date: * (glob)
414 414 Author: * (glob)
415 415 Branch: HEAD
416 416 Tag: (none)
417 417 Branchpoints: branch
418 418 Log:
419 419 ci1
420 420
421 421 Members:
422 422 a:1.1->1.2
423 423
424 424 ---------------------
425 425 PatchSet 6
426 426 Date: * (glob)
427 427 Author: * (glob)
428 428 Branch: HEAD
429 429 Tag: (none)
430 430 Log:
431 431 ci1
432 432
433 433 Members:
434 434 b/c:1.2->1.3
435 435
436 436 ---------------------
437 437 PatchSet 7
438 438 Date: * (glob)
439 439 Author: * (glob)
440 440 Branch: branch
441 441 Tag: (none)
442 442 Log:
443 443 ci2
444 444
445 445 Members:
446 446 b/c:1.1->1.1.2.1
447 447
448 448 ---------------------
449 449 PatchSet 8
450 450 Date: * (glob)
451 451 Author: * (glob)
452 452 Branch: branch
453 453 Tag: (none)
454 454 Log:
455 455 funny
456 456 ----------------------------
457 457 log message
458 458
459 459 Members:
460 460 a:1.2->1.2.2.1
461 461
462 462 ---------------------
463 463 PatchSet 9
464 464 Date: * (glob)
465 465 Author: * (glob)
466 466 Branch: branch
467 467 Tag: (none)
468 468 Log:
469 469 fuzzy
470 470
471 471 Members:
472 472 a:1.2.2.1->1.2.2.2
473 473
474 474 ---------------------
475 475 PatchSet 10
476 476 Date: * (glob)
477 477 Author: * (glob)
478 478 Branch: branch
479 479 Tag: (none)
480 480 Log:
481 481 fuzzy
482 482
483 483 Members:
484 484 b/c:1.1.2.1->1.1.2.2
485 485
486 486 ---------------------
487 487 PatchSet 11
488 488 Date: * (glob)
489 489 Author: * (glob)
490 490 Branch: HEAD
491 491 Tag: (none)
492 492 Log:
493 493 ci
494 494
495 495 Members:
496 496 a:1.2->1.3
497 497 b/c:1.3->1.4(DEAD)
498 498
499 499
500 500 $ cd ..
501
502 Test transcoding CVS log messages (issue5597)
503 =============================================
504
505 To emulate commit messages in (non-ascii) multiple encodings portably,
506 this test scenario writes CVS history file (*,v file) directly via
507 python code.
508
509 Commit messages of version 1.2 - 1.4 use u3042 in 3 encodings below.
510
511 |encoding |byte sequence | decodable as: |
512 | | | utf-8 euc-jp cp932 |
513 +----------+--------------+--------------------+
514 |utf-8 |\xe3\x81\x82 | o x x |
515 |euc-jp |\xa4\xa2 | x o o |
516 |cp932 |\x82\xa0 | x x o |
517
518 $ mkdir -p cvsrepo/transcoding
519 $ python <<EOF
520 > fp = open('cvsrepo/transcoding/file,v', 'w')
521 > fp.write(('''
522 > head 1.4;
523 > access;
524 > symbols
525 > start:1.1.1.1 INITIAL:1.1.1;
526 > locks; strict;
527 > comment @# @;
528 >
529 >
530 > 1.4
531 > date 2017.07.10.00.00.04; author nobody; state Exp;
532 > branches;
533 > next 1.3;
534 > commitid 10059635D016A510FFA;
535 >
536 > 1.3
537 > date 2017.07.10.00.00.03; author nobody; state Exp;
538 > branches;
539 > next 1.2;
540 > commitid 10059635CFF6A4FF34E;
541 >
542 > 1.2
543 > date 2017.07.10.00.00.02; author nobody; state Exp;
544 > branches;
545 > next 1.1;
546 > commitid 10059635CFD6A4D5095;
547 >
548 > 1.1
549 > date 2017.07.10.00.00.01; author nobody; state Exp;
550 > branches
551 > 1.1.1.1;
552 > next ;
553 > commitid 10059635CFB6A4A3C33;
554 >
555 > 1.1.1.1
556 > date 2017.07.10.00.00.01; author nobody; state Exp;
557 > branches;
558 > next ;
559 > commitid 10059635CFB6A4A3C33;
560 >
561 >
562 > desc
563 > @@
564 >
565 >
566 > 1.4
567 > log
568 > @''' + u'\u3042'.encode('cp932') + ''' (cp932)
569 > @
570 > text
571 > @1
572 > 2
573 > 3
574 > 4
575 > @
576 >
577 >
578 > 1.3
579 > log
580 > @''' + u'\u3042'.encode('euc-jp') + ''' (euc-jp)
581 > @
582 > text
583 > @d4 1
584 > @
585 >
586 >
587 > 1.2
588 > log
589 > @''' + u'\u3042'.encode('utf-8') + ''' (utf-8)
590 > @
591 > text
592 > @d3 1
593 > @
594 >
595 >
596 > 1.1
597 > log
598 > @Initial revision
599 > @
600 > text
601 > @d2 1
602 > @
603 >
604 >
605 > 1.1.1.1
606 > log
607 > @import
608 > @
609 > text
610 > @@
611 > ''').lstrip())
612 > EOF
613
614 $ cvscall -q checkout transcoding
615 U transcoding/file
616
617 Test converting in normal case
618 ------------------------------
619
620 (filtering by grep in order to check only form of debug messages)
621
622 $ hg convert --config convert.cvsps.logencoding=utf-8,euc-jp,cp932 -q --debug transcoding transcoding-hg | grep 'transcoding by'
623 transcoding by utf-8: 1.1 of file
624 transcoding by utf-8: 1.1.1.1 of file
625 transcoding by utf-8: 1.2 of file
626 transcoding by euc-jp: 1.3 of file
627 transcoding by cp932: 1.4 of file
628 $ hg -R transcoding-hg --encoding utf-8 log -T "{rev}: {desc}\n"
629 5: update tags
630 4: import
631 3: \xe3\x81\x82 (cp932) (esc)
632 2: \xe3\x81\x82 (euc-jp) (esc)
633 1: \xe3\x81\x82 (utf-8) (esc)
634 0: Initial revision
635 $ rm -rf transcoding-hg
636
637 Test converting in error cases
638 ------------------------------
639
640 unknown encoding in convert.cvsps.logencoding
641
642 $ hg convert --config convert.cvsps.logencoding=foobar -q transcoding transcoding-hg
643 abort: unknown encoding: foobar
644 (check convert.cvsps.logencoding configuration)
645 [255]
646 $ rm -rf transcoding-hg
647
648 no acceptable encoding in convert.cvsps.logencoding
649
650 $ hg convert --config convert.cvsps.logencoding=utf-8,euc-jp -q transcoding transcoding-hg
651 abort: no encoding can transcode CVS log message for 1.4 of file
652 (check convert.cvsps.logencoding configuration)
653 [255]
654 $ rm -rf transcoding-hg
@@ -1,610 +1,616 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > convert=
4 4 > [convert]
5 5 > hg.saverev=False
6 6 > EOF
7 7 $ hg help convert
8 8 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
9 9
10 10 convert a foreign SCM repository to a Mercurial one.
11 11
12 12 Accepted source formats [identifiers]:
13 13
14 14 - Mercurial [hg]
15 15 - CVS [cvs]
16 16 - Darcs [darcs]
17 17 - git [git]
18 18 - Subversion [svn]
19 19 - Monotone [mtn]
20 20 - GNU Arch [gnuarch]
21 21 - Bazaar [bzr]
22 22 - Perforce [p4]
23 23
24 24 Accepted destination formats [identifiers]:
25 25
26 26 - Mercurial [hg]
27 27 - Subversion [svn] (history on branches is not preserved)
28 28
29 29 If no revision is given, all revisions will be converted. Otherwise,
30 30 convert will only import up to the named revision (given in a format
31 31 understood by the source).
32 32
33 33 If no destination directory name is specified, it defaults to the basename
34 34 of the source with "-hg" appended. If the destination repository doesn't
35 35 exist, it will be created.
36 36
37 37 By default, all sources except Mercurial will use --branchsort. Mercurial
38 38 uses --sourcesort to preserve original revision numbers order. Sort modes
39 39 have the following effects:
40 40
41 41 --branchsort convert from parent to child revision when possible, which
42 42 means branches are usually converted one after the other.
43 43 It generates more compact repositories.
44 44 --datesort sort revisions by date. Converted repositories have good-
45 45 looking changelogs but are often an order of magnitude
46 46 larger than the same ones generated by --branchsort.
47 47 --sourcesort try to preserve source revisions order, only supported by
48 48 Mercurial sources.
49 49 --closesort try to move closed revisions as close as possible to parent
50 50 branches, only supported by Mercurial sources.
51 51
52 52 If "REVMAP" isn't given, it will be put in a default location
53 53 ("<dest>/.hg/shamap" by default). The "REVMAP" is a simple text file that
54 54 maps each source commit ID to the destination ID for that revision, like
55 55 so:
56 56
57 57 <source ID> <destination ID>
58 58
59 59 If the file doesn't exist, it's automatically created. It's updated on
60 60 each commit copied, so 'hg convert' can be interrupted and can be run
61 61 repeatedly to copy new commits.
62 62
63 63 The authormap is a simple text file that maps each source commit author to
64 64 a destination commit author. It is handy for source SCMs that use unix
65 65 logins to identify authors (e.g.: CVS). One line per author mapping and
66 66 the line format is:
67 67
68 68 source author = destination author
69 69
70 70 Empty lines and lines starting with a "#" are ignored.
71 71
72 72 The filemap is a file that allows filtering and remapping of files and
73 73 directories. Each line can contain one of the following directives:
74 74
75 75 include path/to/file-or-dir
76 76
77 77 exclude path/to/file-or-dir
78 78
79 79 rename path/to/source path/to/destination
80 80
81 81 Comment lines start with "#". A specified path matches if it equals the
82 82 full relative name of a file or one of its parent directories. The
83 83 "include" or "exclude" directive with the longest matching path applies,
84 84 so line order does not matter.
85 85
86 86 The "include" directive causes a file, or all files under a directory, to
87 87 be included in the destination repository. The default if there are no
88 88 "include" statements is to include everything. If there are any "include"
89 89 statements, nothing else is included. The "exclude" directive causes files
90 90 or directories to be omitted. The "rename" directive renames a file or
91 91 directory if it is converted. To rename from a subdirectory into the root
92 92 of the repository, use "." as the path to rename to.
93 93
94 94 "--full" will make sure the converted changesets contain exactly the right
95 95 files with the right content. It will make a full conversion of all files,
96 96 not just the ones that have changed. Files that already are correct will
97 97 not be changed. This can be used to apply filemap changes when converting
98 98 incrementally. This is currently only supported for Mercurial and
99 99 Subversion.
100 100
101 101 The splicemap is a file that allows insertion of synthetic history,
102 102 letting you specify the parents of a revision. This is useful if you want
103 103 to e.g. give a Subversion merge two parents, or graft two disconnected
104 104 series of history together. Each entry contains a key, followed by a
105 105 space, followed by one or two comma-separated values:
106 106
107 107 key parent1, parent2
108 108
109 109 The key is the revision ID in the source revision control system whose
110 110 parents should be modified (same format as a key in .hg/shamap). The
111 111 values are the revision IDs (in either the source or destination revision
112 112 control system) that should be used as the new parents for that node. For
113 113 example, if you have merged "release-1.0" into "trunk", then you should
114 114 specify the revision on "trunk" as the first parent and the one on the
115 115 "release-1.0" branch as the second.
116 116
117 117 The branchmap is a file that allows you to rename a branch when it is
118 118 being brought in from whatever external repository. When used in
119 119 conjunction with a splicemap, it allows for a powerful combination to help
120 120 fix even the most badly mismanaged repositories and turn them into nicely
121 121 structured Mercurial repositories. The branchmap contains lines of the
122 122 form:
123 123
124 124 original_branch_name new_branch_name
125 125
126 126 where "original_branch_name" is the name of the branch in the source
127 127 repository, and "new_branch_name" is the name of the branch is the
128 128 destination repository. No whitespace is allowed in the new branch name.
129 129 This can be used to (for instance) move code in one repository from
130 130 "default" to a named branch.
131 131
132 132 Mercurial Source
133 133 ################
134 134
135 135 The Mercurial source recognizes the following configuration options, which
136 136 you can set on the command line with "--config":
137 137
138 138 convert.hg.ignoreerrors
139 139 ignore integrity errors when reading. Use it to fix
140 140 Mercurial repositories with missing revlogs, by converting
141 141 from and to Mercurial. Default is False.
142 142 convert.hg.saverev
143 143 store original revision ID in changeset (forces target IDs
144 144 to change). It takes a boolean argument and defaults to
145 145 False.
146 146 convert.hg.startrev
147 147 specify the initial Mercurial revision. The default is 0.
148 148 convert.hg.revs
149 149 revset specifying the source revisions to convert.
150 150
151 151 CVS Source
152 152 ##########
153 153
154 154 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
155 155 indicate the starting point of what will be converted. Direct access to
156 156 the repository files is not needed, unless of course the repository is
157 157 ":local:". The conversion uses the top level directory in the sandbox to
158 158 find the CVS repository, and then uses CVS rlog commands to find files to
159 159 convert. This means that unless a filemap is given, all files under the
160 160 starting directory will be converted, and that any directory
161 161 reorganization in the CVS sandbox is ignored.
162 162
163 163 The following options can be used with "--config":
164 164
165 165 convert.cvsps.cache
166 166 Set to False to disable remote log caching, for testing and
167 167 debugging purposes. Default is True.
168 168 convert.cvsps.fuzz
169 169 Specify the maximum time (in seconds) that is allowed
170 170 between commits with identical user and log message in a
171 171 single changeset. When very large files were checked in as
172 172 part of a changeset then the default may not be long enough.
173 173 The default is 60.
174 convert.cvsps.logencoding
175 Specify encoding name to be used for transcoding CVS log
176 messages. Multiple encoding names can be specified as a list
177 (see 'hg help config.Syntax'), but only the first acceptable
178 encoding in the list is used per CVS log entries. This
179 transcoding is executed before cvslog hook below.
174 180 convert.cvsps.mergeto
175 181 Specify a regular expression to which commit log messages
176 182 are matched. If a match occurs, then the conversion process
177 183 will insert a dummy revision merging the branch on which
178 184 this log message occurs to the branch indicated in the
179 185 regex. Default is "{{mergetobranch ([-\w]+)}}"
180 186 convert.cvsps.mergefrom
181 187 Specify a regular expression to which commit log messages
182 188 are matched. If a match occurs, then the conversion process
183 189 will add the most recent revision on the branch indicated in
184 190 the regex as the second parent of the changeset. Default is
185 191 "{{mergefrombranch ([-\w]+)}}"
186 192 convert.localtimezone
187 193 use local time (as determined by the TZ environment
188 194 variable) for changeset date/times. The default is False
189 195 (use UTC).
190 196 hooks.cvslog Specify a Python function to be called at the end of
191 197 gathering the CVS log. The function is passed a list with
192 198 the log entries, and can modify the entries in-place, or add
193 199 or delete them.
194 200 hooks.cvschangesets
195 201 Specify a Python function to be called after the changesets
196 202 are calculated from the CVS log. The function is passed a
197 203 list with the changeset entries, and can modify the
198 204 changesets in-place, or add or delete them.
199 205
200 206 An additional "debugcvsps" Mercurial command allows the builtin changeset
201 207 merging code to be run without doing a conversion. Its parameters and
202 208 output are similar to that of cvsps 2.1. Please see the command help for
203 209 more details.
204 210
205 211 Subversion Source
206 212 #################
207 213
208 214 Subversion source detects classical trunk/branches/tags layouts. By
209 215 default, the supplied "svn://repo/path/" source URL is converted as a
210 216 single branch. If "svn://repo/path/trunk" exists it replaces the default
211 217 branch. If "svn://repo/path/branches" exists, its subdirectories are
212 218 listed as possible branches. If "svn://repo/path/tags" exists, it is
213 219 looked for tags referencing converted branches. Default "trunk",
214 220 "branches" and "tags" values can be overridden with following options. Set
215 221 them to paths relative to the source URL, or leave them blank to disable
216 222 auto detection.
217 223
218 224 The following options can be set with "--config":
219 225
220 226 convert.svn.branches
221 227 specify the directory containing branches. The default is
222 228 "branches".
223 229 convert.svn.tags
224 230 specify the directory containing tags. The default is
225 231 "tags".
226 232 convert.svn.trunk
227 233 specify the name of the trunk branch. The default is
228 234 "trunk".
229 235 convert.localtimezone
230 236 use local time (as determined by the TZ environment
231 237 variable) for changeset date/times. The default is False
232 238 (use UTC).
233 239
234 240 Source history can be retrieved starting at a specific revision, instead
235 241 of being integrally converted. Only single branch conversions are
236 242 supported.
237 243
238 244 convert.svn.startrev
239 245 specify start Subversion revision number. The default is 0.
240 246
241 247 Git Source
242 248 ##########
243 249
244 250 The Git importer converts commits from all reachable branches (refs in
245 251 refs/heads) and remotes (refs in refs/remotes) to Mercurial. Branches are
246 252 converted to bookmarks with the same name, with the leading 'refs/heads'
247 253 stripped. Git submodules are converted to Git subrepos in Mercurial.
248 254
249 255 The following options can be set with "--config":
250 256
251 257 convert.git.similarity
252 258 specify how similar files modified in a commit must be to be
253 259 imported as renames or copies, as a percentage between "0"
254 260 (disabled) and "100" (files must be identical). For example,
255 261 "90" means that a delete/add pair will be imported as a
256 262 rename if more than 90% of the file hasn't changed. The
257 263 default is "50".
258 264 convert.git.findcopiesharder
259 265 while detecting copies, look at all files in the working
260 266 copy instead of just changed ones. This is very expensive
261 267 for large projects, and is only effective when
262 268 "convert.git.similarity" is greater than 0. The default is
263 269 False.
264 270 convert.git.renamelimit
265 271 perform rename and copy detection up to this many changed
266 272 files in a commit. Increasing this will make rename and copy
267 273 detection more accurate but will significantly slow down
268 274 computation on large projects. The option is only relevant
269 275 if "convert.git.similarity" is greater than 0. The default
270 276 is "400".
271 277 convert.git.committeractions
272 278 list of actions to take when processing author and committer
273 279 values.
274 280
275 281 Git commits have separate author (who wrote the commit) and committer
276 282 (who applied the commit) fields. Not all destinations support separate
277 283 author and committer fields (including Mercurial). This config option
278 284 controls what to do with these author and committer fields during
279 285 conversion.
280 286
281 287 A value of "messagedifferent" will append a "committer: ..." line to
282 288 the commit message if the Git committer is different from the author.
283 289 The prefix of that line can be specified using the syntax
284 290 "messagedifferent=<prefix>". e.g. "messagedifferent=git-committer:".
285 291 When a prefix is specified, a space will always be inserted between
286 292 the prefix and the value.
287 293
288 294 "messagealways" behaves like "messagedifferent" except it will always
289 295 result in a "committer: ..." line being appended to the commit
290 296 message. This value is mutually exclusive with "messagedifferent".
291 297
292 298 "dropcommitter" will remove references to the committer. Only
293 299 references to the author will remain. Actions that add references to
294 300 the committer will have no effect when this is set.
295 301
296 302 "replaceauthor" will replace the value of the author field with the
297 303 committer. Other actions that add references to the committer will
298 304 still take effect when this is set.
299 305
300 306 The default is "messagedifferent".
301 307
302 308 convert.git.extrakeys
303 309 list of extra keys from commit metadata to copy to the
304 310 destination. Some Git repositories store extra metadata in
305 311 commits. By default, this non-default metadata will be lost
306 312 during conversion. Setting this config option can retain
307 313 that metadata. Some built-in keys such as "parent" and
308 314 "branch" are not allowed to be copied.
309 315 convert.git.remoteprefix
310 316 remote refs are converted as bookmarks with
311 317 "convert.git.remoteprefix" as a prefix followed by a /. The
312 318 default is 'remote'.
313 319 convert.git.saverev
314 320 whether to store the original Git commit ID in the metadata
315 321 of the destination commit. The default is True.
316 322 convert.git.skipsubmodules
317 323 does not convert root level .gitmodules files or files with
318 324 160000 mode indicating a submodule. Default is False.
319 325
320 326 Perforce Source
321 327 ###############
322 328
323 329 The Perforce (P4) importer can be given a p4 depot path or a client
324 330 specification as source. It will convert all files in the source to a flat
325 331 Mercurial repository, ignoring labels, branches and integrations. Note
326 332 that when a depot path is given you then usually should specify a target
327 333 directory, because otherwise the target may be named "...-hg".
328 334
329 335 The following options can be set with "--config":
330 336
331 337 convert.p4.encoding
332 338 specify the encoding to use when decoding standard output of
333 339 the Perforce command line tool. The default is default
334 340 system encoding.
335 341 convert.p4.startrev
336 342 specify initial Perforce revision (a Perforce changelist
337 343 number).
338 344
339 345 Mercurial Destination
340 346 #####################
341 347
342 348 The Mercurial destination will recognize Mercurial subrepositories in the
343 349 destination directory, and update the .hgsubstate file automatically if
344 350 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
345 351 Converting a repository with subrepositories requires converting a single
346 352 repository at a time, from the bottom up.
347 353
348 354 The following options are supported:
349 355
350 356 convert.hg.clonebranches
351 357 dispatch source branches in separate clones. The default is
352 358 False.
353 359 convert.hg.tagsbranch
354 360 branch name for tag revisions, defaults to "default".
355 361 convert.hg.usebranchnames
356 362 preserve branch names. The default is True.
357 363 convert.hg.sourcename
358 364 records the given string as a 'convert_source' extra value
359 365 on each commit made in the target repository. The default is
360 366 None.
361 367
362 368 All Destinations
363 369 ################
364 370
365 371 All destination types accept the following options:
366 372
367 373 convert.skiptags
368 374 does not convert tags from the source repo to the target
369 375 repo. The default is False.
370 376
371 377 options ([+] can be repeated):
372 378
373 379 -s --source-type TYPE source repository type
374 380 -d --dest-type TYPE destination repository type
375 381 -r --rev REV [+] import up to source revision REV
376 382 -A --authormap FILE remap usernames using this file
377 383 --filemap FILE remap file names using contents of file
378 384 --full apply filemap changes by converting all files again
379 385 --splicemap FILE splice synthesized history into place
380 386 --branchmap FILE change branch names while converting
381 387 --branchsort try to sort changesets by branches
382 388 --datesort try to sort changesets by date
383 389 --sourcesort preserve source changesets order
384 390 --closesort try to reorder closed revisions
385 391
386 392 (some details hidden, use --verbose to show complete help)
387 393 $ hg init a
388 394 $ cd a
389 395 $ echo a > a
390 396 $ hg ci -d'0 0' -Ama
391 397 adding a
392 398 $ hg cp a b
393 399 $ hg ci -d'1 0' -mb
394 400 $ hg rm a
395 401 $ hg ci -d'2 0' -mc
396 402 $ hg mv b a
397 403 $ hg ci -d'3 0' -md
398 404 $ echo a >> a
399 405 $ hg ci -d'4 0' -me
400 406 $ cd ..
401 407 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
402 408 assuming destination a-hg
403 409 initializing destination a-hg repository
404 410 scanning source...
405 411 sorting...
406 412 converting...
407 413 4 a
408 414 3 b
409 415 2 c
410 416 1 d
411 417 0 e
412 418 $ hg --cwd a-hg pull ../a
413 419 pulling from ../a
414 420 searching for changes
415 421 no changes found
416 422
417 423 conversion to existing file should fail
418 424
419 425 $ touch bogusfile
420 426 $ hg convert a bogusfile
421 427 initializing destination bogusfile repository
422 428 abort: cannot create new bundle repository
423 429 [255]
424 430
425 431 #if unix-permissions no-root
426 432
427 433 conversion to dir without permissions should fail
428 434
429 435 $ mkdir bogusdir
430 436 $ chmod 000 bogusdir
431 437
432 438 $ hg convert a bogusdir
433 439 abort: Permission denied: 'bogusdir'
434 440 [255]
435 441
436 442 user permissions should succeed
437 443
438 444 $ chmod 700 bogusdir
439 445 $ hg convert a bogusdir
440 446 initializing destination bogusdir repository
441 447 scanning source...
442 448 sorting...
443 449 converting...
444 450 4 a
445 451 3 b
446 452 2 c
447 453 1 d
448 454 0 e
449 455
450 456 #endif
451 457
452 458 test pre and post conversion actions
453 459
454 460 $ echo 'include b' > filemap
455 461 $ hg convert --debug --filemap filemap a partialb | \
456 462 > grep 'run hg'
457 463 run hg source pre-conversion action
458 464 run hg sink pre-conversion action
459 465 run hg sink post-conversion action
460 466 run hg source post-conversion action
461 467
462 468 converting empty dir should fail "nicely
463 469
464 470 $ mkdir emptydir
465 471
466 472 override $PATH to ensure p4 not visible; use $PYTHON in case we're
467 473 running from a devel copy, not a temp installation
468 474
469 475 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
470 476 assuming destination emptydir-hg
471 477 initializing destination emptydir-hg repository
472 478 emptydir does not look like a CVS checkout
473 479 $TESTTMP/emptydir does not look like a Git repository (glob)
474 480 emptydir does not look like a Subversion repository
475 481 emptydir is not a local Mercurial repository
476 482 emptydir does not look like a darcs repository
477 483 emptydir does not look like a monotone repository
478 484 emptydir does not look like a GNU Arch repository
479 485 emptydir does not look like a Bazaar repository
480 486 cannot find required "p4" tool
481 487 abort: emptydir: missing or unsupported repository
482 488 [255]
483 489
484 490 convert with imaginary source type
485 491
486 492 $ hg convert --source-type foo a a-foo
487 493 initializing destination a-foo repository
488 494 abort: foo: invalid source repository type
489 495 [255]
490 496
491 497 convert with imaginary sink type
492 498
493 499 $ hg convert --dest-type foo a a-foo
494 500 abort: foo: invalid destination repository type
495 501 [255]
496 502
497 503 testing: convert must not produce duplicate entries in fncache
498 504
499 505 $ hg convert a b
500 506 initializing destination b repository
501 507 scanning source...
502 508 sorting...
503 509 converting...
504 510 4 a
505 511 3 b
506 512 2 c
507 513 1 d
508 514 0 e
509 515
510 516 contents of fncache file:
511 517
512 518 $ cat b/.hg/store/fncache | sort
513 519 data/a.i
514 520 data/b.i
515 521
516 522 test bogus URL
517 523
518 524 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
519 525 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
520 526 [255]
521 527
522 528 test revset converted() lookup
523 529
524 530 $ hg --config convert.hg.saverev=True convert a c
525 531 initializing destination c repository
526 532 scanning source...
527 533 sorting...
528 534 converting...
529 535 4 a
530 536 3 b
531 537 2 c
532 538 1 d
533 539 0 e
534 540 $ echo f > c/f
535 541 $ hg -R c ci -d'0 0' -Amf
536 542 adding f
537 543 created new head
538 544 $ hg -R c log -r "converted(09d945a62ce6)"
539 545 changeset: 1:98c3dd46a874
540 546 user: test
541 547 date: Thu Jan 01 00:00:01 1970 +0000
542 548 summary: b
543 549
544 550 $ hg -R c log -r "converted()"
545 551 changeset: 0:31ed57b2037c
546 552 user: test
547 553 date: Thu Jan 01 00:00:00 1970 +0000
548 554 summary: a
549 555
550 556 changeset: 1:98c3dd46a874
551 557 user: test
552 558 date: Thu Jan 01 00:00:01 1970 +0000
553 559 summary: b
554 560
555 561 changeset: 2:3b9ca06ef716
556 562 user: test
557 563 date: Thu Jan 01 00:00:02 1970 +0000
558 564 summary: c
559 565
560 566 changeset: 3:4e0debd37cf2
561 567 user: test
562 568 date: Thu Jan 01 00:00:03 1970 +0000
563 569 summary: d
564 570
565 571 changeset: 4:9de3bc9349c5
566 572 user: test
567 573 date: Thu Jan 01 00:00:04 1970 +0000
568 574 summary: e
569 575
570 576
571 577 test specifying a sourcename
572 578 $ echo g > a/g
573 579 $ hg -R a ci -d'0 0' -Amg
574 580 adding g
575 581 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
576 582 scanning source...
577 583 sorting...
578 584 converting...
579 585 0 g
580 586 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
581 587 branch=default
582 588 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
583 589 convert_source=mysource
584 590
585 591 $ cat > branchmap.txt << EOF
586 592 > old branch new_branch
587 593 > EOF
588 594
589 595 $ hg -R a branch -q 'old branch'
590 596 $ echo gg > a/g
591 597 $ hg -R a ci -m 'branch name with spaces'
592 598 $ hg convert --branchmap branchmap.txt a d
593 599 initializing destination d repository
594 600 scanning source...
595 601 sorting...
596 602 converting...
597 603 6 a
598 604 5 b
599 605 4 c
600 606 3 d
601 607 2 e
602 608 1 g
603 609 0 branch name with spaces
604 610
605 611 $ hg -R a branches
606 612 old branch 6:a24a66ade009
607 613 default 5:a3bc6100aa8e (inactive)
608 614 $ hg -R d branches
609 615 new_branch 6:64ed208b732b
610 616 default 5:a3bc6100aa8e (inactive)
General Comments 0
You need to be logged in to leave comments. Login now