##// END OF EJS Templates
convert: add a config option to help doing identity hg->hg conversion...
Valentin Gatien-Baron -
r42839:d98ec36b default
parent child Browse files
Show More
@@ -0,0 +1,40 b''
1 Testing that convert.hg.preserve-hash=true can be used to make hg
2 convert from hg repo to hg repo preserve hashes, even if the
3 computation of the files list in commits change slightly between hg
4 versions.
5
6 $ cat <<'EOF' >> "$HGRCPATH"
7 > [extensions]
8 > convert =
9 > EOF
10 $ cat <<'EOF' > changefileslist.py
11 > from mercurial import (changelog, extensions)
12 > def wrap(orig, clog, manifest, files, *args, **kwargs):
13 > return orig(clog, manifest, ["a"], *args, **kwargs)
14 > def extsetup(ui):
15 > extensions.wrapfunction(changelog.changelog, 'add', wrap)
16 > EOF
17
18 $ hg init repo
19 $ cd repo
20 $ echo a > a; hg commit -qAm a
21 $ echo b > a; hg commit -qAm b
22 $ hg up -qr 0; echo c > c; hg commit -qAm c
23 $ hg merge -qr 1
24 $ hg commit -m_ --config extensions.x=../changefileslist.py
25 $ hg log -r . -T '{node|short} {files|json}\n'
26 c085bbe93d59 ["a"]
27
28 Now that we have a commit with a files list that's not what the
29 current hg version would create, check that convert either fixes it or
30 keeps it depending on config:
31
32 $ hg convert -q . ../convert
33 $ hg --cwd ../convert log -r tip -T '{node|short} {files|json}\n'
34 b7c4d4bbacd3 []
35 $ rm -rf ../convert
36
37 $ hg convert -q . ../convert --config convert.hg.preserve-hash=true
38 $ hg --cwd ../convert log -r tip -T '{node|short} {files|json}\n'
39 c085bbe93d59 ["a"]
40 $ rm -rf ../convert
@@ -1,518 +1,523 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 Bazaar Source
208 208 #############
209 209
210 210 The following options can be used with ``--config``:
211 211
212 212 :convert.bzr.saverev: whether to store the original Bazaar commit ID in
213 213 the metadata of the destination commit. The default is True.
214 214
215 215 CVS Source
216 216 ##########
217 217
218 218 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
219 219 to indicate the starting point of what will be converted. Direct
220 220 access to the repository files is not needed, unless of course the
221 221 repository is ``:local:``. The conversion uses the top level
222 222 directory in the sandbox to find the CVS repository, and then uses
223 223 CVS rlog commands to find files to convert. This means that unless
224 224 a filemap is given, all files under the starting directory will be
225 225 converted, and that any directory reorganization in the CVS
226 226 sandbox is ignored.
227 227
228 228 The following options can be used with ``--config``:
229 229
230 230 :convert.cvsps.cache: Set to False to disable remote log caching,
231 231 for testing and debugging purposes. Default is True.
232 232
233 233 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
234 234 allowed between commits with identical user and log message in
235 235 a single changeset. When very large files were checked in as
236 236 part of a changeset then the default may not be long enough.
237 237 The default is 60.
238 238
239 239 :convert.cvsps.logencoding: Specify encoding name to be used for
240 240 transcoding CVS log messages. Multiple encoding names can be
241 241 specified as a list (see :hg:`help config.Syntax`), but only
242 242 the first acceptable encoding in the list is used per CVS log
243 243 entries. This transcoding is executed before cvslog hook below.
244 244
245 245 :convert.cvsps.mergeto: Specify a regular expression to which
246 246 commit log messages are matched. If a match occurs, then the
247 247 conversion process will insert a dummy revision merging the
248 248 branch on which this log message occurs to the branch
249 249 indicated in the regex. Default is ``{{mergetobranch
250 250 ([-\\w]+)}}``
251 251
252 252 :convert.cvsps.mergefrom: Specify a regular expression to which
253 253 commit log messages are matched. If a match occurs, then the
254 254 conversion process will add the most recent revision on the
255 255 branch indicated in the regex as the second parent of the
256 256 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
257 257
258 258 :convert.localtimezone: use local time (as determined by the TZ
259 259 environment variable) for changeset date/times. The default
260 260 is False (use UTC).
261 261
262 262 :hooks.cvslog: Specify a Python function to be called at the end of
263 263 gathering the CVS log. The function is passed a list with the
264 264 log entries, and can modify the entries in-place, or add or
265 265 delete them.
266 266
267 267 :hooks.cvschangesets: Specify a Python function to be called after
268 268 the changesets are calculated from the CVS log. The
269 269 function is passed a list with the changeset entries, and can
270 270 modify the changesets in-place, or add or delete them.
271 271
272 272 An additional "debugcvsps" Mercurial command allows the builtin
273 273 changeset merging code to be run without doing a conversion. Its
274 274 parameters and output are similar to that of cvsps 2.1. Please see
275 275 the command help for more details.
276 276
277 277 Subversion Source
278 278 #################
279 279
280 280 Subversion source detects classical trunk/branches/tags layouts.
281 281 By default, the supplied ``svn://repo/path/`` source URL is
282 282 converted as a single branch. If ``svn://repo/path/trunk`` exists
283 283 it replaces the default branch. If ``svn://repo/path/branches``
284 284 exists, its subdirectories are listed as possible branches. If
285 285 ``svn://repo/path/tags`` exists, it is looked for tags referencing
286 286 converted branches. Default ``trunk``, ``branches`` and ``tags``
287 287 values can be overridden with following options. Set them to paths
288 288 relative to the source URL, or leave them blank to disable auto
289 289 detection.
290 290
291 291 The following options can be set with ``--config``:
292 292
293 293 :convert.svn.branches: specify the directory containing branches.
294 294 The default is ``branches``.
295 295
296 296 :convert.svn.tags: specify the directory containing tags. The
297 297 default is ``tags``.
298 298
299 299 :convert.svn.trunk: specify the name of the trunk branch. The
300 300 default is ``trunk``.
301 301
302 302 :convert.localtimezone: use local time (as determined by the TZ
303 303 environment variable) for changeset date/times. The default
304 304 is False (use UTC).
305 305
306 306 Source history can be retrieved starting at a specific revision,
307 307 instead of being integrally converted. Only single branch
308 308 conversions are supported.
309 309
310 310 :convert.svn.startrev: specify start Subversion revision number.
311 311 The default is 0.
312 312
313 313 Git Source
314 314 ##########
315 315
316 316 The Git importer converts commits from all reachable branches (refs
317 317 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
318 318 Branches are converted to bookmarks with the same name, with the
319 319 leading 'refs/heads' stripped. Git submodules are converted to Git
320 320 subrepos in Mercurial.
321 321
322 322 The following options can be set with ``--config``:
323 323
324 324 :convert.git.similarity: specify how similar files modified in a
325 325 commit must be to be imported as renames or copies, as a
326 326 percentage between ``0`` (disabled) and ``100`` (files must be
327 327 identical). For example, ``90`` means that a delete/add pair will
328 328 be imported as a rename if more than 90% of the file hasn't
329 329 changed. The default is ``50``.
330 330
331 331 :convert.git.findcopiesharder: while detecting copies, look at all
332 332 files in the working copy instead of just changed ones. This
333 333 is very expensive for large projects, and is only effective when
334 334 ``convert.git.similarity`` is greater than 0. The default is False.
335 335
336 336 :convert.git.renamelimit: perform rename and copy detection up to this
337 337 many changed files in a commit. Increasing this will make rename
338 338 and copy detection more accurate but will significantly slow down
339 339 computation on large projects. The option is only relevant if
340 340 ``convert.git.similarity`` is greater than 0. The default is
341 341 ``400``.
342 342
343 343 :convert.git.committeractions: list of actions to take when processing
344 344 author and committer values.
345 345
346 346 Git commits have separate author (who wrote the commit) and committer
347 347 (who applied the commit) fields. Not all destinations support separate
348 348 author and committer fields (including Mercurial). This config option
349 349 controls what to do with these author and committer fields during
350 350 conversion.
351 351
352 352 A value of ``messagedifferent`` will append a ``committer: ...``
353 353 line to the commit message if the Git committer is different from the
354 354 author. The prefix of that line can be specified using the syntax
355 355 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
356 356 When a prefix is specified, a space will always be inserted between the
357 357 prefix and the value.
358 358
359 359 ``messagealways`` behaves like ``messagedifferent`` except it will
360 360 always result in a ``committer: ...`` line being appended to the commit
361 361 message. This value is mutually exclusive with ``messagedifferent``.
362 362
363 363 ``dropcommitter`` will remove references to the committer. Only
364 364 references to the author will remain. Actions that add references
365 365 to the committer will have no effect when this is set.
366 366
367 367 ``replaceauthor`` will replace the value of the author field with
368 368 the committer. Other actions that add references to the committer
369 369 will still take effect when this is set.
370 370
371 371 The default is ``messagedifferent``.
372 372
373 373 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
374 374 the destination. Some Git repositories store extra metadata in commits.
375 375 By default, this non-default metadata will be lost during conversion.
376 376 Setting this config option can retain that metadata. Some built-in
377 377 keys such as ``parent`` and ``branch`` are not allowed to be copied.
378 378
379 379 :convert.git.remoteprefix: remote refs are converted as bookmarks with
380 380 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
381 381 is 'remote'.
382 382
383 383 :convert.git.saverev: whether to store the original Git commit ID in the
384 384 metadata of the destination commit. The default is True.
385 385
386 386 :convert.git.skipsubmodules: does not convert root level .gitmodules files
387 387 or files with 160000 mode indicating a submodule. Default is False.
388 388
389 389 Perforce Source
390 390 ###############
391 391
392 392 The Perforce (P4) importer can be given a p4 depot path or a
393 393 client specification as source. It will convert all files in the
394 394 source to a flat Mercurial repository, ignoring labels, branches
395 395 and integrations. Note that when a depot path is given you then
396 396 usually should specify a target directory, because otherwise the
397 397 target may be named ``...-hg``.
398 398
399 399 The following options can be set with ``--config``:
400 400
401 401 :convert.p4.encoding: specify the encoding to use when decoding standard
402 402 output of the Perforce command line tool. The default is default system
403 403 encoding.
404 404
405 405 :convert.p4.startrev: specify initial Perforce revision (a
406 406 Perforce changelist number).
407 407
408 408 Mercurial Destination
409 409 #####################
410 410
411 411 The Mercurial destination will recognize Mercurial subrepositories in the
412 412 destination directory, and update the .hgsubstate file automatically if the
413 413 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
414 414 Converting a repository with subrepositories requires converting a single
415 415 repository at a time, from the bottom up.
416 416
417 417 .. container:: verbose
418 418
419 419 An example showing how to convert a repository with subrepositories::
420 420
421 421 # so convert knows the type when it sees a non empty destination
422 422 $ hg init converted
423 423
424 424 $ hg convert orig/sub1 converted/sub1
425 425 $ hg convert orig/sub2 converted/sub2
426 426 $ hg convert orig converted
427 427
428 428 The following options are supported:
429 429
430 430 :convert.hg.clonebranches: dispatch source branches in separate
431 431 clones. The default is False.
432 432
433 433 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
434 434 ``default``.
435 435
436 436 :convert.hg.usebranchnames: preserve branch names. The default is
437 437 True.
438 438
439 439 :convert.hg.sourcename: records the given string as a 'convert_source' extra
440 440 value on each commit made in the target repository. The default is None.
441 441
442 :convert.hg.preserve-hash: only works with mercurial sources. Make convert
443 prevent performance improvement to the list of modified files in commits
444 when such an improvement would cause the hash of a commit to change.
445 The default is False.
446
442 447 All Destinations
443 448 ################
444 449
445 450 All destination types accept the following options:
446 451
447 452 :convert.skiptags: does not convert tags from the source repo to the target
448 453 repo. The default is False.
449 454 """
450 455 return convcmd.convert(ui, src, dest, revmapfile, **opts)
451 456
452 457 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
453 458 def debugsvnlog(ui, **opts):
454 459 return subversion.debugsvnlog(ui, **opts)
455 460
456 461 @command('debugcvsps',
457 462 [
458 463 # Main options shared with cvsps-2.1
459 464 ('b', 'branches', [], _('only return changes on specified branches')),
460 465 ('p', 'prefix', '', _('prefix to remove from file names')),
461 466 ('r', 'revisions', [],
462 467 _('only return changes after or between specified tags')),
463 468 ('u', 'update-cache', None, _("update cvs log cache")),
464 469 ('x', 'new-cache', None, _("create new cvs log cache")),
465 470 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
466 471 ('', 'root', '', _('specify cvsroot')),
467 472 # Options specific to builtin cvsps
468 473 ('', 'parents', '', _('show parent changesets')),
469 474 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
470 475 # Options that are ignored for compatibility with cvsps-2.1
471 476 ('A', 'cvs-direct', None, _('ignored for compatibility')),
472 477 ],
473 478 _('hg debugcvsps [OPTION]... [PATH]...'),
474 479 norepo=True)
475 480 def debugcvsps(ui, *args, **opts):
476 481 '''create changeset information from CVS
477 482
478 483 This command is intended as a debugging tool for the CVS to
479 484 Mercurial converter, and can be used as a direct replacement for
480 485 cvsps.
481 486
482 487 Hg debugcvsps reads the CVS rlog for current directory (or any
483 488 named directory) in the CVS repository, and converts the log to a
484 489 series of changesets based on matching commit log entries and
485 490 dates.'''
486 491 return cvsps.debugcvsps(ui, *args, **opts)
487 492
488 493 def kwconverted(context, mapping, name):
489 494 ctx = context.resource(mapping, 'ctx')
490 495 rev = ctx.extra().get('convert_revision', '')
491 496 if rev.startswith('svn:'):
492 497 if name == 'svnrev':
493 498 return (b"%d" % subversion.revsplit(rev)[2])
494 499 elif name == 'svnpath':
495 500 return subversion.revsplit(rev)[1]
496 501 elif name == 'svnuuid':
497 502 return subversion.revsplit(rev)[0]
498 503 return rev
499 504
500 505 templatekeyword = registrar.templatekeyword()
501 506
502 507 @templatekeyword('svnrev', requires={'ctx'})
503 508 def kwsvnrev(context, mapping):
504 509 """String. Converted subversion revision number."""
505 510 return kwconverted(context, mapping, 'svnrev')
506 511
507 512 @templatekeyword('svnpath', requires={'ctx'})
508 513 def kwsvnpath(context, mapping):
509 514 """String. Converted subversion revision project path."""
510 515 return kwconverted(context, mapping, 'svnpath')
511 516
512 517 @templatekeyword('svnuuid', requires={'ctx'})
513 518 def kwsvnuuid(context, mapping):
514 519 """String. Converted subversion revision repository identifier."""
515 520 return kwconverted(context, mapping, 'svnuuid')
516 521
517 522 # tell hggettext to extract docstrings from these functions:
518 523 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,553 +1,554 b''
1 1 # common.py - common code for the convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
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 base64
10 10 import datetime
11 11 import errno
12 12 import os
13 13 import re
14 14 import shlex
15 15 import subprocess
16 16
17 17 from mercurial.i18n import _
18 18 from mercurial import (
19 19 encoding,
20 20 error,
21 21 phases,
22 22 pycompat,
23 23 util,
24 24 )
25 25 from mercurial.utils import (
26 26 procutil,
27 27 )
28 28
29 29 pickle = util.pickle
30 30 propertycache = util.propertycache
31 31
32 32 def _encodeornone(d):
33 33 if d is None:
34 34 return
35 35 return d.encode('latin1')
36 36
37 37 class _shlexpy3proxy(object):
38 38
39 39 def __init__(self, l):
40 40 self._l = l
41 41
42 42 def __iter__(self):
43 43 return (_encodeornone(v) for v in self._l)
44 44
45 45 def get_token(self):
46 46 return _encodeornone(self._l.get_token())
47 47
48 48 @property
49 49 def infile(self):
50 50 return self._l.infile or '<unknown>'
51 51
52 52 @property
53 53 def lineno(self):
54 54 return self._l.lineno
55 55
56 56 def shlexer(data=None, filepath=None, wordchars=None, whitespace=None):
57 57 if data is None:
58 58 if pycompat.ispy3:
59 59 data = open(filepath, 'r', encoding=r'latin1')
60 60 else:
61 61 data = open(filepath, 'r')
62 62 else:
63 63 if filepath is not None:
64 64 raise error.ProgrammingError(
65 65 'shlexer only accepts data or filepath, not both')
66 66 if pycompat.ispy3:
67 67 data = data.decode('latin1')
68 68 l = shlex.shlex(data, infile=filepath, posix=True)
69 69 if whitespace is not None:
70 70 l.whitespace_split = True
71 71 if pycompat.ispy3:
72 72 l.whitespace += whitespace.decode('latin1')
73 73 else:
74 74 l.whitespace += whitespace
75 75 if wordchars is not None:
76 76 if pycompat.ispy3:
77 77 l.wordchars += wordchars.decode('latin1')
78 78 else:
79 79 l.wordchars += wordchars
80 80 if pycompat.ispy3:
81 81 return _shlexpy3proxy(l)
82 82 return l
83 83
84 84 def encodeargs(args):
85 85 def encodearg(s):
86 86 lines = base64.encodestring(s)
87 87 lines = [l.splitlines()[0] for l in lines]
88 88 return ''.join(lines)
89 89
90 90 s = pickle.dumps(args)
91 91 return encodearg(s)
92 92
93 93 def decodeargs(s):
94 94 s = base64.decodestring(s)
95 95 return pickle.loads(s)
96 96
97 97 class MissingTool(Exception):
98 98 pass
99 99
100 100 def checktool(exe, name=None, abort=True):
101 101 name = name or exe
102 102 if not procutil.findexe(exe):
103 103 if abort:
104 104 exc = error.Abort
105 105 else:
106 106 exc = MissingTool
107 107 raise exc(_('cannot find required "%s" tool') % name)
108 108
109 109 class NoRepo(Exception):
110 110 pass
111 111
112 112 SKIPREV = 'SKIP'
113 113
114 114 class commit(object):
115 115 def __init__(self, author, date, desc, parents, branch=None, rev=None,
116 116 extra=None, sortkey=None, saverev=True, phase=phases.draft,
117 optparents=None):
117 optparents=None, ctx=None):
118 118 self.author = author or 'unknown'
119 119 self.date = date or '0 0'
120 120 self.desc = desc
121 121 self.parents = parents # will be converted and used as parents
122 122 self.optparents = optparents or [] # will be used if already converted
123 123 self.branch = branch
124 124 self.rev = rev
125 125 self.extra = extra or {}
126 126 self.sortkey = sortkey
127 127 self.saverev = saverev
128 128 self.phase = phase
129 self.ctx = ctx # for hg to hg conversions
129 130
130 131 class converter_source(object):
131 132 """Conversion source interface"""
132 133
133 134 def __init__(self, ui, repotype, path=None, revs=None):
134 135 """Initialize conversion source (or raise NoRepo("message")
135 136 exception if path is not a valid repository)"""
136 137 self.ui = ui
137 138 self.path = path
138 139 self.revs = revs
139 140 self.repotype = repotype
140 141
141 142 self.encoding = 'utf-8'
142 143
143 144 def checkhexformat(self, revstr, mapname='splicemap'):
144 145 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
145 146 such format for their revision numbering
146 147 """
147 148 if not re.match(br'[0-9a-fA-F]{40,40}$', revstr):
148 149 raise error.Abort(_('%s entry %s is not a valid revision'
149 150 ' identifier') % (mapname, revstr))
150 151
151 152 def before(self):
152 153 pass
153 154
154 155 def after(self):
155 156 pass
156 157
157 158 def targetfilebelongstosource(self, targetfilename):
158 159 """Returns true if the given targetfile belongs to the source repo. This
159 160 is useful when only a subdirectory of the target belongs to the source
160 161 repo."""
161 162 # For normal full repo converts, this is always True.
162 163 return True
163 164
164 165 def setrevmap(self, revmap):
165 166 """set the map of already-converted revisions"""
166 167
167 168 def getheads(self):
168 169 """Return a list of this repository's heads"""
169 170 raise NotImplementedError
170 171
171 172 def getfile(self, name, rev):
172 173 """Return a pair (data, mode) where data is the file content
173 174 as a string and mode one of '', 'x' or 'l'. rev is the
174 175 identifier returned by a previous call to getchanges().
175 176 Data is None if file is missing/deleted in rev.
176 177 """
177 178 raise NotImplementedError
178 179
179 180 def getchanges(self, version, full):
180 181 """Returns a tuple of (files, copies, cleanp2).
181 182
182 183 files is a sorted list of (filename, id) tuples for all files
183 184 changed between version and its first parent returned by
184 185 getcommit(). If full, all files in that revision is returned.
185 186 id is the source revision id of the file.
186 187
187 188 copies is a dictionary of dest: source
188 189
189 190 cleanp2 is the set of files filenames that are clean against p2.
190 191 (Files that are clean against p1 are already not in files (unless
191 192 full). This makes it possible to handle p2 clean files similarly.)
192 193 """
193 194 raise NotImplementedError
194 195
195 196 def getcommit(self, version):
196 197 """Return the commit object for version"""
197 198 raise NotImplementedError
198 199
199 200 def numcommits(self):
200 201 """Return the number of commits in this source.
201 202
202 203 If unknown, return None.
203 204 """
204 205 return None
205 206
206 207 def gettags(self):
207 208 """Return the tags as a dictionary of name: revision
208 209
209 210 Tag names must be UTF-8 strings.
210 211 """
211 212 raise NotImplementedError
212 213
213 214 def recode(self, s, encoding=None):
214 215 if not encoding:
215 216 encoding = self.encoding or 'utf-8'
216 217
217 218 if isinstance(s, pycompat.unicode):
218 219 return s.encode("utf-8")
219 220 try:
220 221 return s.decode(pycompat.sysstr(encoding)).encode("utf-8")
221 222 except UnicodeError:
222 223 try:
223 224 return s.decode("latin-1").encode("utf-8")
224 225 except UnicodeError:
225 226 return s.decode(pycompat.sysstr(encoding),
226 227 "replace").encode("utf-8")
227 228
228 229 def getchangedfiles(self, rev, i):
229 230 """Return the files changed by rev compared to parent[i].
230 231
231 232 i is an index selecting one of the parents of rev. The return
232 233 value should be the list of files that are different in rev and
233 234 this parent.
234 235
235 236 If rev has no parents, i is None.
236 237
237 238 This function is only needed to support --filemap
238 239 """
239 240 raise NotImplementedError
240 241
241 242 def converted(self, rev, sinkrev):
242 243 '''Notify the source that a revision has been converted.'''
243 244
244 245 def hasnativeorder(self):
245 246 """Return true if this source has a meaningful, native revision
246 247 order. For instance, Mercurial revisions are store sequentially
247 248 while there is no such global ordering with Darcs.
248 249 """
249 250 return False
250 251
251 252 def hasnativeclose(self):
252 253 """Return true if this source has ability to close branch.
253 254 """
254 255 return False
255 256
256 257 def lookuprev(self, rev):
257 258 """If rev is a meaningful revision reference in source, return
258 259 the referenced identifier in the same format used by getcommit().
259 260 return None otherwise.
260 261 """
261 262 return None
262 263
263 264 def getbookmarks(self):
264 265 """Return the bookmarks as a dictionary of name: revision
265 266
266 267 Bookmark names are to be UTF-8 strings.
267 268 """
268 269 return {}
269 270
270 271 def checkrevformat(self, revstr, mapname='splicemap'):
271 272 """revstr is a string that describes a revision in the given
272 273 source control system. Return true if revstr has correct
273 274 format.
274 275 """
275 276 return True
276 277
277 278 class converter_sink(object):
278 279 """Conversion sink (target) interface"""
279 280
280 281 def __init__(self, ui, repotype, path):
281 282 """Initialize conversion sink (or raise NoRepo("message")
282 283 exception if path is not a valid repository)
283 284
284 285 created is a list of paths to remove if a fatal error occurs
285 286 later"""
286 287 self.ui = ui
287 288 self.path = path
288 289 self.created = []
289 290 self.repotype = repotype
290 291
291 292 def revmapfile(self):
292 293 """Path to a file that will contain lines
293 294 source_rev_id sink_rev_id
294 295 mapping equivalent revision identifiers for each system."""
295 296 raise NotImplementedError
296 297
297 298 def authorfile(self):
298 299 """Path to a file that will contain lines
299 300 srcauthor=dstauthor
300 301 mapping equivalent authors identifiers for each system."""
301 302 return None
302 303
303 304 def putcommit(self, files, copies, parents, commit, source, revmap, full,
304 305 cleanp2):
305 306 """Create a revision with all changed files listed in 'files'
306 307 and having listed parents. 'commit' is a commit object
307 308 containing at a minimum the author, date, and message for this
308 309 changeset. 'files' is a list of (path, version) tuples,
309 310 'copies' is a dictionary mapping destinations to sources,
310 311 'source' is the source repository, and 'revmap' is a mapfile
311 312 of source revisions to converted revisions. Only getfile() and
312 313 lookuprev() should be called on 'source'. 'full' means that 'files'
313 314 is complete and all other files should be removed.
314 315 'cleanp2' is a set of the filenames that are unchanged from p2
315 316 (only in the common merge case where there two parents).
316 317
317 318 Note that the sink repository is not told to update itself to
318 319 a particular revision (or even what that revision would be)
319 320 before it receives the file data.
320 321 """
321 322 raise NotImplementedError
322 323
323 324 def puttags(self, tags):
324 325 """Put tags into sink.
325 326
326 327 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
327 328 Return a pair (tag_revision, tag_parent_revision), or (None, None)
328 329 if nothing was changed.
329 330 """
330 331 raise NotImplementedError
331 332
332 333 def setbranch(self, branch, pbranches):
333 334 """Set the current branch name. Called before the first putcommit
334 335 on the branch.
335 336 branch: branch name for subsequent commits
336 337 pbranches: (converted parent revision, parent branch) tuples"""
337 338
338 339 def setfilemapmode(self, active):
339 340 """Tell the destination that we're using a filemap
340 341
341 342 Some converter_sources (svn in particular) can claim that a file
342 343 was changed in a revision, even if there was no change. This method
343 344 tells the destination that we're using a filemap and that it should
344 345 filter empty revisions.
345 346 """
346 347
347 348 def before(self):
348 349 pass
349 350
350 351 def after(self):
351 352 pass
352 353
353 354 def putbookmarks(self, bookmarks):
354 355 """Put bookmarks into sink.
355 356
356 357 bookmarks: {bookmarkname: sink_rev_id, ...}
357 358 where bookmarkname is an UTF-8 string.
358 359 """
359 360
360 361 def hascommitfrommap(self, rev):
361 362 """Return False if a rev mentioned in a filemap is known to not be
362 363 present."""
363 364 raise NotImplementedError
364 365
365 366 def hascommitforsplicemap(self, rev):
366 367 """This method is for the special needs for splicemap handling and not
367 368 for general use. Returns True if the sink contains rev, aborts on some
368 369 special cases."""
369 370 raise NotImplementedError
370 371
371 372 class commandline(object):
372 373 def __init__(self, ui, command):
373 374 self.ui = ui
374 375 self.command = command
375 376
376 377 def prerun(self):
377 378 pass
378 379
379 380 def postrun(self):
380 381 pass
381 382
382 383 def _cmdline(self, cmd, *args, **kwargs):
383 384 kwargs = pycompat.byteskwargs(kwargs)
384 385 cmdline = [self.command, cmd] + list(args)
385 386 for k, v in kwargs.iteritems():
386 387 if len(k) == 1:
387 388 cmdline.append('-' + k)
388 389 else:
389 390 cmdline.append('--' + k.replace('_', '-'))
390 391 try:
391 392 if len(k) == 1:
392 393 cmdline.append('' + v)
393 394 else:
394 395 cmdline[-1] += '=' + v
395 396 except TypeError:
396 397 pass
397 398 cmdline = [procutil.shellquote(arg) for arg in cmdline]
398 399 if not self.ui.debugflag:
399 400 cmdline += ['2>', pycompat.bytestr(os.devnull)]
400 401 cmdline = ' '.join(cmdline)
401 402 return cmdline
402 403
403 404 def _run(self, cmd, *args, **kwargs):
404 405 def popen(cmdline):
405 406 p = subprocess.Popen(procutil.tonativestr(cmdline),
406 407 shell=True, bufsize=-1,
407 408 close_fds=procutil.closefds,
408 409 stdout=subprocess.PIPE)
409 410 return p
410 411 return self._dorun(popen, cmd, *args, **kwargs)
411 412
412 413 def _run2(self, cmd, *args, **kwargs):
413 414 return self._dorun(procutil.popen2, cmd, *args, **kwargs)
414 415
415 416 def _run3(self, cmd, *args, **kwargs):
416 417 return self._dorun(procutil.popen3, cmd, *args, **kwargs)
417 418
418 419 def _dorun(self, openfunc, cmd, *args, **kwargs):
419 420 cmdline = self._cmdline(cmd, *args, **kwargs)
420 421 self.ui.debug('running: %s\n' % (cmdline,))
421 422 self.prerun()
422 423 try:
423 424 return openfunc(cmdline)
424 425 finally:
425 426 self.postrun()
426 427
427 428 def run(self, cmd, *args, **kwargs):
428 429 p = self._run(cmd, *args, **kwargs)
429 430 output = p.communicate()[0]
430 431 self.ui.debug(output)
431 432 return output, p.returncode
432 433
433 434 def runlines(self, cmd, *args, **kwargs):
434 435 p = self._run(cmd, *args, **kwargs)
435 436 output = p.stdout.readlines()
436 437 p.wait()
437 438 self.ui.debug(''.join(output))
438 439 return output, p.returncode
439 440
440 441 def checkexit(self, status, output=''):
441 442 if status:
442 443 if output:
443 444 self.ui.warn(_('%s error:\n') % self.command)
444 445 self.ui.warn(output)
445 446 msg = procutil.explainexit(status)
446 447 raise error.Abort('%s %s' % (self.command, msg))
447 448
448 449 def run0(self, cmd, *args, **kwargs):
449 450 output, status = self.run(cmd, *args, **kwargs)
450 451 self.checkexit(status, output)
451 452 return output
452 453
453 454 def runlines0(self, cmd, *args, **kwargs):
454 455 output, status = self.runlines(cmd, *args, **kwargs)
455 456 self.checkexit(status, ''.join(output))
456 457 return output
457 458
458 459 @propertycache
459 460 def argmax(self):
460 461 # POSIX requires at least 4096 bytes for ARG_MAX
461 462 argmax = 4096
462 463 try:
463 464 argmax = os.sysconf(r"SC_ARG_MAX")
464 465 except (AttributeError, ValueError):
465 466 pass
466 467
467 468 # Windows shells impose their own limits on command line length,
468 469 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
469 470 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
470 471 # details about cmd.exe limitations.
471 472
472 473 # Since ARG_MAX is for command line _and_ environment, lower our limit
473 474 # (and make happy Windows shells while doing this).
474 475 return argmax // 2 - 1
475 476
476 477 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
477 478 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
478 479 limit = self.argmax - cmdlen
479 480 numbytes = 0
480 481 fl = []
481 482 for fn in arglist:
482 483 b = len(fn) + 3
483 484 if numbytes + b < limit or len(fl) == 0:
484 485 fl.append(fn)
485 486 numbytes += b
486 487 else:
487 488 yield fl
488 489 fl = [fn]
489 490 numbytes = b
490 491 if fl:
491 492 yield fl
492 493
493 494 def xargs(self, arglist, cmd, *args, **kwargs):
494 495 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
495 496 self.run0(cmd, *(list(args) + l), **kwargs)
496 497
497 498 class mapfile(dict):
498 499 def __init__(self, ui, path):
499 500 super(mapfile, self).__init__()
500 501 self.ui = ui
501 502 self.path = path
502 503 self.fp = None
503 504 self.order = []
504 505 self._read()
505 506
506 507 def _read(self):
507 508 if not self.path:
508 509 return
509 510 try:
510 511 fp = open(self.path, 'rb')
511 512 except IOError as err:
512 513 if err.errno != errno.ENOENT:
513 514 raise
514 515 return
515 516 for i, line in enumerate(util.iterfile(fp)):
516 517 line = line.splitlines()[0].rstrip()
517 518 if not line:
518 519 # Ignore blank lines
519 520 continue
520 521 try:
521 522 key, value = line.rsplit(' ', 1)
522 523 except ValueError:
523 524 raise error.Abort(
524 525 _('syntax error in %s(%d): key/value pair expected')
525 526 % (self.path, i + 1))
526 527 if key not in self:
527 528 self.order.append(key)
528 529 super(mapfile, self).__setitem__(key, value)
529 530 fp.close()
530 531
531 532 def __setitem__(self, key, value):
532 533 if self.fp is None:
533 534 try:
534 535 self.fp = open(self.path, 'ab')
535 536 except IOError as err:
536 537 raise error.Abort(
537 538 _('could not open map file %r: %s') %
538 539 (self.path, encoding.strtolocal(err.strerror)))
539 540 self.fp.write(util.tonativeeol('%s %s\n' % (key, value)))
540 541 self.fp.flush()
541 542 super(mapfile, self).__setitem__(key, value)
542 543
543 544 def close(self):
544 545 if self.fp:
545 546 self.fp.close()
546 547 self.fp = None
547 548
548 549 def makedatetimestamp(t):
549 550 """Like dateutil.makedate() but for time t instead of current time"""
550 551 delta = (datetime.datetime.utcfromtimestamp(t) -
551 552 datetime.datetime.fromtimestamp(t))
552 553 tz = delta.days * 86400 + delta.seconds
553 554 return t, tz
@@ -1,651 +1,656 b''
1 1 # hg.py - hg backend for convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
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 # Notes for hg->hg conversion:
9 9 #
10 10 # * Old versions of Mercurial didn't trim the whitespace from the ends
11 11 # of commit messages, but new versions do. Changesets created by
12 12 # those older versions, then converted, may thus have different
13 13 # hashes for changesets that are otherwise identical.
14 14 #
15 15 # * Using "--config convert.hg.saverev=true" will make the source
16 16 # identifier to be stored in the converted revision. This will cause
17 17 # the converted revision to have a different identity than the
18 18 # source.
19 19 from __future__ import absolute_import
20 20
21 21 import os
22 22 import re
23 23 import time
24 24
25 25 from mercurial.i18n import _
26 26 from mercurial import (
27 27 bookmarks,
28 28 context,
29 29 error,
30 30 exchange,
31 31 hg,
32 32 lock as lockmod,
33 33 merge as mergemod,
34 34 node as nodemod,
35 35 phases,
36 36 scmutil,
37 37 util,
38 38 )
39 39 from mercurial.utils import dateutil
40 40 stringio = util.stringio
41 41
42 42 from . import common
43 43 mapfile = common.mapfile
44 44 NoRepo = common.NoRepo
45 45
46 46 sha1re = re.compile(br'\b[0-9a-f]{12,40}\b')
47 47
48 48 class mercurial_sink(common.converter_sink):
49 49 def __init__(self, ui, repotype, path):
50 50 common.converter_sink.__init__(self, ui, repotype, path)
51 51 self.branchnames = ui.configbool('convert', 'hg.usebranchnames')
52 52 self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
53 53 self.tagsbranch = ui.config('convert', 'hg.tagsbranch')
54 54 self.lastbranch = None
55 55 if os.path.isdir(path) and len(os.listdir(path)) > 0:
56 56 try:
57 57 self.repo = hg.repository(self.ui, path)
58 58 if not self.repo.local():
59 59 raise NoRepo(_('%s is not a local Mercurial repository')
60 60 % path)
61 61 except error.RepoError as err:
62 62 ui.traceback()
63 63 raise NoRepo(err.args[0])
64 64 else:
65 65 try:
66 66 ui.status(_('initializing destination %s repository\n') % path)
67 67 self.repo = hg.repository(self.ui, path, create=True)
68 68 if not self.repo.local():
69 69 raise NoRepo(_('%s is not a local Mercurial repository')
70 70 % path)
71 71 self.created.append(path)
72 72 except error.RepoError:
73 73 ui.traceback()
74 74 raise NoRepo(_("could not create hg repository %s as sink")
75 75 % path)
76 76 self.lock = None
77 77 self.wlock = None
78 78 self.filemapmode = False
79 79 self.subrevmaps = {}
80 80
81 81 def before(self):
82 82 self.ui.debug('run hg sink pre-conversion action\n')
83 83 self.wlock = self.repo.wlock()
84 84 self.lock = self.repo.lock()
85 85
86 86 def after(self):
87 87 self.ui.debug('run hg sink post-conversion action\n')
88 88 if self.lock:
89 89 self.lock.release()
90 90 if self.wlock:
91 91 self.wlock.release()
92 92
93 93 def revmapfile(self):
94 94 return self.repo.vfs.join("shamap")
95 95
96 96 def authorfile(self):
97 97 return self.repo.vfs.join("authormap")
98 98
99 99 def setbranch(self, branch, pbranches):
100 100 if not self.clonebranches:
101 101 return
102 102
103 103 setbranch = (branch != self.lastbranch)
104 104 self.lastbranch = branch
105 105 if not branch:
106 106 branch = 'default'
107 107 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
108 108
109 109 branchpath = os.path.join(self.path, branch)
110 110 if setbranch:
111 111 self.after()
112 112 try:
113 113 self.repo = hg.repository(self.ui, branchpath)
114 114 except Exception:
115 115 self.repo = hg.repository(self.ui, branchpath, create=True)
116 116 self.before()
117 117
118 118 # pbranches may bring revisions from other branches (merge parents)
119 119 # Make sure we have them, or pull them.
120 120 missings = {}
121 121 for b in pbranches:
122 122 try:
123 123 self.repo.lookup(b[0])
124 124 except Exception:
125 125 missings.setdefault(b[1], []).append(b[0])
126 126
127 127 if missings:
128 128 self.after()
129 129 for pbranch, heads in sorted(missings.iteritems()):
130 130 pbranchpath = os.path.join(self.path, pbranch)
131 131 prepo = hg.peer(self.ui, {}, pbranchpath)
132 132 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
133 133 exchange.pull(self.repo, prepo,
134 134 [prepo.lookup(h) for h in heads])
135 135 self.before()
136 136
137 137 def _rewritetags(self, source, revmap, data):
138 138 fp = stringio()
139 139 for line in data.splitlines():
140 140 s = line.split(' ', 1)
141 141 if len(s) != 2:
142 142 self.ui.warn(_('invalid tag entry: "%s"\n') % line)
143 143 fp.write('%s\n' % line) # Bogus, but keep for hash stability
144 144 continue
145 145 revid = revmap.get(source.lookuprev(s[0]))
146 146 if not revid:
147 147 if s[0] == nodemod.nullhex:
148 148 revid = s[0]
149 149 else:
150 150 # missing, but keep for hash stability
151 151 self.ui.warn(_('missing tag entry: "%s"\n') % line)
152 152 fp.write('%s\n' % line)
153 153 continue
154 154 fp.write('%s %s\n' % (revid, s[1]))
155 155 return fp.getvalue()
156 156
157 157 def _rewritesubstate(self, source, data):
158 158 fp = stringio()
159 159 for line in data.splitlines():
160 160 s = line.split(' ', 1)
161 161 if len(s) != 2:
162 162 continue
163 163
164 164 revid = s[0]
165 165 subpath = s[1]
166 166 if revid != nodemod.nullhex:
167 167 revmap = self.subrevmaps.get(subpath)
168 168 if revmap is None:
169 169 revmap = mapfile(self.ui,
170 170 self.repo.wjoin(subpath, '.hg/shamap'))
171 171 self.subrevmaps[subpath] = revmap
172 172
173 173 # It is reasonable that one or more of the subrepos don't
174 174 # need to be converted, in which case they can be cloned
175 175 # into place instead of converted. Therefore, only warn
176 176 # once.
177 177 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
178 178 if len(revmap) == 0:
179 179 sub = self.repo.wvfs.reljoin(subpath, '.hg')
180 180
181 181 if self.repo.wvfs.exists(sub):
182 182 self.ui.warn(msg % subpath)
183 183
184 184 newid = revmap.get(revid)
185 185 if not newid:
186 186 if len(revmap) > 0:
187 187 self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
188 188 (revid, subpath))
189 189 else:
190 190 revid = newid
191 191
192 192 fp.write('%s %s\n' % (revid, subpath))
193 193
194 194 return fp.getvalue()
195 195
196 196 def _calculatemergedfiles(self, source, p1ctx, p2ctx):
197 197 """Calculates the files from p2 that we need to pull in when merging p1
198 198 and p2, given that the merge is coming from the given source.
199 199
200 200 This prevents us from losing files that only exist in the target p2 and
201 201 that don't come from the source repo (like if you're merging multiple
202 202 repositories together).
203 203 """
204 204 anc = [p1ctx.ancestor(p2ctx)]
205 205 # Calculate what files are coming from p2
206 206 actions, diverge, rename = mergemod.calculateupdates(
207 207 self.repo, p1ctx, p2ctx, anc,
208 208 True, # branchmerge
209 209 True, # force
210 210 False, # acceptremote
211 211 False, # followcopies
212 212 )
213 213
214 214 for file, (action, info, msg) in actions.iteritems():
215 215 if source.targetfilebelongstosource(file):
216 216 # If the file belongs to the source repo, ignore the p2
217 217 # since it will be covered by the existing fileset.
218 218 continue
219 219
220 220 # If the file requires actual merging, abort. We don't have enough
221 221 # context to resolve merges correctly.
222 222 if action in ['m', 'dm', 'cd', 'dc']:
223 223 raise error.Abort(_("unable to convert merge commit "
224 224 "since target parents do not merge cleanly (file "
225 225 "%s, parents %s and %s)") % (file, p1ctx,
226 226 p2ctx))
227 227 elif action == 'k':
228 228 # 'keep' means nothing changed from p1
229 229 continue
230 230 else:
231 231 # Any other change means we want to take the p2 version
232 232 yield file
233 233
234 234 def putcommit(self, files, copies, parents, commit, source, revmap, full,
235 235 cleanp2):
236 236 files = dict(files)
237 237
238 238 def getfilectx(repo, memctx, f):
239 239 if p2ctx and f in p2files and f not in copies:
240 240 self.ui.debug('reusing %s from p2\n' % f)
241 241 try:
242 242 return p2ctx[f]
243 243 except error.ManifestLookupError:
244 244 # If the file doesn't exist in p2, then we're syncing a
245 245 # delete, so just return None.
246 246 return None
247 247 try:
248 248 v = files[f]
249 249 except KeyError:
250 250 return None
251 251 data, mode = source.getfile(f, v)
252 252 if data is None:
253 253 return None
254 254 if f == '.hgtags':
255 255 data = self._rewritetags(source, revmap, data)
256 256 if f == '.hgsubstate':
257 257 data = self._rewritesubstate(source, data)
258 258 return context.memfilectx(self.repo, memctx, f, data, 'l' in mode,
259 259 'x' in mode, copies.get(f))
260 260
261 261 pl = []
262 262 for p in parents:
263 263 if p not in pl:
264 264 pl.append(p)
265 265 parents = pl
266 266 nparents = len(parents)
267 267 if self.filemapmode and nparents == 1:
268 268 m1node = self.repo.changelog.read(nodemod.bin(parents[0]))[0]
269 269 parent = parents[0]
270 270
271 271 if len(parents) < 2:
272 272 parents.append(nodemod.nullid)
273 273 if len(parents) < 2:
274 274 parents.append(nodemod.nullid)
275 275 p2 = parents.pop(0)
276 276
277 277 text = commit.desc
278 278
279 279 sha1s = re.findall(sha1re, text)
280 280 for sha1 in sha1s:
281 281 oldrev = source.lookuprev(sha1)
282 282 newrev = revmap.get(oldrev)
283 283 if newrev is not None:
284 284 text = text.replace(sha1, newrev[:len(sha1)])
285 285
286 286 extra = commit.extra.copy()
287 287
288 288 sourcename = self.repo.ui.config('convert', 'hg.sourcename')
289 289 if sourcename:
290 290 extra['convert_source'] = sourcename
291 291
292 292 for label in ('source', 'transplant_source', 'rebase_source',
293 293 'intermediate-source'):
294 294 node = extra.get(label)
295 295
296 296 if node is None:
297 297 continue
298 298
299 299 # Only transplant stores its reference in binary
300 300 if label == 'transplant_source':
301 301 node = nodemod.hex(node)
302 302
303 303 newrev = revmap.get(node)
304 304 if newrev is not None:
305 305 if label == 'transplant_source':
306 306 newrev = nodemod.bin(newrev)
307 307
308 308 extra[label] = newrev
309 309
310 310 if self.branchnames and commit.branch:
311 311 extra['branch'] = commit.branch
312 312 if commit.rev and commit.saverev:
313 313 extra['convert_revision'] = commit.rev
314 314
315 315 while parents:
316 316 p1 = p2
317 317 p2 = parents.pop(0)
318 318 p1ctx = self.repo[p1]
319 319 p2ctx = None
320 320 if p2 != nodemod.nullid:
321 321 p2ctx = self.repo[p2]
322 322 fileset = set(files)
323 323 if full:
324 324 fileset.update(self.repo[p1])
325 325 fileset.update(self.repo[p2])
326 326
327 327 if p2ctx:
328 328 p2files = set(cleanp2)
329 329 for file in self._calculatemergedfiles(source, p1ctx, p2ctx):
330 330 p2files.add(file)
331 331 fileset.add(file)
332 332
333 333 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
334 334 getfilectx, commit.author, commit.date, extra)
335 335
336 336 # We won't know if the conversion changes the node until after the
337 337 # commit, so copy the source's phase for now.
338 338 self.repo.ui.setconfig('phases', 'new-commit',
339 339 phases.phasenames[commit.phase], 'convert')
340 340
341 341 with self.repo.transaction("convert") as tr:
342 node = nodemod.hex(self.repo.commitctx(ctx))
342 if self.repo.ui.config('convert', 'hg.preserve-hash'):
343 origctx = commit.ctx
344 else:
345 origctx = None
346 node = nodemod.hex(self.repo.commitctx(ctx, origctx=origctx))
343 347
344 348 # If the node value has changed, but the phase is lower than
345 349 # draft, set it back to draft since it hasn't been exposed
346 350 # anywhere.
347 351 if commit.rev != node:
348 352 ctx = self.repo[node]
349 353 if ctx.phase() < phases.draft:
350 354 phases.registernew(self.repo, tr, phases.draft,
351 355 [ctx.node()])
352 356
353 357 text = "(octopus merge fixup)\n"
354 358 p2 = node
355 359
356 360 if self.filemapmode and nparents == 1:
357 361 man = self.repo.manifestlog.getstorage(b'')
358 362 mnode = self.repo.changelog.read(nodemod.bin(p2))[0]
359 363 closed = 'close' in commit.extra
360 364 if not closed and not man.cmp(m1node, man.revision(mnode)):
361 365 self.ui.status(_("filtering out empty revision\n"))
362 366 self.repo.rollback(force=True)
363 367 return parent
364 368 return p2
365 369
366 370 def puttags(self, tags):
367 371 tagparent = self.repo.branchtip(self.tagsbranch, ignoremissing=True)
368 372 tagparent = tagparent or nodemod.nullid
369 373
370 374 oldlines = set()
371 375 for branch, heads in self.repo.branchmap().iteritems():
372 376 for h in heads:
373 377 if '.hgtags' in self.repo[h]:
374 378 oldlines.update(
375 379 set(self.repo[h]['.hgtags'].data().splitlines(True)))
376 380 oldlines = sorted(list(oldlines))
377 381
378 382 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
379 383 if newlines == oldlines:
380 384 return None, None
381 385
382 386 # if the old and new tags match, then there is nothing to update
383 387 oldtags = set()
384 388 newtags = set()
385 389 for line in oldlines:
386 390 s = line.strip().split(' ', 1)
387 391 if len(s) != 2:
388 392 continue
389 393 oldtags.add(s[1])
390 394 for line in newlines:
391 395 s = line.strip().split(' ', 1)
392 396 if len(s) != 2:
393 397 continue
394 398 if s[1] not in oldtags:
395 399 newtags.add(s[1].strip())
396 400
397 401 if not newtags:
398 402 return None, None
399 403
400 404 data = "".join(newlines)
401 405 def getfilectx(repo, memctx, f):
402 406 return context.memfilectx(repo, memctx, f, data, False, False, None)
403 407
404 408 self.ui.status(_("updating tags\n"))
405 409 date = "%d 0" % int(time.mktime(time.gmtime()))
406 410 extra = {'branch': self.tagsbranch}
407 411 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
408 412 [".hgtags"], getfilectx, "convert-repo", date,
409 413 extra)
410 414 node = self.repo.commitctx(ctx)
411 415 return nodemod.hex(node), nodemod.hex(tagparent)
412 416
413 417 def setfilemapmode(self, active):
414 418 self.filemapmode = active
415 419
416 420 def putbookmarks(self, updatedbookmark):
417 421 if not len(updatedbookmark):
418 422 return
419 423 wlock = lock = tr = None
420 424 try:
421 425 wlock = self.repo.wlock()
422 426 lock = self.repo.lock()
423 427 tr = self.repo.transaction('bookmark')
424 428 self.ui.status(_("updating bookmarks\n"))
425 429 destmarks = self.repo._bookmarks
426 430 changes = [(bookmark, nodemod.bin(updatedbookmark[bookmark]))
427 431 for bookmark in updatedbookmark]
428 432 destmarks.applychanges(self.repo, tr, changes)
429 433 tr.close()
430 434 finally:
431 435 lockmod.release(lock, wlock, tr)
432 436
433 437 def hascommitfrommap(self, rev):
434 438 # the exact semantics of clonebranches is unclear so we can't say no
435 439 return rev in self.repo or self.clonebranches
436 440
437 441 def hascommitforsplicemap(self, rev):
438 442 if rev not in self.repo and self.clonebranches:
439 443 raise error.Abort(_('revision %s not found in destination '
440 444 'repository (lookups with clonebranches=true '
441 445 'are not implemented)') % rev)
442 446 return rev in self.repo
443 447
444 448 class mercurial_source(common.converter_source):
445 449 def __init__(self, ui, repotype, path, revs=None):
446 450 common.converter_source.__init__(self, ui, repotype, path, revs)
447 451 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors')
448 452 self.ignored = set()
449 453 self.saverev = ui.configbool('convert', 'hg.saverev')
450 454 try:
451 455 self.repo = hg.repository(self.ui, path)
452 456 # try to provoke an exception if this isn't really a hg
453 457 # repo, but some other bogus compatible-looking url
454 458 if not self.repo.local():
455 459 raise error.RepoError
456 460 except error.RepoError:
457 461 ui.traceback()
458 462 raise NoRepo(_("%s is not a local Mercurial repository") % path)
459 463 self.lastrev = None
460 464 self.lastctx = None
461 465 self._changescache = None, None
462 466 self.convertfp = None
463 467 # Restrict converted revisions to startrev descendants
464 468 startnode = ui.config('convert', 'hg.startrev')
465 469 hgrevs = ui.config('convert', 'hg.revs')
466 470 if hgrevs is None:
467 471 if startnode is not None:
468 472 try:
469 473 startnode = self.repo.lookup(startnode)
470 474 except error.RepoError:
471 475 raise error.Abort(_('%s is not a valid start revision')
472 476 % startnode)
473 477 startrev = self.repo.changelog.rev(startnode)
474 478 children = {startnode: 1}
475 479 for r in self.repo.changelog.descendants([startrev]):
476 480 children[self.repo.changelog.node(r)] = 1
477 481 self.keep = children.__contains__
478 482 else:
479 483 self.keep = util.always
480 484 if revs:
481 485 self._heads = [self.repo.lookup(r) for r in revs]
482 486 else:
483 487 self._heads = self.repo.heads()
484 488 else:
485 489 if revs or startnode is not None:
486 490 raise error.Abort(_('hg.revs cannot be combined with '
487 491 'hg.startrev or --rev'))
488 492 nodes = set()
489 493 parents = set()
490 494 for r in scmutil.revrange(self.repo, [hgrevs]):
491 495 ctx = self.repo[r]
492 496 nodes.add(ctx.node())
493 497 parents.update(p.node() for p in ctx.parents())
494 498 self.keep = nodes.__contains__
495 499 self._heads = nodes - parents
496 500
497 501 def _changectx(self, rev):
498 502 if self.lastrev != rev:
499 503 self.lastctx = self.repo[rev]
500 504 self.lastrev = rev
501 505 return self.lastctx
502 506
503 507 def _parents(self, ctx):
504 508 return [p for p in ctx.parents() if p and self.keep(p.node())]
505 509
506 510 def getheads(self):
507 511 return [nodemod.hex(h) for h in self._heads if self.keep(h)]
508 512
509 513 def getfile(self, name, rev):
510 514 try:
511 515 fctx = self._changectx(rev)[name]
512 516 return fctx.data(), fctx.flags()
513 517 except error.LookupError:
514 518 return None, None
515 519
516 520 def _changedfiles(self, ctx1, ctx2):
517 521 ma, r = [], []
518 522 maappend = ma.append
519 523 rappend = r.append
520 524 d = ctx1.manifest().diff(ctx2.manifest())
521 525 for f, ((node1, flag1), (node2, flag2)) in d.iteritems():
522 526 if node2 is None:
523 527 rappend(f)
524 528 else:
525 529 maappend(f)
526 530 return ma, r
527 531
528 532 def getchanges(self, rev, full):
529 533 ctx = self._changectx(rev)
530 534 parents = self._parents(ctx)
531 535 if full or not parents:
532 536 files = copyfiles = ctx.manifest()
533 537 if parents:
534 538 if self._changescache[0] == rev:
535 539 ma, r = self._changescache[1]
536 540 else:
537 541 ma, r = self._changedfiles(parents[0], ctx)
538 542 if not full:
539 543 files = ma + r
540 544 copyfiles = ma
541 545 # _getcopies() is also run for roots and before filtering so missing
542 546 # revlogs are detected early
543 547 copies = self._getcopies(ctx, parents, copyfiles)
544 548 cleanp2 = set()
545 549 if len(parents) == 2:
546 550 d = parents[1].manifest().diff(ctx.manifest(), clean=True)
547 551 for f, value in d.iteritems():
548 552 if value is None:
549 553 cleanp2.add(f)
550 554 changes = [(f, rev) for f in files if f not in self.ignored]
551 555 changes.sort()
552 556 return changes, copies, cleanp2
553 557
554 558 def _getcopies(self, ctx, parents, files):
555 559 copies = {}
556 560 for name in files:
557 561 if name in self.ignored:
558 562 continue
559 563 try:
560 564 copysource = ctx.filectx(name).copysource()
561 565 if copysource in self.ignored:
562 566 continue
563 567 # Ignore copy sources not in parent revisions
564 568 if not any(copysource in p for p in parents):
565 569 continue
566 570 copies[name] = copysource
567 571 except TypeError:
568 572 pass
569 573 except error.LookupError as e:
570 574 if not self.ignoreerrors:
571 575 raise
572 576 self.ignored.add(name)
573 577 self.ui.warn(_('ignoring: %s\n') % e)
574 578 return copies
575 579
576 580 def getcommit(self, rev):
577 581 ctx = self._changectx(rev)
578 582 _parents = self._parents(ctx)
579 583 parents = [p.hex() for p in _parents]
580 584 optparents = [p.hex() for p in ctx.parents() if p and p not in _parents]
581 585 crev = rev
582 586
583 587 return common.commit(author=ctx.user(),
584 588 date=dateutil.datestr(ctx.date(),
585 589 '%Y-%m-%d %H:%M:%S %1%2'),
586 590 desc=ctx.description(),
587 591 rev=crev,
588 592 parents=parents,
589 593 optparents=optparents,
590 594 branch=ctx.branch(),
591 595 extra=ctx.extra(),
592 596 sortkey=ctx.rev(),
593 597 saverev=self.saverev,
594 phase=ctx.phase())
598 phase=ctx.phase(),
599 ctx=ctx)
595 600
596 601 def numcommits(self):
597 602 return len(self.repo)
598 603
599 604 def gettags(self):
600 605 # This will get written to .hgtags, filter non global tags out.
601 606 tags = [t for t in self.repo.tagslist()
602 607 if self.repo.tagtype(t[0]) == 'global']
603 608 return dict([(name, nodemod.hex(node)) for name, node in tags
604 609 if self.keep(node)])
605 610
606 611 def getchangedfiles(self, rev, i):
607 612 ctx = self._changectx(rev)
608 613 parents = self._parents(ctx)
609 614 if not parents and i is None:
610 615 i = 0
611 616 ma, r = ctx.manifest().keys(), []
612 617 else:
613 618 i = i or 0
614 619 ma, r = self._changedfiles(parents[i], ctx)
615 620 ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)]
616 621
617 622 if i == 0:
618 623 self._changescache = (rev, (ma, r))
619 624
620 625 return ma + r
621 626
622 627 def converted(self, rev, destrev):
623 628 if self.convertfp is None:
624 629 self.convertfp = open(self.repo.vfs.join('shamap'), 'ab')
625 630 self.convertfp.write(util.tonativeeol('%s %s\n' % (destrev, rev)))
626 631 self.convertfp.flush()
627 632
628 633 def before(self):
629 634 self.ui.debug('run hg source pre-conversion action\n')
630 635
631 636 def after(self):
632 637 self.ui.debug('run hg source post-conversion action\n')
633 638
634 639 def hasnativeorder(self):
635 640 return True
636 641
637 642 def hasnativeclose(self):
638 643 return True
639 644
640 645 def lookuprev(self, rev):
641 646 try:
642 647 return nodemod.hex(self.repo.lookup(rev))
643 648 except (error.RepoError, error.LookupError):
644 649 return None
645 650
646 651 def getbookmarks(self):
647 652 return bookmarks.listbookmarks(self.repo)
648 653
649 654 def checkrevformat(self, revstr, mapname='splicemap'):
650 655 """ Mercurial, revision string is a 40 byte hex """
651 656 self.checkhexformat(revstr, mapname)
@@ -1,421 +1,421 b''
1 1 """automatically manage newlines in repository files
2 2
3 3 This extension allows you to manage the type of line endings (CRLF or
4 4 LF) that are used in the repository and in the local working
5 5 directory. That way you can get CRLF line endings on Windows and LF on
6 6 Unix/Mac, thereby letting everybody use their OS native line endings.
7 7
8 8 The extension reads its configuration from a versioned ``.hgeol``
9 9 configuration file found in the root of the working directory. The
10 10 ``.hgeol`` file use the same syntax as all other Mercurial
11 11 configuration files. It uses two sections, ``[patterns]`` and
12 12 ``[repository]``.
13 13
14 14 The ``[patterns]`` section specifies how line endings should be
15 15 converted between the working directory and the repository. The format is
16 16 specified by a file pattern. The first match is used, so put more
17 17 specific patterns first. The available line endings are ``LF``,
18 18 ``CRLF``, and ``BIN``.
19 19
20 20 Files with the declared format of ``CRLF`` or ``LF`` are always
21 21 checked out and stored in the repository in that format and files
22 22 declared to be binary (``BIN``) are left unchanged. Additionally,
23 23 ``native`` is an alias for checking out in the platform's default line
24 24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
25 25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
26 26 default behavior; it is only needed if you need to override a later,
27 27 more general pattern.
28 28
29 29 The optional ``[repository]`` section specifies the line endings to
30 30 use for files stored in the repository. It has a single setting,
31 31 ``native``, which determines the storage line endings for files
32 32 declared as ``native`` in the ``[patterns]`` section. It can be set to
33 33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
34 34 that on Windows, files configured as ``native`` (``CRLF`` by default)
35 35 will be converted to ``LF`` when stored in the repository. Files
36 36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
37 37 are always stored as-is in the repository.
38 38
39 39 Example versioned ``.hgeol`` file::
40 40
41 41 [patterns]
42 42 **.py = native
43 43 **.vcproj = CRLF
44 44 **.txt = native
45 45 Makefile = LF
46 46 **.jpg = BIN
47 47
48 48 [repository]
49 49 native = LF
50 50
51 51 .. note::
52 52
53 53 The rules will first apply when files are touched in the working
54 54 directory, e.g. by updating to null and back to tip to touch all files.
55 55
56 56 The extension uses an optional ``[eol]`` section read from both the
57 57 normal Mercurial configuration files and the ``.hgeol`` file, with the
58 58 latter overriding the former. You can use that section to control the
59 59 overall behavior. There are three settings:
60 60
61 61 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
62 62 ``CRLF`` to override the default interpretation of ``native`` for
63 63 checkout. This can be used with :hg:`archive` on Unix, say, to
64 64 generate an archive where files have line endings for Windows.
65 65
66 66 - ``eol.only-consistent`` (default True) can be set to False to make
67 67 the extension convert files with inconsistent EOLs. Inconsistent
68 68 means that there is both ``CRLF`` and ``LF`` present in the file.
69 69 Such files are normally not touched under the assumption that they
70 70 have mixed EOLs on purpose.
71 71
72 72 - ``eol.fix-trailing-newline`` (default False) can be set to True to
73 73 ensure that converted files end with a EOL character (either ``\\n``
74 74 or ``\\r\\n`` as per the configured patterns).
75 75
76 76 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
77 77 like the deprecated win32text extension does. This means that you can
78 78 disable win32text and enable eol and your filters will still work. You
79 79 only need to these filters until you have prepared a ``.hgeol`` file.
80 80
81 81 The ``win32text.forbid*`` hooks provided by the win32text extension
82 82 have been unified into a single hook named ``eol.checkheadshook``. The
83 83 hook will lookup the expected line endings from the ``.hgeol`` file,
84 84 which means you must migrate to a ``.hgeol`` file first before using
85 85 the hook. ``eol.checkheadshook`` only checks heads, intermediate
86 86 invalid revisions will be pushed. To forbid them completely, use the
87 87 ``eol.checkallhook`` hook. These hooks are best used as
88 88 ``pretxnchangegroup`` hooks.
89 89
90 90 See :hg:`help patterns` for more information about the glob patterns
91 91 used.
92 92 """
93 93
94 94 from __future__ import absolute_import
95 95
96 96 import os
97 97 import re
98 98 from mercurial.i18n import _
99 99 from mercurial import (
100 100 config,
101 101 error as errormod,
102 102 extensions,
103 103 match,
104 104 pycompat,
105 105 registrar,
106 106 scmutil,
107 107 util,
108 108 )
109 109 from mercurial.utils import (
110 110 stringutil,
111 111 )
112 112
113 113 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
114 114 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
115 115 # be specifying the version(s) of Mercurial they are tested with, or
116 116 # leave the attribute unspecified.
117 117 testedwith = 'ships-with-hg-core'
118 118
119 119 configtable = {}
120 120 configitem = registrar.configitem(configtable)
121 121
122 122 configitem('eol', 'fix-trailing-newline',
123 123 default=False,
124 124 )
125 125 configitem('eol', 'native',
126 126 default=pycompat.oslinesep,
127 127 )
128 128 configitem('eol', 'only-consistent',
129 129 default=True,
130 130 )
131 131
132 132 # Matches a lone LF, i.e., one that is not part of CRLF.
133 133 singlelf = re.compile('(^|[^\r])\n')
134 134
135 135 def inconsistenteol(data):
136 136 return '\r\n' in data and singlelf.search(data)
137 137
138 138 def tolf(s, params, ui, **kwargs):
139 139 """Filter to convert to LF EOLs."""
140 140 if stringutil.binary(s):
141 141 return s
142 142 if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
143 143 return s
144 144 if (ui.configbool('eol', 'fix-trailing-newline')
145 145 and s and not s.endswith('\n')):
146 146 s = s + '\n'
147 147 return util.tolf(s)
148 148
149 149 def tocrlf(s, params, ui, **kwargs):
150 150 """Filter to convert to CRLF EOLs."""
151 151 if stringutil.binary(s):
152 152 return s
153 153 if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
154 154 return s
155 155 if (ui.configbool('eol', 'fix-trailing-newline')
156 156 and s and not s.endswith('\n')):
157 157 s = s + '\n'
158 158 return util.tocrlf(s)
159 159
160 160 def isbinary(s, params):
161 161 """Filter to do nothing with the file."""
162 162 return s
163 163
164 164 filters = {
165 165 'to-lf': tolf,
166 166 'to-crlf': tocrlf,
167 167 'is-binary': isbinary,
168 168 # The following provide backwards compatibility with win32text
169 169 'cleverencode:': tolf,
170 170 'cleverdecode:': tocrlf
171 171 }
172 172
173 173 class eolfile(object):
174 174 def __init__(self, ui, root, data):
175 175 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
176 176 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
177 177
178 178 self.cfg = config.config()
179 179 # Our files should not be touched. The pattern must be
180 180 # inserted first override a '** = native' pattern.
181 181 self.cfg.set('patterns', '.hg*', 'BIN', 'eol')
182 182 # We can then parse the user's patterns.
183 183 self.cfg.parse('.hgeol', data)
184 184
185 185 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
186 186 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
187 187 iswdlf = ui.config('eol', 'native') in ('LF', '\n')
188 188 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
189 189
190 190 include = []
191 191 exclude = []
192 192 self.patterns = []
193 193 for pattern, style in self.cfg.items('patterns'):
194 194 key = style.upper()
195 195 if key == 'BIN':
196 196 exclude.append(pattern)
197 197 else:
198 198 include.append(pattern)
199 199 m = match.match(root, '', [pattern])
200 200 self.patterns.append((pattern, key, m))
201 201 # This will match the files for which we need to care
202 202 # about inconsistent newlines.
203 203 self.match = match.match(root, '', [], include, exclude)
204 204
205 205 def copytoui(self, ui):
206 206 for pattern, key, m in self.patterns:
207 207 try:
208 208 ui.setconfig('decode', pattern, self._decode[key], 'eol')
209 209 ui.setconfig('encode', pattern, self._encode[key], 'eol')
210 210 except KeyError:
211 211 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
212 212 % (key, self.cfg.source('patterns', pattern)))
213 213 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
214 214 for k, v in self.cfg.items('eol'):
215 215 ui.setconfig('eol', k, v, 'eol')
216 216
217 217 def checkrev(self, repo, ctx, files):
218 218 failed = []
219 219 for f in (files or ctx.files()):
220 220 if f not in ctx:
221 221 continue
222 222 for pattern, key, m in self.patterns:
223 223 if not m(f):
224 224 continue
225 225 target = self._encode[key]
226 226 data = ctx[f].data()
227 227 if (target == "to-lf" and "\r\n" in data
228 228 or target == "to-crlf" and singlelf.search(data)):
229 229 failed.append((f, target, bytes(ctx)))
230 230 break
231 231 return failed
232 232
233 233 def parseeol(ui, repo, nodes):
234 234 try:
235 235 for node in nodes:
236 236 try:
237 237 if node is None:
238 238 # Cannot use workingctx.data() since it would load
239 239 # and cache the filters before we configure them.
240 240 data = repo.wvfs('.hgeol').read()
241 241 else:
242 242 data = repo[node]['.hgeol'].data()
243 243 return eolfile(ui, repo.root, data)
244 244 except (IOError, LookupError):
245 245 pass
246 246 except errormod.ParseError as inst:
247 247 ui.warn(_("warning: ignoring .hgeol file due to parse error "
248 248 "at %s: %s\n") % (inst.args[1], inst.args[0]))
249 249 return None
250 250
251 251 def ensureenabled(ui):
252 252 """make sure the extension is enabled when used as hook
253 253
254 254 When eol is used through hooks, the extension is never formally loaded and
255 255 enabled. This has some side effect, for example the config declaration is
256 256 never loaded. This function ensure the extension is enabled when running
257 257 hooks.
258 258 """
259 259 if 'eol' in ui._knownconfig:
260 260 return
261 261 ui.setconfig('extensions', 'eol', '', source='internal')
262 262 extensions.loadall(ui, ['eol'])
263 263
264 264 def _checkhook(ui, repo, node, headsonly):
265 265 # Get revisions to check and touched files at the same time
266 266 ensureenabled(ui)
267 267 files = set()
268 268 revs = set()
269 269 for rev in pycompat.xrange(repo[node].rev(), len(repo)):
270 270 revs.add(rev)
271 271 if headsonly:
272 272 ctx = repo[rev]
273 273 files.update(ctx.files())
274 274 for pctx in ctx.parents():
275 275 revs.discard(pctx.rev())
276 276 failed = []
277 277 for rev in revs:
278 278 ctx = repo[rev]
279 279 eol = parseeol(ui, repo, [ctx.node()])
280 280 if eol:
281 281 failed.extend(eol.checkrev(repo, ctx, files))
282 282
283 283 if failed:
284 284 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
285 285 msgs = []
286 286 for f, target, node in sorted(failed):
287 287 msgs.append(_(" %s in %s should not have %s line endings") %
288 288 (f, node, eols[target]))
289 289 raise errormod.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
290 290
291 291 def checkallhook(ui, repo, node, hooktype, **kwargs):
292 292 """verify that files have expected EOLs"""
293 293 _checkhook(ui, repo, node, False)
294 294
295 295 def checkheadshook(ui, repo, node, hooktype, **kwargs):
296 296 """verify that files have expected EOLs"""
297 297 _checkhook(ui, repo, node, True)
298 298
299 299 # "checkheadshook" used to be called "hook"
300 300 hook = checkheadshook
301 301
302 302 def preupdate(ui, repo, hooktype, parent1, parent2):
303 303 p1node = scmutil.resolvehexnodeidprefix(repo, parent1)
304 304 repo.loadeol([p1node])
305 305 return False
306 306
307 307 def uisetup(ui):
308 308 ui.setconfig('hooks', 'preupdate.eol', preupdate, 'eol')
309 309
310 310 def extsetup(ui):
311 311 try:
312 312 extensions.find('win32text')
313 313 ui.warn(_("the eol extension is incompatible with the "
314 314 "win32text extension\n"))
315 315 except KeyError:
316 316 pass
317 317
318 318
319 319 def reposetup(ui, repo):
320 320 uisetup(repo.ui)
321 321
322 322 if not repo.local():
323 323 return
324 324 for name, fn in filters.iteritems():
325 325 repo.adddatafilter(name, fn)
326 326
327 327 ui.setconfig('patch', 'eol', 'auto', 'eol')
328 328
329 329 class eolrepo(repo.__class__):
330 330
331 331 def loadeol(self, nodes):
332 332 eol = parseeol(self.ui, self, nodes)
333 333 if eol is None:
334 334 return None
335 335 eol.copytoui(self.ui)
336 336 return eol.match
337 337
338 338 def _hgcleardirstate(self):
339 339 self._eolmatch = self.loadeol([None, 'tip'])
340 340 if not self._eolmatch:
341 341 self._eolmatch = util.never
342 342 return
343 343
344 344 oldeol = None
345 345 try:
346 346 cachemtime = os.path.getmtime(self.vfs.join("eol.cache"))
347 347 except OSError:
348 348 cachemtime = 0
349 349 else:
350 350 olddata = self.vfs.read("eol.cache")
351 351 if olddata:
352 352 oldeol = eolfile(self.ui, self.root, olddata)
353 353
354 354 try:
355 355 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
356 356 except OSError:
357 357 eolmtime = 0
358 358
359 359 if eolmtime > cachemtime:
360 360 self.ui.debug("eol: detected change in .hgeol\n")
361 361
362 362 hgeoldata = self.wvfs.read('.hgeol')
363 363 neweol = eolfile(self.ui, self.root, hgeoldata)
364 364
365 365 wlock = None
366 366 try:
367 367 wlock = self.wlock()
368 368 for f in self.dirstate:
369 369 if self.dirstate[f] != 'n':
370 370 continue
371 371 if oldeol is not None:
372 372 if not oldeol.match(f) and not neweol.match(f):
373 373 continue
374 374 oldkey = None
375 375 for pattern, key, m in oldeol.patterns:
376 376 if m(f):
377 377 oldkey = key
378 378 break
379 379 newkey = None
380 380 for pattern, key, m in neweol.patterns:
381 381 if m(f):
382 382 newkey = key
383 383 break
384 384 if oldkey == newkey:
385 385 continue
386 386 # all normal files need to be looked at again since
387 387 # the new .hgeol file specify a different filter
388 388 self.dirstate.normallookup(f)
389 389 # Write the cache to update mtime and cache .hgeol
390 390 with self.vfs("eol.cache", "w") as f:
391 391 f.write(hgeoldata)
392 392 except errormod.LockUnavailable:
393 393 # If we cannot lock the repository and clear the
394 394 # dirstate, then a commit might not see all files
395 395 # as modified. But if we cannot lock the
396 396 # repository, then we can also not make a commit,
397 397 # so ignore the error.
398 398 pass
399 399 finally:
400 400 if wlock is not None:
401 401 wlock.release()
402 402
403 def commitctx(self, ctx, error=False):
403 def commitctx(self, ctx, error=False, origctx=None):
404 404 for f in sorted(ctx.added() + ctx.modified()):
405 405 if not self._eolmatch(f):
406 406 continue
407 407 fctx = ctx[f]
408 408 if fctx is None:
409 409 continue
410 410 data = fctx.data()
411 411 if stringutil.binary(data):
412 412 # We should not abort here, since the user should
413 413 # be able to say "** = native" to automatically
414 414 # have all non-binary files taken care of.
415 415 continue
416 416 if inconsistenteol(data):
417 417 raise errormod.Abort(_("inconsistent newline style "
418 418 "in %s\n") % f)
419 return super(eolrepo, self).commitctx(ctx, error)
419 return super(eolrepo, self).commitctx(ctx, error, origctx)
420 420 repo.__class__ = eolrepo
421 421 repo._hgcleardirstate()
@@ -1,817 +1,817 b''
1 1 # keyword.py - $Keyword$ expansion for Mercurial
2 2 #
3 3 # Copyright 2007-2015 Christian Ebert <blacktrash@gmx.net>
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 # $Id$
9 9 #
10 10 # Keyword expansion hack against the grain of a Distributed SCM
11 11 #
12 12 # There are many good reasons why this is not needed in a distributed
13 13 # SCM, still it may be useful in very small projects based on single
14 14 # files (like LaTeX packages), that are mostly addressed to an
15 15 # audience not running a version control system.
16 16 #
17 17 # For in-depth discussion refer to
18 18 # <https://mercurial-scm.org/wiki/KeywordPlan>.
19 19 #
20 20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 21 #
22 22 # Binary files are not touched.
23 23 #
24 24 # Files to act upon/ignore are specified in the [keyword] section.
25 25 # Customized keyword template mappings in the [keywordmaps] section.
26 26 #
27 27 # Run 'hg help keyword' and 'hg kwdemo' to get info on configuration.
28 28
29 29 '''expand keywords in tracked files
30 30
31 31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 32 tracked text files selected by your configuration.
33 33
34 34 Keywords are only expanded in local repositories and not stored in the
35 35 change history. The mechanism can be regarded as a convenience for the
36 36 current user or for archive distribution.
37 37
38 38 Keywords expand to the changeset data pertaining to the latest change
39 39 relative to the working directory parent of each file.
40 40
41 41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 42 sections of hgrc files.
43 43
44 44 Example::
45 45
46 46 [keyword]
47 47 # expand keywords in every python file except those matching "x*"
48 48 **.py =
49 49 x* = ignore
50 50
51 51 [keywordset]
52 52 # prefer svn- over cvs-like default keywordmaps
53 53 svn = True
54 54
55 55 .. note::
56 56
57 57 The more specific you are in your filename patterns the less you
58 58 lose speed in huge repositories.
59 59
60 60 For [keywordmaps] template mapping and expansion demonstration and
61 61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
62 62 available templates and filters.
63 63
64 64 Three additional date template filters are provided:
65 65
66 66 :``utcdate``: "2006/09/18 15:13:13"
67 67 :``svnutcdate``: "2006-09-18 15:13:13Z"
68 68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
69 69
70 70 The default template mappings (view with :hg:`kwdemo -d`) can be
71 71 replaced with customized keywords and templates. Again, run
72 72 :hg:`kwdemo` to control the results of your configuration changes.
73 73
74 74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
75 75 to avoid storing expanded keywords in the change history.
76 76
77 77 To force expansion after enabling it, or a configuration change, run
78 78 :hg:`kwexpand`.
79 79
80 80 Expansions spanning more than one line and incremental expansions,
81 81 like CVS' $Log$, are not supported. A keyword template map "Log =
82 82 {desc}" expands to the first line of the changeset description.
83 83 '''
84 84
85 85
86 86 from __future__ import absolute_import
87 87
88 88 import os
89 89 import re
90 90 import weakref
91 91
92 92 from mercurial.i18n import _
93 93 from mercurial.hgweb import webcommands
94 94
95 95 from mercurial import (
96 96 cmdutil,
97 97 context,
98 98 dispatch,
99 99 error,
100 100 extensions,
101 101 filelog,
102 102 localrepo,
103 103 logcmdutil,
104 104 match,
105 105 patch,
106 106 pathutil,
107 107 pycompat,
108 108 registrar,
109 109 scmutil,
110 110 templatefilters,
111 111 templateutil,
112 112 util,
113 113 )
114 114 from mercurial.utils import (
115 115 dateutil,
116 116 stringutil,
117 117 )
118 118
119 119 cmdtable = {}
120 120 command = registrar.command(cmdtable)
121 121 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
122 122 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
123 123 # be specifying the version(s) of Mercurial they are tested with, or
124 124 # leave the attribute unspecified.
125 125 testedwith = 'ships-with-hg-core'
126 126
127 127 # hg commands that do not act on keywords
128 128 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
129 129 ' outgoing push tip verify convert email glog')
130 130
131 131 # webcommands that do not act on keywords
132 132 nokwwebcommands = ('annotate changeset rev filediff diff comparison')
133 133
134 134 # hg commands that trigger expansion only when writing to working dir,
135 135 # not when reading filelog, and unexpand when reading from working dir
136 136 restricted = ('merge kwexpand kwshrink record qrecord resolve transplant'
137 137 ' unshelve rebase graft backout histedit fetch')
138 138
139 139 # names of extensions using dorecord
140 140 recordextensions = 'record'
141 141
142 142 colortable = {
143 143 'kwfiles.enabled': 'green bold',
144 144 'kwfiles.deleted': 'cyan bold underline',
145 145 'kwfiles.enabledunknown': 'green',
146 146 'kwfiles.ignored': 'bold',
147 147 'kwfiles.ignoredunknown': 'none'
148 148 }
149 149
150 150 templatefilter = registrar.templatefilter()
151 151
152 152 configtable = {}
153 153 configitem = registrar.configitem(configtable)
154 154
155 155 configitem('keywordset', 'svn',
156 156 default=False,
157 157 )
158 158 # date like in cvs' $Date
159 159 @templatefilter('utcdate', intype=templateutil.date)
160 160 def utcdate(date):
161 161 '''Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
162 162 '''
163 163 dateformat = '%Y/%m/%d %H:%M:%S'
164 164 return dateutil.datestr((date[0], 0), dateformat)
165 165 # date like in svn's $Date
166 166 @templatefilter('svnisodate', intype=templateutil.date)
167 167 def svnisodate(date):
168 168 '''Date. Returns a date in this format: "2009-08-18 13:00:13
169 169 +0200 (Tue, 18 Aug 2009)".
170 170 '''
171 171 return dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
172 172 # date like in svn's $Id
173 173 @templatefilter('svnutcdate', intype=templateutil.date)
174 174 def svnutcdate(date):
175 175 '''Date. Returns a UTC-date in this format: "2009-08-18
176 176 11:00:13Z".
177 177 '''
178 178 dateformat = '%Y-%m-%d %H:%M:%SZ'
179 179 return dateutil.datestr((date[0], 0), dateformat)
180 180
181 181 # make keyword tools accessible
182 182 kwtools = {'hgcmd': ''}
183 183
184 184 def _defaultkwmaps(ui):
185 185 '''Returns default keywordmaps according to keywordset configuration.'''
186 186 templates = {
187 187 'Revision': '{node|short}',
188 188 'Author': '{author|user}',
189 189 }
190 190 kwsets = ({
191 191 'Date': '{date|utcdate}',
192 192 'RCSfile': '{file|basename},v',
193 193 'RCSFile': '{file|basename},v', # kept for backwards compatibility
194 194 # with hg-keyword
195 195 'Source': '{root}/{file},v',
196 196 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
197 197 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
198 198 }, {
199 199 'Date': '{date|svnisodate}',
200 200 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
201 201 'LastChangedRevision': '{node|short}',
202 202 'LastChangedBy': '{author|user}',
203 203 'LastChangedDate': '{date|svnisodate}',
204 204 })
205 205 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
206 206 return templates
207 207
208 208 def _shrinktext(text, subfunc):
209 209 '''Helper for keyword expansion removal in text.
210 210 Depending on subfunc also returns number of substitutions.'''
211 211 return subfunc(br'$\1$', text)
212 212
213 213 def _preselect(wstatus, changed):
214 214 '''Retrieves modified and added files from a working directory state
215 215 and returns the subset of each contained in given changed files
216 216 retrieved from a change context.'''
217 217 modified = [f for f in wstatus.modified if f in changed]
218 218 added = [f for f in wstatus.added if f in changed]
219 219 return modified, added
220 220
221 221
222 222 class kwtemplater(object):
223 223 '''
224 224 Sets up keyword templates, corresponding keyword regex, and
225 225 provides keyword substitution functions.
226 226 '''
227 227
228 228 def __init__(self, ui, repo, inc, exc):
229 229 self.ui = ui
230 230 self._repo = weakref.ref(repo)
231 231 self.match = match.match(repo.root, '', [], inc, exc)
232 232 self.restrict = kwtools['hgcmd'] in restricted.split()
233 233 self.postcommit = False
234 234
235 235 kwmaps = self.ui.configitems('keywordmaps')
236 236 if kwmaps: # override default templates
237 237 self.templates = dict(kwmaps)
238 238 else:
239 239 self.templates = _defaultkwmaps(self.ui)
240 240
241 241 @property
242 242 def repo(self):
243 243 return self._repo()
244 244
245 245 @util.propertycache
246 246 def escape(self):
247 247 '''Returns bar-separated and escaped keywords.'''
248 248 return '|'.join(map(stringutil.reescape, self.templates.keys()))
249 249
250 250 @util.propertycache
251 251 def rekw(self):
252 252 '''Returns regex for unexpanded keywords.'''
253 253 return re.compile(br'\$(%s)\$' % self.escape)
254 254
255 255 @util.propertycache
256 256 def rekwexp(self):
257 257 '''Returns regex for expanded keywords.'''
258 258 return re.compile(br'\$(%s): [^$\n\r]*? \$' % self.escape)
259 259
260 260 def substitute(self, data, path, ctx, subfunc):
261 261 '''Replaces keywords in data with expanded template.'''
262 262 def kwsub(mobj):
263 263 kw = mobj.group(1)
264 264 ct = logcmdutil.maketemplater(self.ui, self.repo,
265 265 self.templates[kw])
266 266 self.ui.pushbuffer()
267 267 ct.show(ctx, root=self.repo.root, file=path)
268 268 ekw = templatefilters.firstline(self.ui.popbuffer())
269 269 return '$%s: %s $' % (kw, ekw)
270 270 return subfunc(kwsub, data)
271 271
272 272 def linkctx(self, path, fileid):
273 273 '''Similar to filelog.linkrev, but returns a changectx.'''
274 274 return self.repo.filectx(path, fileid=fileid).changectx()
275 275
276 276 def expand(self, path, node, data):
277 277 '''Returns data with keywords expanded.'''
278 278 if (not self.restrict and self.match(path)
279 279 and not stringutil.binary(data)):
280 280 ctx = self.linkctx(path, node)
281 281 return self.substitute(data, path, ctx, self.rekw.sub)
282 282 return data
283 283
284 284 def iskwfile(self, cand, ctx):
285 285 '''Returns subset of candidates which are configured for keyword
286 286 expansion but are not symbolic links.'''
287 287 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
288 288
289 289 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
290 290 '''Overwrites selected files expanding/shrinking keywords.'''
291 291 if self.restrict or lookup or self.postcommit: # exclude kw_copy
292 292 candidates = self.iskwfile(candidates, ctx)
293 293 if not candidates:
294 294 return
295 295 kwcmd = self.restrict and lookup # kwexpand/kwshrink
296 296 if self.restrict or expand and lookup:
297 297 mf = ctx.manifest()
298 298 if self.restrict or rekw:
299 299 re_kw = self.rekw
300 300 else:
301 301 re_kw = self.rekwexp
302 302 if expand:
303 303 msg = _('overwriting %s expanding keywords\n')
304 304 else:
305 305 msg = _('overwriting %s shrinking keywords\n')
306 306 for f in candidates:
307 307 if self.restrict:
308 308 data = self.repo.file(f).read(mf[f])
309 309 else:
310 310 data = self.repo.wread(f)
311 311 if stringutil.binary(data):
312 312 continue
313 313 if expand:
314 314 parents = ctx.parents()
315 315 if lookup:
316 316 ctx = self.linkctx(f, mf[f])
317 317 elif self.restrict and len(parents) > 1:
318 318 # merge commit
319 319 # in case of conflict f is in modified state during
320 320 # merge, even if f does not differ from f in parent
321 321 for p in parents:
322 322 if f in p and not p[f].cmp(ctx[f]):
323 323 ctx = p[f].changectx()
324 324 break
325 325 data, found = self.substitute(data, f, ctx, re_kw.subn)
326 326 elif self.restrict:
327 327 found = re_kw.search(data)
328 328 else:
329 329 data, found = _shrinktext(data, re_kw.subn)
330 330 if found:
331 331 self.ui.note(msg % f)
332 332 fp = self.repo.wvfs(f, "wb", atomictemp=True)
333 333 fp.write(data)
334 334 fp.close()
335 335 if kwcmd:
336 336 self.repo.dirstate.normal(f)
337 337 elif self.postcommit:
338 338 self.repo.dirstate.normallookup(f)
339 339
340 340 def shrink(self, fname, text):
341 341 '''Returns text with all keyword substitutions removed.'''
342 342 if self.match(fname) and not stringutil.binary(text):
343 343 return _shrinktext(text, self.rekwexp.sub)
344 344 return text
345 345
346 346 def shrinklines(self, fname, lines):
347 347 '''Returns lines with keyword substitutions removed.'''
348 348 if self.match(fname):
349 349 text = ''.join(lines)
350 350 if not stringutil.binary(text):
351 351 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
352 352 return lines
353 353
354 354 def wread(self, fname, data):
355 355 '''If in restricted mode returns data read from wdir with
356 356 keyword substitutions removed.'''
357 357 if self.restrict:
358 358 return self.shrink(fname, data)
359 359 return data
360 360
361 361 class kwfilelog(filelog.filelog):
362 362 '''
363 363 Subclass of filelog to hook into its read, add, cmp methods.
364 364 Keywords are "stored" unexpanded, and processed on reading.
365 365 '''
366 366 def __init__(self, opener, kwt, path):
367 367 super(kwfilelog, self).__init__(opener, path)
368 368 self.kwt = kwt
369 369 self.path = path
370 370
371 371 def read(self, node):
372 372 '''Expands keywords when reading filelog.'''
373 373 data = super(kwfilelog, self).read(node)
374 374 if self.renamed(node):
375 375 return data
376 376 return self.kwt.expand(self.path, node, data)
377 377
378 378 def add(self, text, meta, tr, link, p1=None, p2=None):
379 379 '''Removes keyword substitutions when adding to filelog.'''
380 380 text = self.kwt.shrink(self.path, text)
381 381 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
382 382
383 383 def cmp(self, node, text):
384 384 '''Removes keyword substitutions for comparison.'''
385 385 text = self.kwt.shrink(self.path, text)
386 386 return super(kwfilelog, self).cmp(node, text)
387 387
388 388 def _status(ui, repo, wctx, kwt, *pats, **opts):
389 389 '''Bails out if [keyword] configuration is not active.
390 390 Returns status of working directory.'''
391 391 if kwt:
392 392 opts = pycompat.byteskwargs(opts)
393 393 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
394 394 unknown=opts.get('unknown') or opts.get('all'))
395 395 if ui.configitems('keyword'):
396 396 raise error.Abort(_('[keyword] patterns cannot match'))
397 397 raise error.Abort(_('no [keyword] patterns configured'))
398 398
399 399 def _kwfwrite(ui, repo, expand, *pats, **opts):
400 400 '''Selects files and passes them to kwtemplater.overwrite.'''
401 401 wctx = repo[None]
402 402 if len(wctx.parents()) > 1:
403 403 raise error.Abort(_('outstanding uncommitted merge'))
404 404 kwt = getattr(repo, '_keywordkwt', None)
405 405 with repo.wlock():
406 406 status = _status(ui, repo, wctx, kwt, *pats, **opts)
407 407 if status.modified or status.added or status.removed or status.deleted:
408 408 raise error.Abort(_('outstanding uncommitted changes'))
409 409 kwt.overwrite(wctx, status.clean, True, expand)
410 410
411 411 @command('kwdemo',
412 412 [('d', 'default', None, _('show default keyword template maps')),
413 413 ('f', 'rcfile', '',
414 414 _('read maps from rcfile'), _('FILE'))],
415 415 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'),
416 416 optionalrepo=True)
417 417 def demo(ui, repo, *args, **opts):
418 418 '''print [keywordmaps] configuration and an expansion example
419 419
420 420 Show current, custom, or default keyword template maps and their
421 421 expansions.
422 422
423 423 Extend the current configuration by specifying maps as arguments
424 424 and using -f/--rcfile to source an external hgrc file.
425 425
426 426 Use -d/--default to disable current configuration.
427 427
428 428 See :hg:`help templates` for information on templates and filters.
429 429 '''
430 430 def demoitems(section, items):
431 431 ui.write('[%s]\n' % section)
432 432 for k, v in sorted(items):
433 433 if isinstance(v, bool):
434 434 v = stringutil.pprint(v)
435 435 ui.write('%s = %s\n' % (k, v))
436 436
437 437 fn = 'demo.txt'
438 438 tmpdir = pycompat.mkdtemp('', 'kwdemo.')
439 439 ui.note(_('creating temporary repository at %s\n') % tmpdir)
440 440 if repo is None:
441 441 baseui = ui
442 442 else:
443 443 baseui = repo.baseui
444 444 repo = localrepo.instance(baseui, tmpdir, create=True)
445 445 ui.setconfig('keyword', fn, '', 'keyword')
446 446 svn = ui.configbool('keywordset', 'svn')
447 447 # explicitly set keywordset for demo output
448 448 ui.setconfig('keywordset', 'svn', svn, 'keyword')
449 449
450 450 uikwmaps = ui.configitems('keywordmaps')
451 451 if args or opts.get(r'rcfile'):
452 452 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
453 453 if uikwmaps:
454 454 ui.status(_('\textending current template maps\n'))
455 455 if opts.get(r'default') or not uikwmaps:
456 456 if svn:
457 457 ui.status(_('\toverriding default svn keywordset\n'))
458 458 else:
459 459 ui.status(_('\toverriding default cvs keywordset\n'))
460 460 if opts.get(r'rcfile'):
461 461 ui.readconfig(opts.get('rcfile'))
462 462 if args:
463 463 # simulate hgrc parsing
464 464 rcmaps = '[keywordmaps]\n%s\n' % '\n'.join(args)
465 465 repo.vfs.write('hgrc', rcmaps)
466 466 ui.readconfig(repo.vfs.join('hgrc'))
467 467 kwmaps = dict(ui.configitems('keywordmaps'))
468 468 elif opts.get(r'default'):
469 469 if svn:
470 470 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
471 471 else:
472 472 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
473 473 kwmaps = _defaultkwmaps(ui)
474 474 if uikwmaps:
475 475 ui.status(_('\tdisabling current template maps\n'))
476 476 for k, v in kwmaps.iteritems():
477 477 ui.setconfig('keywordmaps', k, v, 'keyword')
478 478 else:
479 479 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
480 480 if uikwmaps:
481 481 kwmaps = dict(uikwmaps)
482 482 else:
483 483 kwmaps = _defaultkwmaps(ui)
484 484
485 485 uisetup(ui)
486 486 reposetup(ui, repo)
487 487 ui.write(('[extensions]\nkeyword =\n'))
488 488 demoitems('keyword', ui.configitems('keyword'))
489 489 demoitems('keywordset', ui.configitems('keywordset'))
490 490 demoitems('keywordmaps', kwmaps.iteritems())
491 491 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
492 492 repo.wvfs.write(fn, keywords)
493 493 repo[None].add([fn])
494 494 ui.note(_('\nkeywords written to %s:\n') % fn)
495 495 ui.note(keywords)
496 496 with repo.wlock():
497 497 repo.dirstate.setbranch('demobranch')
498 498 for name, cmd in ui.configitems('hooks'):
499 499 if name.split('.', 1)[0].find('commit') > -1:
500 500 repo.ui.setconfig('hooks', name, '', 'keyword')
501 501 msg = _('hg keyword configuration and expansion example')
502 502 ui.note(("hg ci -m '%s'\n" % msg))
503 503 repo.commit(text=msg)
504 504 ui.status(_('\n\tkeywords expanded\n'))
505 505 ui.write(repo.wread(fn))
506 506 repo.wvfs.rmtree(repo.root)
507 507
508 508 @command('kwexpand',
509 509 cmdutil.walkopts,
510 510 _('hg kwexpand [OPTION]... [FILE]...'),
511 511 inferrepo=True)
512 512 def expand(ui, repo, *pats, **opts):
513 513 '''expand keywords in the working directory
514 514
515 515 Run after (re)enabling keyword expansion.
516 516
517 517 kwexpand refuses to run if given files contain local changes.
518 518 '''
519 519 # 3rd argument sets expansion to True
520 520 _kwfwrite(ui, repo, True, *pats, **opts)
521 521
522 522 @command('kwfiles',
523 523 [('A', 'all', None, _('show keyword status flags of all files')),
524 524 ('i', 'ignore', None, _('show files excluded from expansion')),
525 525 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
526 526 ] + cmdutil.walkopts,
527 527 _('hg kwfiles [OPTION]... [FILE]...'),
528 528 inferrepo=True)
529 529 def files(ui, repo, *pats, **opts):
530 530 '''show files configured for keyword expansion
531 531
532 532 List which files in the working directory are matched by the
533 533 [keyword] configuration patterns.
534 534
535 535 Useful to prevent inadvertent keyword expansion and to speed up
536 536 execution by including only files that are actual candidates for
537 537 expansion.
538 538
539 539 See :hg:`help keyword` on how to construct patterns both for
540 540 inclusion and exclusion of files.
541 541
542 542 With -A/--all and -v/--verbose the codes used to show the status
543 543 of files are::
544 544
545 545 K = keyword expansion candidate
546 546 k = keyword expansion candidate (not tracked)
547 547 I = ignored
548 548 i = ignored (not tracked)
549 549 '''
550 550 kwt = getattr(repo, '_keywordkwt', None)
551 551 wctx = repo[None]
552 552 status = _status(ui, repo, wctx, kwt, *pats, **opts)
553 553 if pats:
554 554 cwd = repo.getcwd()
555 555 else:
556 556 cwd = ''
557 557 files = []
558 558 opts = pycompat.byteskwargs(opts)
559 559 if not opts.get('unknown') or opts.get('all'):
560 560 files = sorted(status.modified + status.added + status.clean)
561 561 kwfiles = kwt.iskwfile(files, wctx)
562 562 kwdeleted = kwt.iskwfile(status.deleted, wctx)
563 563 kwunknown = kwt.iskwfile(status.unknown, wctx)
564 564 if not opts.get('ignore') or opts.get('all'):
565 565 showfiles = kwfiles, kwdeleted, kwunknown
566 566 else:
567 567 showfiles = [], [], []
568 568 if opts.get('all') or opts.get('ignore'):
569 569 showfiles += ([f for f in files if f not in kwfiles],
570 570 [f for f in status.unknown if f not in kwunknown])
571 571 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
572 572 kwstates = zip(kwlabels, pycompat.bytestr('K!kIi'), showfiles)
573 573 fm = ui.formatter('kwfiles', opts)
574 574 fmt = '%.0s%s\n'
575 575 if opts.get('all') or ui.verbose:
576 576 fmt = '%s %s\n'
577 577 for kwstate, char, filenames in kwstates:
578 578 label = 'kwfiles.' + kwstate
579 579 for f in filenames:
580 580 fm.startitem()
581 581 fm.data(kwstatus=char, path=f)
582 582 fm.plain(fmt % (char, repo.pathto(f, cwd)), label=label)
583 583 fm.end()
584 584
585 585 @command('kwshrink',
586 586 cmdutil.walkopts,
587 587 _('hg kwshrink [OPTION]... [FILE]...'),
588 588 inferrepo=True)
589 589 def shrink(ui, repo, *pats, **opts):
590 590 '''revert expanded keywords in the working directory
591 591
592 592 Must be run before changing/disabling active keywords.
593 593
594 594 kwshrink refuses to run if given files contain local changes.
595 595 '''
596 596 # 3rd argument sets expansion to False
597 597 _kwfwrite(ui, repo, False, *pats, **opts)
598 598
599 599 # monkeypatches
600 600
601 601 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
602 602 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
603 603 rejects or conflicts due to expanded keywords in working dir.'''
604 604 orig(self, ui, gp, backend, store, eolmode)
605 605 kwt = getattr(getattr(backend, 'repo', None), '_keywordkwt', None)
606 606 if kwt:
607 607 # shrink keywords read from working dir
608 608 self.lines = kwt.shrinklines(self.fname, self.lines)
609 609
610 610 def kwdiff(orig, repo, *args, **kwargs):
611 611 '''Monkeypatch patch.diff to avoid expansion.'''
612 612 kwt = getattr(repo, '_keywordkwt', None)
613 613 if kwt:
614 614 restrict = kwt.restrict
615 615 kwt.restrict = True
616 616 try:
617 617 for chunk in orig(repo, *args, **kwargs):
618 618 yield chunk
619 619 finally:
620 620 if kwt:
621 621 kwt.restrict = restrict
622 622
623 623 def kwweb_skip(orig, web):
624 624 '''Wraps webcommands.x turning off keyword expansion.'''
625 625 kwt = getattr(web.repo, '_keywordkwt', None)
626 626 if kwt:
627 627 origmatch = kwt.match
628 628 kwt.match = util.never
629 629 try:
630 630 for chunk in orig(web):
631 631 yield chunk
632 632 finally:
633 633 if kwt:
634 634 kwt.match = origmatch
635 635
636 636 def kw_amend(orig, ui, repo, old, extra, pats, opts):
637 637 '''Wraps cmdutil.amend expanding keywords after amend.'''
638 638 kwt = getattr(repo, '_keywordkwt', None)
639 639 if kwt is None:
640 640 return orig(ui, repo, old, extra, pats, opts)
641 641 with repo.wlock():
642 642 kwt.postcommit = True
643 643 newid = orig(ui, repo, old, extra, pats, opts)
644 644 if newid != old.node():
645 645 ctx = repo[newid]
646 646 kwt.restrict = True
647 647 kwt.overwrite(ctx, ctx.files(), False, True)
648 648 kwt.restrict = False
649 649 return newid
650 650
651 651 def kw_copy(orig, ui, repo, pats, opts, rename=False):
652 652 '''Wraps cmdutil.copy so that copy/rename destinations do not
653 653 contain expanded keywords.
654 654 Note that the source of a regular file destination may also be a
655 655 symlink:
656 656 hg cp sym x -> x is symlink
657 657 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
658 658 For the latter we have to follow the symlink to find out whether its
659 659 target is configured for expansion and we therefore must unexpand the
660 660 keywords in the destination.'''
661 661 kwt = getattr(repo, '_keywordkwt', None)
662 662 if kwt is None:
663 663 return orig(ui, repo, pats, opts, rename)
664 664 with repo.wlock():
665 665 orig(ui, repo, pats, opts, rename)
666 666 if opts.get('dry_run'):
667 667 return
668 668 wctx = repo[None]
669 669 cwd = repo.getcwd()
670 670
671 671 def haskwsource(dest):
672 672 '''Returns true if dest is a regular file and configured for
673 673 expansion or a symlink which points to a file configured for
674 674 expansion. '''
675 675 source = repo.dirstate.copied(dest)
676 676 if 'l' in wctx.flags(source):
677 677 source = pathutil.canonpath(repo.root, cwd,
678 678 os.path.realpath(source))
679 679 return kwt.match(source)
680 680
681 681 candidates = [f for f in repo.dirstate.copies() if
682 682 'l' not in wctx.flags(f) and haskwsource(f)]
683 683 kwt.overwrite(wctx, candidates, False, False)
684 684
685 685 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
686 686 '''Wraps record.dorecord expanding keywords after recording.'''
687 687 kwt = getattr(repo, '_keywordkwt', None)
688 688 if kwt is None:
689 689 return orig(ui, repo, commitfunc, *pats, **opts)
690 690 with repo.wlock():
691 691 # record returns 0 even when nothing has changed
692 692 # therefore compare nodes before and after
693 693 kwt.postcommit = True
694 694 ctx = repo['.']
695 695 wstatus = ctx.status()
696 696 ret = orig(ui, repo, commitfunc, *pats, **opts)
697 697 recctx = repo['.']
698 698 if ctx != recctx:
699 699 modified, added = _preselect(wstatus, recctx.files())
700 700 kwt.restrict = False
701 701 kwt.overwrite(recctx, modified, False, True)
702 702 kwt.overwrite(recctx, added, False, True, True)
703 703 kwt.restrict = True
704 704 return ret
705 705
706 706 def kwfilectx_cmp(orig, self, fctx):
707 707 if fctx._customcmp:
708 708 return fctx.cmp(self)
709 709 kwt = getattr(self._repo, '_keywordkwt', None)
710 710 if kwt is None:
711 711 return orig(self, fctx)
712 712 # keyword affects data size, comparing wdir and filelog size does
713 713 # not make sense
714 714 if (fctx._filenode is None and
715 715 (self._repo._encodefilterpats or
716 716 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
717 717 self.size() - 4 == fctx.size()) or
718 718 self.size() == fctx.size()):
719 719 return self._filelog.cmp(self._filenode, fctx.data())
720 720 return True
721 721
722 722 def uisetup(ui):
723 723 ''' Monkeypatches dispatch._parse to retrieve user command.
724 724 Overrides file method to return kwfilelog instead of filelog
725 725 if file matches user configuration.
726 726 Wraps commit to overwrite configured files with updated
727 727 keyword substitutions.
728 728 Monkeypatches patch and webcommands.'''
729 729
730 730 def kwdispatch_parse(orig, ui, args):
731 731 '''Monkeypatch dispatch._parse to obtain running hg command.'''
732 732 cmd, func, args, options, cmdoptions = orig(ui, args)
733 733 kwtools['hgcmd'] = cmd
734 734 return cmd, func, args, options, cmdoptions
735 735
736 736 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
737 737
738 738 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
739 739 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
740 740 extensions.wrapfunction(patch, 'diff', kwdiff)
741 741 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
742 742 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
743 743 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
744 744 for c in nokwwebcommands.split():
745 745 extensions.wrapfunction(webcommands, c, kwweb_skip)
746 746
747 747 def reposetup(ui, repo):
748 748 '''Sets up repo as kwrepo for keyword substitution.'''
749 749
750 750 try:
751 751 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
752 752 or '.hg' in util.splitpath(repo.root)
753 753 or repo._url.startswith('bundle:')):
754 754 return
755 755 except AttributeError:
756 756 pass
757 757
758 758 inc, exc = [], ['.hg*']
759 759 for pat, opt in ui.configitems('keyword'):
760 760 if opt != 'ignore':
761 761 inc.append(pat)
762 762 else:
763 763 exc.append(pat)
764 764 if not inc:
765 765 return
766 766
767 767 kwt = kwtemplater(ui, repo, inc, exc)
768 768
769 769 class kwrepo(repo.__class__):
770 770 def file(self, f):
771 771 if f[0] == '/':
772 772 f = f[1:]
773 773 return kwfilelog(self.svfs, kwt, f)
774 774
775 775 def wread(self, filename):
776 776 data = super(kwrepo, self).wread(filename)
777 777 return kwt.wread(filename, data)
778 778
779 779 def commit(self, *args, **opts):
780 780 # use custom commitctx for user commands
781 781 # other extensions can still wrap repo.commitctx directly
782 782 self.commitctx = self.kwcommitctx
783 783 try:
784 784 return super(kwrepo, self).commit(*args, **opts)
785 785 finally:
786 786 del self.commitctx
787 787
788 def kwcommitctx(self, ctx, error=False):
789 n = super(kwrepo, self).commitctx(ctx, error)
788 def kwcommitctx(self, ctx, error=False, origctx=None):
789 n = super(kwrepo, self).commitctx(ctx, error, origctx)
790 790 # no lock needed, only called from repo.commit() which already locks
791 791 if not kwt.postcommit:
792 792 restrict = kwt.restrict
793 793 kwt.restrict = True
794 794 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
795 795 False, True)
796 796 kwt.restrict = restrict
797 797 return n
798 798
799 799 def rollback(self, dryrun=False, force=False):
800 800 with self.wlock():
801 801 origrestrict = kwt.restrict
802 802 try:
803 803 if not dryrun:
804 804 changed = self['.'].files()
805 805 ret = super(kwrepo, self).rollback(dryrun, force)
806 806 if not dryrun:
807 807 ctx = self['.']
808 808 modified, added = _preselect(ctx.status(), changed)
809 809 kwt.restrict = False
810 810 kwt.overwrite(ctx, modified, True, True)
811 811 kwt.overwrite(ctx, added, True, False)
812 812 return ret
813 813 finally:
814 814 kwt.restrict = origrestrict
815 815
816 816 repo.__class__ = kwrepo
817 817 repo._keywordkwt = kwt
@@ -1,383 +1,383 b''
1 1 # lfs - hash-preserving large file support using Git-LFS protocol
2 2 #
3 3 # Copyright 2017 Facebook, Inc.
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 """lfs - large file support (EXPERIMENTAL)
9 9
10 10 This extension allows large files to be tracked outside of the normal
11 11 repository storage and stored on a centralized server, similar to the
12 12 ``largefiles`` extension. The ``git-lfs`` protocol is used when
13 13 communicating with the server, so existing git infrastructure can be
14 14 harnessed. Even though the files are stored outside of the repository,
15 15 they are still integrity checked in the same manner as normal files.
16 16
17 17 The files stored outside of the repository are downloaded on demand,
18 18 which reduces the time to clone, and possibly the local disk usage.
19 19 This changes fundamental workflows in a DVCS, so careful thought
20 20 should be given before deploying it. :hg:`convert` can be used to
21 21 convert LFS repositories to normal repositories that no longer
22 22 require this extension, and do so without changing the commit hashes.
23 23 This allows the extension to be disabled if the centralized workflow
24 24 becomes burdensome. However, the pre and post convert clones will
25 25 not be able to communicate with each other unless the extension is
26 26 enabled on both.
27 27
28 28 To start a new repository, or to add LFS files to an existing one, just
29 29 create an ``.hglfs`` file as described below in the root directory of
30 30 the repository. Typically, this file should be put under version
31 31 control, so that the settings will propagate to other repositories with
32 32 push and pull. During any commit, Mercurial will consult this file to
33 33 determine if an added or modified file should be stored externally. The
34 34 type of storage depends on the characteristics of the file at each
35 35 commit. A file that is near a size threshold may switch back and forth
36 36 between LFS and normal storage, as needed.
37 37
38 38 Alternately, both normal repositories and largefile controlled
39 39 repositories can be converted to LFS by using :hg:`convert` and the
40 40 ``lfs.track`` config option described below. The ``.hglfs`` file
41 41 should then be created and added, to control subsequent LFS selection.
42 42 The hashes are also unchanged in this case. The LFS and non-LFS
43 43 repositories can be distinguished because the LFS repository will
44 44 abort any command if this extension is disabled.
45 45
46 46 Committed LFS files are held locally, until the repository is pushed.
47 47 Prior to pushing the normal repository data, the LFS files that are
48 48 tracked by the outgoing commits are automatically uploaded to the
49 49 configured central server. No LFS files are transferred on
50 50 :hg:`pull` or :hg:`clone`. Instead, the files are downloaded on
51 51 demand as they need to be read, if a cached copy cannot be found
52 52 locally. Both committing and downloading an LFS file will link the
53 53 file to a usercache, to speed up future access. See the `usercache`
54 54 config setting described below.
55 55
56 56 .hglfs::
57 57
58 58 The extension reads its configuration from a versioned ``.hglfs``
59 59 configuration file found in the root of the working directory. The
60 60 ``.hglfs`` file uses the same syntax as all other Mercurial
61 61 configuration files. It uses a single section, ``[track]``.
62 62
63 63 The ``[track]`` section specifies which files are stored as LFS (or
64 64 not). Each line is keyed by a file pattern, with a predicate value.
65 65 The first file pattern match is used, so put more specific patterns
66 66 first. The available predicates are ``all()``, ``none()``, and
67 67 ``size()``. See "hg help filesets.size" for the latter.
68 68
69 69 Example versioned ``.hglfs`` file::
70 70
71 71 [track]
72 72 # No Makefile or python file, anywhere, will be LFS
73 73 **Makefile = none()
74 74 **.py = none()
75 75
76 76 **.zip = all()
77 77 **.exe = size(">1MB")
78 78
79 79 # Catchall for everything not matched above
80 80 ** = size(">10MB")
81 81
82 82 Configs::
83 83
84 84 [lfs]
85 85 # Remote endpoint. Multiple protocols are supported:
86 86 # - http(s)://user:pass@example.com/path
87 87 # git-lfs endpoint
88 88 # - file:///tmp/path
89 89 # local filesystem, usually for testing
90 90 # if unset, lfs will assume the remote repository also handles blob storage
91 91 # for http(s) URLs. Otherwise, lfs will prompt to set this when it must
92 92 # use this value.
93 93 # (default: unset)
94 94 url = https://example.com/repo.git/info/lfs
95 95
96 96 # Which files to track in LFS. Path tests are "**.extname" for file
97 97 # extensions, and "path:under/some/directory" for path prefix. Both
98 98 # are relative to the repository root.
99 99 # File size can be tested with the "size()" fileset, and tests can be
100 100 # joined with fileset operators. (See "hg help filesets.operators".)
101 101 #
102 102 # Some examples:
103 103 # - all() # everything
104 104 # - none() # nothing
105 105 # - size(">20MB") # larger than 20MB
106 106 # - !**.txt # anything not a *.txt file
107 107 # - **.zip | **.tar.gz | **.7z # some types of compressed files
108 108 # - path:bin # files under "bin" in the project root
109 109 # - (**.php & size(">2MB")) | (**.js & size(">5MB")) | **.tar.gz
110 110 # | (path:bin & !path:/bin/README) | size(">1GB")
111 111 # (default: none())
112 112 #
113 113 # This is ignored if there is a tracked '.hglfs' file, and this setting
114 114 # will eventually be deprecated and removed.
115 115 track = size(">10M")
116 116
117 117 # how many times to retry before giving up on transferring an object
118 118 retry = 5
119 119
120 120 # the local directory to store lfs files for sharing across local clones.
121 121 # If not set, the cache is located in an OS specific cache location.
122 122 usercache = /path/to/global/cache
123 123 """
124 124
125 125 from __future__ import absolute_import
126 126
127 127 import sys
128 128
129 129 from mercurial.i18n import _
130 130
131 131 from mercurial import (
132 132 config,
133 133 context,
134 134 error,
135 135 exchange,
136 136 extensions,
137 137 exthelper,
138 138 filelog,
139 139 filesetlang,
140 140 localrepo,
141 141 minifileset,
142 142 node,
143 143 pycompat,
144 144 repository,
145 145 revlog,
146 146 scmutil,
147 147 templateutil,
148 148 util,
149 149 )
150 150
151 151 from . import (
152 152 blobstore,
153 153 wireprotolfsserver,
154 154 wrapper,
155 155 )
156 156
157 157 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
158 158 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
159 159 # be specifying the version(s) of Mercurial they are tested with, or
160 160 # leave the attribute unspecified.
161 161 testedwith = 'ships-with-hg-core'
162 162
163 163 eh = exthelper.exthelper()
164 164 eh.merge(wrapper.eh)
165 165 eh.merge(wireprotolfsserver.eh)
166 166
167 167 cmdtable = eh.cmdtable
168 168 configtable = eh.configtable
169 169 extsetup = eh.finalextsetup
170 170 uisetup = eh.finaluisetup
171 171 filesetpredicate = eh.filesetpredicate
172 172 reposetup = eh.finalreposetup
173 173 templatekeyword = eh.templatekeyword
174 174
175 175 eh.configitem('experimental', 'lfs.serve',
176 176 default=True,
177 177 )
178 178 eh.configitem('experimental', 'lfs.user-agent',
179 179 default=None,
180 180 )
181 181 eh.configitem('experimental', 'lfs.disableusercache',
182 182 default=False,
183 183 )
184 184 eh.configitem('experimental', 'lfs.worker-enable',
185 185 default=False,
186 186 )
187 187
188 188 eh.configitem('lfs', 'url',
189 189 default=None,
190 190 )
191 191 eh.configitem('lfs', 'usercache',
192 192 default=None,
193 193 )
194 194 # Deprecated
195 195 eh.configitem('lfs', 'threshold',
196 196 default=None,
197 197 )
198 198 eh.configitem('lfs', 'track',
199 199 default='none()',
200 200 )
201 201 eh.configitem('lfs', 'retry',
202 202 default=5,
203 203 )
204 204
205 205 lfsprocessor = (
206 206 wrapper.readfromstore,
207 207 wrapper.writetostore,
208 208 wrapper.bypasscheckhash,
209 209 )
210 210
211 211 def featuresetup(ui, supported):
212 212 # don't die on seeing a repo with the lfs requirement
213 213 supported |= {'lfs'}
214 214
215 215 @eh.uisetup
216 216 def _uisetup(ui):
217 217 localrepo.featuresetupfuncs.add(featuresetup)
218 218
219 219 @eh.reposetup
220 220 def _reposetup(ui, repo):
221 221 # Nothing to do with a remote repo
222 222 if not repo.local():
223 223 return
224 224
225 225 repo.svfs.lfslocalblobstore = blobstore.local(repo)
226 226 repo.svfs.lfsremoteblobstore = blobstore.remote(repo)
227 227
228 228 class lfsrepo(repo.__class__):
229 229 @localrepo.unfilteredmethod
230 def commitctx(self, ctx, error=False):
230 def commitctx(self, ctx, error=False, origctx=None):
231 231 repo.svfs.options['lfstrack'] = _trackedmatcher(self)
232 return super(lfsrepo, self).commitctx(ctx, error)
232 return super(lfsrepo, self).commitctx(ctx, error, origctx=origctx)
233 233
234 234 repo.__class__ = lfsrepo
235 235
236 236 if 'lfs' not in repo.requirements:
237 237 def checkrequireslfs(ui, repo, **kwargs):
238 238 if 'lfs' in repo.requirements:
239 239 return 0
240 240
241 241 last = kwargs.get(r'node_last')
242 242 _bin = node.bin
243 243 if last:
244 244 s = repo.set('%n:%n', _bin(kwargs[r'node']), _bin(last))
245 245 else:
246 246 s = repo.set('%n', _bin(kwargs[r'node']))
247 247 match = repo._storenarrowmatch
248 248 for ctx in s:
249 249 # TODO: is there a way to just walk the files in the commit?
250 250 if any(ctx[f].islfs() for f in ctx.files()
251 251 if f in ctx and match(f)):
252 252 repo.requirements.add('lfs')
253 253 repo.features.add(repository.REPO_FEATURE_LFS)
254 254 repo._writerequirements()
255 255 repo.prepushoutgoinghooks.add('lfs', wrapper.prepush)
256 256 break
257 257
258 258 ui.setconfig('hooks', 'commit.lfs', checkrequireslfs, 'lfs')
259 259 ui.setconfig('hooks', 'pretxnchangegroup.lfs', checkrequireslfs, 'lfs')
260 260 else:
261 261 repo.prepushoutgoinghooks.add('lfs', wrapper.prepush)
262 262
263 263 def _trackedmatcher(repo):
264 264 """Return a function (path, size) -> bool indicating whether or not to
265 265 track a given file with lfs."""
266 266 if not repo.wvfs.exists('.hglfs'):
267 267 # No '.hglfs' in wdir. Fallback to config for now.
268 268 trackspec = repo.ui.config('lfs', 'track')
269 269
270 270 # deprecated config: lfs.threshold
271 271 threshold = repo.ui.configbytes('lfs', 'threshold')
272 272 if threshold:
273 273 filesetlang.parse(trackspec) # make sure syntax errors are confined
274 274 trackspec = "(%s) | size('>%d')" % (trackspec, threshold)
275 275
276 276 return minifileset.compile(trackspec)
277 277
278 278 data = repo.wvfs.tryread('.hglfs')
279 279 if not data:
280 280 return lambda p, s: False
281 281
282 282 # Parse errors here will abort with a message that points to the .hglfs file
283 283 # and line number.
284 284 cfg = config.config()
285 285 cfg.parse('.hglfs', data)
286 286
287 287 try:
288 288 rules = [(minifileset.compile(pattern), minifileset.compile(rule))
289 289 for pattern, rule in cfg.items('track')]
290 290 except error.ParseError as e:
291 291 # The original exception gives no indicator that the error is in the
292 292 # .hglfs file, so add that.
293 293
294 294 # TODO: See if the line number of the file can be made available.
295 295 raise error.Abort(_('parse error in .hglfs: %s') % e)
296 296
297 297 def _match(path, size):
298 298 for pat, rule in rules:
299 299 if pat(path, size):
300 300 return rule(path, size)
301 301
302 302 return False
303 303
304 304 return _match
305 305
306 306 # Called by remotefilelog
307 307 def wrapfilelog(filelog):
308 308 wrapfunction = extensions.wrapfunction
309 309
310 310 wrapfunction(filelog, 'addrevision', wrapper.filelogaddrevision)
311 311 wrapfunction(filelog, 'renamed', wrapper.filelogrenamed)
312 312 wrapfunction(filelog, 'size', wrapper.filelogsize)
313 313
314 314 @eh.wrapfunction(localrepo, 'resolverevlogstorevfsoptions')
315 315 def _resolverevlogstorevfsoptions(orig, ui, requirements, features):
316 316 opts = orig(ui, requirements, features)
317 317 for name, module in extensions.extensions(ui):
318 318 if module is sys.modules[__name__]:
319 319 if revlog.REVIDX_EXTSTORED in opts[b'flagprocessors']:
320 320 msg = (_(b"cannot register multiple processors on flag '%#x'.")
321 321 % revlog.REVIDX_EXTSTORED)
322 322 raise error.Abort(msg)
323 323
324 324 opts[b'flagprocessors'][revlog.REVIDX_EXTSTORED] = lfsprocessor
325 325 break
326 326
327 327 return opts
328 328
329 329 @eh.extsetup
330 330 def _extsetup(ui):
331 331 wrapfilelog(filelog.filelog)
332 332
333 333 context.basefilectx.islfs = wrapper.filectxislfs
334 334
335 335 scmutil.fileprefetchhooks.add('lfs', wrapper._prefetchfiles)
336 336
337 337 # Make bundle choose changegroup3 instead of changegroup2. This affects
338 338 # "hg bundle" command. Note: it does not cover all bundle formats like
339 339 # "packed1". Using "packed1" with lfs will likely cause trouble.
340 340 exchange._bundlespeccontentopts["v2"]["cg.version"] = "03"
341 341
342 342 @eh.filesetpredicate('lfs()')
343 343 def lfsfileset(mctx, x):
344 344 """File that uses LFS storage."""
345 345 # i18n: "lfs" is a keyword
346 346 filesetlang.getargs(x, 0, 0, _("lfs takes no arguments"))
347 347 ctx = mctx.ctx
348 348 def lfsfilep(f):
349 349 return wrapper.pointerfromctx(ctx, f, removed=True) is not None
350 350 return mctx.predicate(lfsfilep, predrepr='<lfs>')
351 351
352 352 @eh.templatekeyword('lfs_files', requires={'ctx'})
353 353 def lfsfiles(context, mapping):
354 354 """List of strings. All files modified, added, or removed by this
355 355 changeset."""
356 356 ctx = context.resource(mapping, 'ctx')
357 357
358 358 pointers = wrapper.pointersfromctx(ctx, removed=True) # {path: pointer}
359 359 files = sorted(pointers.keys())
360 360
361 361 def pointer(v):
362 362 # In the file spec, version is first and the other keys are sorted.
363 363 sortkeyfunc = lambda x: (x[0] != 'version', x)
364 364 items = sorted(pointers[v].iteritems(), key=sortkeyfunc)
365 365 return util.sortdict(items)
366 366
367 367 makemap = lambda v: {
368 368 'file': v,
369 369 'lfsoid': pointers[v].oid() if pointers[v] else None,
370 370 'lfspointer': templateutil.hybriddict(pointer(v)),
371 371 }
372 372
373 373 # TODO: make the separator ', '?
374 374 f = templateutil._showcompatlist(context, mapping, 'lfs_file', files)
375 375 return templateutil.hybrid(f, files, makemap, pycompat.identity)
376 376
377 377 @eh.command('debuglfsupload',
378 378 [('r', 'rev', [], _('upload large files introduced by REV'))])
379 379 def debuglfsupload(ui, repo, **opts):
380 380 """upload lfs blobs added by the working copy parent or given revisions"""
381 381 revs = opts.get(r'rev', [])
382 382 pointers = wrapper.extractpointers(repo, scmutil.revrange(repo, revs))
383 383 wrapper.uploadblobs(repo, pointers)
@@ -1,300 +1,301 b''
1 1 # shallowrepo.py - shallow repository that uses remote filelogs
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
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
11 11 from mercurial.i18n import _
12 12 from mercurial.node import hex, nullid, nullrev
13 13 from mercurial import (
14 14 encoding,
15 15 error,
16 16 localrepo,
17 17 match,
18 18 scmutil,
19 19 sparse,
20 20 util,
21 21 )
22 22 from mercurial.utils import procutil
23 23 from . import (
24 24 connectionpool,
25 25 constants,
26 26 contentstore,
27 27 datapack,
28 28 fileserverclient,
29 29 historypack,
30 30 metadatastore,
31 31 remotefilectx,
32 32 remotefilelog,
33 33 shallowutil,
34 34 )
35 35
36 36 # These make*stores functions are global so that other extensions can replace
37 37 # them.
38 38 def makelocalstores(repo):
39 39 """In-repo stores, like .hg/store/data; can not be discarded."""
40 40 localpath = os.path.join(repo.svfs.vfs.base, 'data')
41 41 if not os.path.exists(localpath):
42 42 os.makedirs(localpath)
43 43
44 44 # Instantiate local data stores
45 45 localcontent = contentstore.remotefilelogcontentstore(
46 46 repo, localpath, repo.name, shared=False)
47 47 localmetadata = metadatastore.remotefilelogmetadatastore(
48 48 repo, localpath, repo.name, shared=False)
49 49 return localcontent, localmetadata
50 50
51 51 def makecachestores(repo):
52 52 """Typically machine-wide, cache of remote data; can be discarded."""
53 53 # Instantiate shared cache stores
54 54 cachepath = shallowutil.getcachepath(repo.ui)
55 55 cachecontent = contentstore.remotefilelogcontentstore(
56 56 repo, cachepath, repo.name, shared=True)
57 57 cachemetadata = metadatastore.remotefilelogmetadatastore(
58 58 repo, cachepath, repo.name, shared=True)
59 59
60 60 repo.sharedstore = cachecontent
61 61 repo.shareddatastores.append(cachecontent)
62 62 repo.sharedhistorystores.append(cachemetadata)
63 63
64 64 return cachecontent, cachemetadata
65 65
66 66 def makeremotestores(repo, cachecontent, cachemetadata):
67 67 """These stores fetch data from a remote server."""
68 68 # Instantiate remote stores
69 69 repo.fileservice = fileserverclient.fileserverclient(repo)
70 70 remotecontent = contentstore.remotecontentstore(
71 71 repo.ui, repo.fileservice, cachecontent)
72 72 remotemetadata = metadatastore.remotemetadatastore(
73 73 repo.ui, repo.fileservice, cachemetadata)
74 74 return remotecontent, remotemetadata
75 75
76 76 def makepackstores(repo):
77 77 """Packs are more efficient (to read from) cache stores."""
78 78 # Instantiate pack stores
79 79 packpath = shallowutil.getcachepackpath(repo,
80 80 constants.FILEPACK_CATEGORY)
81 81 packcontentstore = datapack.datapackstore(repo.ui, packpath)
82 82 packmetadatastore = historypack.historypackstore(repo.ui, packpath)
83 83
84 84 repo.shareddatastores.append(packcontentstore)
85 85 repo.sharedhistorystores.append(packmetadatastore)
86 86 shallowutil.reportpackmetrics(repo.ui, 'filestore', packcontentstore,
87 87 packmetadatastore)
88 88 return packcontentstore, packmetadatastore
89 89
90 90 def makeunionstores(repo):
91 91 """Union stores iterate the other stores and return the first result."""
92 92 repo.shareddatastores = []
93 93 repo.sharedhistorystores = []
94 94
95 95 packcontentstore, packmetadatastore = makepackstores(repo)
96 96 cachecontent, cachemetadata = makecachestores(repo)
97 97 localcontent, localmetadata = makelocalstores(repo)
98 98 remotecontent, remotemetadata = makeremotestores(repo, cachecontent,
99 99 cachemetadata)
100 100
101 101 # Instantiate union stores
102 102 repo.contentstore = contentstore.unioncontentstore(
103 103 packcontentstore, cachecontent,
104 104 localcontent, remotecontent, writestore=localcontent)
105 105 repo.metadatastore = metadatastore.unionmetadatastore(
106 106 packmetadatastore, cachemetadata, localmetadata, remotemetadata,
107 107 writestore=localmetadata)
108 108
109 109 fileservicedatawrite = cachecontent
110 110 fileservicehistorywrite = cachemetadata
111 111 repo.fileservice.setstore(repo.contentstore, repo.metadatastore,
112 112 fileservicedatawrite, fileservicehistorywrite)
113 113 shallowutil.reportpackmetrics(repo.ui, 'filestore',
114 114 packcontentstore, packmetadatastore)
115 115
116 116 def wraprepo(repo):
117 117 class shallowrepository(repo.__class__):
118 118 @util.propertycache
119 119 def name(self):
120 120 return self.ui.config('remotefilelog', 'reponame')
121 121
122 122 @util.propertycache
123 123 def fallbackpath(self):
124 124 path = repo.ui.config("remotefilelog", "fallbackpath",
125 125 repo.ui.config('paths', 'default'))
126 126 if not path:
127 127 raise error.Abort("no remotefilelog server "
128 128 "configured - is your .hg/hgrc trusted?")
129 129
130 130 return path
131 131
132 132 def maybesparsematch(self, *revs, **kwargs):
133 133 '''
134 134 A wrapper that allows the remotefilelog to invoke sparsematch() if
135 135 this is a sparse repository, or returns None if this is not a
136 136 sparse repository.
137 137 '''
138 138 if revs:
139 139 ret = sparse.matcher(repo, revs=revs)
140 140 else:
141 141 ret = sparse.matcher(repo)
142 142
143 143 if ret.always():
144 144 return None
145 145 return ret
146 146
147 147 def file(self, f):
148 148 if f[0] == '/':
149 149 f = f[1:]
150 150
151 151 if self.shallowmatch(f):
152 152 return remotefilelog.remotefilelog(self.svfs, f, self)
153 153 else:
154 154 return super(shallowrepository, self).file(f)
155 155
156 156 def filectx(self, path, *args, **kwargs):
157 157 if self.shallowmatch(path):
158 158 return remotefilectx.remotefilectx(self, path, *args, **kwargs)
159 159 else:
160 160 return super(shallowrepository, self).filectx(path, *args,
161 161 **kwargs)
162 162
163 163 @localrepo.unfilteredmethod
164 def commitctx(self, ctx, error=False):
164 def commitctx(self, ctx, error=False, origctx=None):
165 165 """Add a new revision to current repository.
166 166 Revision information is passed via the context argument.
167 167 """
168 168
169 169 # some contexts already have manifest nodes, they don't need any
170 170 # prefetching (for example if we're just editing a commit message
171 171 # we can reuse manifest
172 172 if not ctx.manifestnode():
173 173 # prefetch files that will likely be compared
174 174 m1 = ctx.p1().manifest()
175 175 files = []
176 176 for f in ctx.modified() + ctx.added():
177 177 fparent1 = m1.get(f, nullid)
178 178 if fparent1 != nullid:
179 179 files.append((f, hex(fparent1)))
180 180 self.fileservice.prefetch(files)
181 181 return super(shallowrepository, self).commitctx(ctx,
182 error=error)
182 error=error,
183 origctx=origctx)
183 184
184 185 def backgroundprefetch(self, revs, base=None, repack=False, pats=None,
185 186 opts=None):
186 187 """Runs prefetch in background with optional repack
187 188 """
188 189 cmd = [procutil.hgexecutable(), '-R', repo.origroot, 'prefetch']
189 190 if repack:
190 191 cmd.append('--repack')
191 192 if revs:
192 193 cmd += ['-r', revs]
193 194 # We know this command will find a binary, so don't block
194 195 # on it starting.
195 196 procutil.runbgcommand(cmd, encoding.environ, ensurestart=False)
196 197
197 198 def prefetch(self, revs, base=None, pats=None, opts=None):
198 199 """Prefetches all the necessary file revisions for the given revs
199 200 Optionally runs repack in background
200 201 """
201 202 with repo._lock(repo.svfs, 'prefetchlock', True, None, None,
202 203 _('prefetching in %s') % repo.origroot):
203 204 self._prefetch(revs, base, pats, opts)
204 205
205 206 def _prefetch(self, revs, base=None, pats=None, opts=None):
206 207 fallbackpath = self.fallbackpath
207 208 if fallbackpath:
208 209 # If we know a rev is on the server, we should fetch the server
209 210 # version of those files, since our local file versions might
210 211 # become obsolete if the local commits are stripped.
211 212 localrevs = repo.revs('outgoing(%s)', fallbackpath)
212 213 if base is not None and base != nullrev:
213 214 serverbase = list(repo.revs('first(reverse(::%s) - %ld)',
214 215 base, localrevs))
215 216 if serverbase:
216 217 base = serverbase[0]
217 218 else:
218 219 localrevs = repo
219 220
220 221 mfl = repo.manifestlog
221 222 mfrevlog = mfl.getstorage('')
222 223 if base is not None:
223 224 mfdict = mfl[repo[base].manifestnode()].read()
224 225 skip = set(mfdict.iteritems())
225 226 else:
226 227 skip = set()
227 228
228 229 # Copy the skip set to start large and avoid constant resizing,
229 230 # and since it's likely to be very similar to the prefetch set.
230 231 files = skip.copy()
231 232 serverfiles = skip.copy()
232 233 visited = set()
233 234 visited.add(nullrev)
234 235 revcount = len(revs)
235 236 progress = self.ui.makeprogress(_('prefetching'), total=revcount)
236 237 progress.update(0)
237 238 for rev in sorted(revs):
238 239 ctx = repo[rev]
239 240 if pats:
240 241 m = scmutil.match(ctx, pats, opts)
241 242 sparsematch = repo.maybesparsematch(rev)
242 243
243 244 mfnode = ctx.manifestnode()
244 245 mfrev = mfrevlog.rev(mfnode)
245 246
246 247 # Decompressing manifests is expensive.
247 248 # When possible, only read the deltas.
248 249 p1, p2 = mfrevlog.parentrevs(mfrev)
249 250 if p1 in visited and p2 in visited:
250 251 mfdict = mfl[mfnode].readfast()
251 252 else:
252 253 mfdict = mfl[mfnode].read()
253 254
254 255 diff = mfdict.iteritems()
255 256 if pats:
256 257 diff = (pf for pf in diff if m(pf[0]))
257 258 if sparsematch:
258 259 diff = (pf for pf in diff if sparsematch(pf[0]))
259 260 if rev not in localrevs:
260 261 serverfiles.update(diff)
261 262 else:
262 263 files.update(diff)
263 264
264 265 visited.add(mfrev)
265 266 progress.increment()
266 267
267 268 files.difference_update(skip)
268 269 serverfiles.difference_update(skip)
269 270 progress.complete()
270 271
271 272 # Fetch files known to be on the server
272 273 if serverfiles:
273 274 results = [(path, hex(fnode)) for (path, fnode) in serverfiles]
274 275 repo.fileservice.prefetch(results, force=True)
275 276
276 277 # Fetch files that may or may not be on the server
277 278 if files:
278 279 results = [(path, hex(fnode)) for (path, fnode) in files]
279 280 repo.fileservice.prefetch(results)
280 281
281 282 def close(self):
282 283 super(shallowrepository, self).close()
283 284 self.connectionpool.close()
284 285
285 286 repo.__class__ = shallowrepository
286 287
287 288 repo.shallowmatch = match.always()
288 289
289 290 makeunionstores(repo)
290 291
291 292 repo.includepattern = repo.ui.configlist("remotefilelog", "includepattern",
292 293 None)
293 294 repo.excludepattern = repo.ui.configlist("remotefilelog", "excludepattern",
294 295 None)
295 296 if not util.safehasattr(repo, 'connectionpool'):
296 297 repo.connectionpool = connectionpool.connectionpool(repo)
297 298
298 299 if repo.includepattern or repo.excludepattern:
299 300 repo.shallowmatch = match.match(repo.root, '', None,
300 301 repo.includepattern, repo.excludepattern)
@@ -1,1499 +1,1502 b''
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
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 from __future__ import absolute_import
9 9
10 10 import functools
11 11 import re
12 12
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 )
17 17
18 18 def loadconfigtable(ui, extname, configtable):
19 19 """update config item known to the ui with the extension ones"""
20 20 for section, items in sorted(configtable.items()):
21 21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 22 knownkeys = set(knownitems)
23 23 newkeys = set(items)
24 24 for key in sorted(knownkeys & newkeys):
25 25 msg = "extension '%s' overwrite config item '%s.%s'"
26 26 msg %= (extname, section, key)
27 27 ui.develwarn(msg, config='warn-config')
28 28
29 29 knownitems.update(items)
30 30
31 31 class configitem(object):
32 32 """represent a known config item
33 33
34 34 :section: the official config section where to find this item,
35 35 :name: the official name within the section,
36 36 :default: default value for this item,
37 37 :alias: optional list of tuples as alternatives,
38 38 :generic: this is a generic definition, match name using regular expression.
39 39 """
40 40
41 41 def __init__(self, section, name, default=None, alias=(),
42 42 generic=False, priority=0):
43 43 self.section = section
44 44 self.name = name
45 45 self.default = default
46 46 self.alias = list(alias)
47 47 self.generic = generic
48 48 self.priority = priority
49 49 self._re = None
50 50 if generic:
51 51 self._re = re.compile(self.name)
52 52
53 53 class itemregister(dict):
54 54 """A specialized dictionary that can handle wild-card selection"""
55 55
56 56 def __init__(self):
57 57 super(itemregister, self).__init__()
58 58 self._generics = set()
59 59
60 60 def update(self, other):
61 61 super(itemregister, self).update(other)
62 62 self._generics.update(other._generics)
63 63
64 64 def __setitem__(self, key, item):
65 65 super(itemregister, self).__setitem__(key, item)
66 66 if item.generic:
67 67 self._generics.add(item)
68 68
69 69 def get(self, key):
70 70 baseitem = super(itemregister, self).get(key)
71 71 if baseitem is not None and not baseitem.generic:
72 72 return baseitem
73 73
74 74 # search for a matching generic item
75 75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 76 for item in generics:
77 77 # we use 'match' instead of 'search' to make the matching simpler
78 78 # for people unfamiliar with regular expression. Having the match
79 79 # rooted to the start of the string will produce less surprising
80 80 # result for user writing simple regex for sub-attribute.
81 81 #
82 82 # For example using "color\..*" match produces an unsurprising
83 83 # result, while using search could suddenly match apparently
84 84 # unrelated configuration that happens to contains "color."
85 85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 86 # some match to avoid the need to prefix most pattern with "^".
87 87 # The "^" seems more error prone.
88 88 if item._re.match(key):
89 89 return item
90 90
91 91 return None
92 92
93 93 coreitems = {}
94 94
95 95 def _register(configtable, *args, **kwargs):
96 96 item = configitem(*args, **kwargs)
97 97 section = configtable.setdefault(item.section, itemregister())
98 98 if item.name in section:
99 99 msg = "duplicated config item registration for '%s.%s'"
100 100 raise error.ProgrammingError(msg % (item.section, item.name))
101 101 section[item.name] = item
102 102
103 103 # special value for case where the default is derived from other values
104 104 dynamicdefault = object()
105 105
106 106 # Registering actual config items
107 107
108 108 def getitemregister(configtable):
109 109 f = functools.partial(_register, configtable)
110 110 # export pseudo enum as configitem.*
111 111 f.dynamicdefault = dynamicdefault
112 112 return f
113 113
114 114 coreconfigitem = getitemregister(coreitems)
115 115
116 116 def _registerdiffopts(section, configprefix=''):
117 117 coreconfigitem(section, configprefix + 'nodates',
118 118 default=False,
119 119 )
120 120 coreconfigitem(section, configprefix + 'showfunc',
121 121 default=False,
122 122 )
123 123 coreconfigitem(section, configprefix + 'unified',
124 124 default=None,
125 125 )
126 126 coreconfigitem(section, configprefix + 'git',
127 127 default=False,
128 128 )
129 129 coreconfigitem(section, configprefix + 'ignorews',
130 130 default=False,
131 131 )
132 132 coreconfigitem(section, configprefix + 'ignorewsamount',
133 133 default=False,
134 134 )
135 135 coreconfigitem(section, configprefix + 'ignoreblanklines',
136 136 default=False,
137 137 )
138 138 coreconfigitem(section, configprefix + 'ignorewseol',
139 139 default=False,
140 140 )
141 141 coreconfigitem(section, configprefix + 'nobinary',
142 142 default=False,
143 143 )
144 144 coreconfigitem(section, configprefix + 'noprefix',
145 145 default=False,
146 146 )
147 147 coreconfigitem(section, configprefix + 'word-diff',
148 148 default=False,
149 149 )
150 150
151 151 coreconfigitem('alias', '.*',
152 152 default=dynamicdefault,
153 153 generic=True,
154 154 )
155 155 coreconfigitem('auth', 'cookiefile',
156 156 default=None,
157 157 )
158 158 _registerdiffopts(section='annotate')
159 159 # bookmarks.pushing: internal hack for discovery
160 160 coreconfigitem('bookmarks', 'pushing',
161 161 default=list,
162 162 )
163 163 # bundle.mainreporoot: internal hack for bundlerepo
164 164 coreconfigitem('bundle', 'mainreporoot',
165 165 default='',
166 166 )
167 167 coreconfigitem('censor', 'policy',
168 168 default='abort',
169 169 )
170 170 coreconfigitem('chgserver', 'idletimeout',
171 171 default=3600,
172 172 )
173 173 coreconfigitem('chgserver', 'skiphash',
174 174 default=False,
175 175 )
176 176 coreconfigitem('cmdserver', 'log',
177 177 default=None,
178 178 )
179 179 coreconfigitem('cmdserver', 'max-log-files',
180 180 default=7,
181 181 )
182 182 coreconfigitem('cmdserver', 'max-log-size',
183 183 default='1 MB',
184 184 )
185 185 coreconfigitem('cmdserver', 'max-repo-cache',
186 186 default=0,
187 187 )
188 188 coreconfigitem('cmdserver', 'message-encodings',
189 189 default=list,
190 190 )
191 191 coreconfigitem('cmdserver', 'track-log',
192 192 default=lambda: ['chgserver', 'cmdserver', 'repocache'],
193 193 )
194 194 coreconfigitem('color', '.*',
195 195 default=None,
196 196 generic=True,
197 197 )
198 198 coreconfigitem('color', 'mode',
199 199 default='auto',
200 200 )
201 201 coreconfigitem('color', 'pagermode',
202 202 default=dynamicdefault,
203 203 )
204 204 _registerdiffopts(section='commands', configprefix='commit.interactive.')
205 205 coreconfigitem('commands', 'commit.post-status',
206 206 default=False,
207 207 )
208 208 coreconfigitem('commands', 'grep.all-files',
209 209 default=False,
210 210 )
211 211 coreconfigitem('commands', 'resolve.confirm',
212 212 default=False,
213 213 )
214 214 coreconfigitem('commands', 'resolve.explicit-re-merge',
215 215 default=False,
216 216 )
217 217 coreconfigitem('commands', 'resolve.mark-check',
218 218 default='none',
219 219 )
220 220 _registerdiffopts(section='commands', configprefix='revert.interactive.')
221 221 coreconfigitem('commands', 'show.aliasprefix',
222 222 default=list,
223 223 )
224 224 coreconfigitem('commands', 'status.relative',
225 225 default=False,
226 226 )
227 227 coreconfigitem('commands', 'status.skipstates',
228 228 default=[],
229 229 )
230 230 coreconfigitem('commands', 'status.terse',
231 231 default='',
232 232 )
233 233 coreconfigitem('commands', 'status.verbose',
234 234 default=False,
235 235 )
236 236 coreconfigitem('commands', 'update.check',
237 237 default=None,
238 238 )
239 239 coreconfigitem('commands', 'update.requiredest',
240 240 default=False,
241 241 )
242 242 coreconfigitem('committemplate', '.*',
243 243 default=None,
244 244 generic=True,
245 245 )
246 246 coreconfigitem('convert', 'bzr.saverev',
247 247 default=True,
248 248 )
249 249 coreconfigitem('convert', 'cvsps.cache',
250 250 default=True,
251 251 )
252 252 coreconfigitem('convert', 'cvsps.fuzz',
253 253 default=60,
254 254 )
255 255 coreconfigitem('convert', 'cvsps.logencoding',
256 256 default=None,
257 257 )
258 258 coreconfigitem('convert', 'cvsps.mergefrom',
259 259 default=None,
260 260 )
261 261 coreconfigitem('convert', 'cvsps.mergeto',
262 262 default=None,
263 263 )
264 264 coreconfigitem('convert', 'git.committeractions',
265 265 default=lambda: ['messagedifferent'],
266 266 )
267 267 coreconfigitem('convert', 'git.extrakeys',
268 268 default=list,
269 269 )
270 270 coreconfigitem('convert', 'git.findcopiesharder',
271 271 default=False,
272 272 )
273 273 coreconfigitem('convert', 'git.remoteprefix',
274 274 default='remote',
275 275 )
276 276 coreconfigitem('convert', 'git.renamelimit',
277 277 default=400,
278 278 )
279 279 coreconfigitem('convert', 'git.saverev',
280 280 default=True,
281 281 )
282 282 coreconfigitem('convert', 'git.similarity',
283 283 default=50,
284 284 )
285 285 coreconfigitem('convert', 'git.skipsubmodules',
286 286 default=False,
287 287 )
288 288 coreconfigitem('convert', 'hg.clonebranches',
289 289 default=False,
290 290 )
291 291 coreconfigitem('convert', 'hg.ignoreerrors',
292 292 default=False,
293 293 )
294 coreconfigitem('convert', 'hg.preserve-hash',
295 default=False,
296 )
294 297 coreconfigitem('convert', 'hg.revs',
295 298 default=None,
296 299 )
297 300 coreconfigitem('convert', 'hg.saverev',
298 301 default=False,
299 302 )
300 303 coreconfigitem('convert', 'hg.sourcename',
301 304 default=None,
302 305 )
303 306 coreconfigitem('convert', 'hg.startrev',
304 307 default=None,
305 308 )
306 309 coreconfigitem('convert', 'hg.tagsbranch',
307 310 default='default',
308 311 )
309 312 coreconfigitem('convert', 'hg.usebranchnames',
310 313 default=True,
311 314 )
312 315 coreconfigitem('convert', 'ignoreancestorcheck',
313 316 default=False,
314 317 )
315 318 coreconfigitem('convert', 'localtimezone',
316 319 default=False,
317 320 )
318 321 coreconfigitem('convert', 'p4.encoding',
319 322 default=dynamicdefault,
320 323 )
321 324 coreconfigitem('convert', 'p4.startrev',
322 325 default=0,
323 326 )
324 327 coreconfigitem('convert', 'skiptags',
325 328 default=False,
326 329 )
327 330 coreconfigitem('convert', 'svn.debugsvnlog',
328 331 default=True,
329 332 )
330 333 coreconfigitem('convert', 'svn.trunk',
331 334 default=None,
332 335 )
333 336 coreconfigitem('convert', 'svn.tags',
334 337 default=None,
335 338 )
336 339 coreconfigitem('convert', 'svn.branches',
337 340 default=None,
338 341 )
339 342 coreconfigitem('convert', 'svn.startrev',
340 343 default=0,
341 344 )
342 345 coreconfigitem('debug', 'dirstate.delaywrite',
343 346 default=0,
344 347 )
345 348 coreconfigitem('defaults', '.*',
346 349 default=None,
347 350 generic=True,
348 351 )
349 352 coreconfigitem('devel', 'all-warnings',
350 353 default=False,
351 354 )
352 355 coreconfigitem('devel', 'bundle2.debug',
353 356 default=False,
354 357 )
355 358 coreconfigitem('devel', 'bundle.delta',
356 359 default='',
357 360 )
358 361 coreconfigitem('devel', 'cache-vfs',
359 362 default=None,
360 363 )
361 364 coreconfigitem('devel', 'check-locks',
362 365 default=False,
363 366 )
364 367 coreconfigitem('devel', 'check-relroot',
365 368 default=False,
366 369 )
367 370 coreconfigitem('devel', 'default-date',
368 371 default=None,
369 372 )
370 373 coreconfigitem('devel', 'deprec-warn',
371 374 default=False,
372 375 )
373 376 coreconfigitem('devel', 'disableloaddefaultcerts',
374 377 default=False,
375 378 )
376 379 coreconfigitem('devel', 'warn-empty-changegroup',
377 380 default=False,
378 381 )
379 382 coreconfigitem('devel', 'legacy.exchange',
380 383 default=list,
381 384 )
382 385 coreconfigitem('devel', 'servercafile',
383 386 default='',
384 387 )
385 388 coreconfigitem('devel', 'serverexactprotocol',
386 389 default='',
387 390 )
388 391 coreconfigitem('devel', 'serverrequirecert',
389 392 default=False,
390 393 )
391 394 coreconfigitem('devel', 'strip-obsmarkers',
392 395 default=True,
393 396 )
394 397 coreconfigitem('devel', 'warn-config',
395 398 default=None,
396 399 )
397 400 coreconfigitem('devel', 'warn-config-default',
398 401 default=None,
399 402 )
400 403 coreconfigitem('devel', 'user.obsmarker',
401 404 default=None,
402 405 )
403 406 coreconfigitem('devel', 'warn-config-unknown',
404 407 default=None,
405 408 )
406 409 coreconfigitem('devel', 'debug.copies',
407 410 default=False,
408 411 )
409 412 coreconfigitem('devel', 'debug.extensions',
410 413 default=False,
411 414 )
412 415 coreconfigitem('devel', 'debug.peer-request',
413 416 default=False,
414 417 )
415 418 _registerdiffopts(section='diff')
416 419 coreconfigitem('email', 'bcc',
417 420 default=None,
418 421 )
419 422 coreconfigitem('email', 'cc',
420 423 default=None,
421 424 )
422 425 coreconfigitem('email', 'charsets',
423 426 default=list,
424 427 )
425 428 coreconfigitem('email', 'from',
426 429 default=None,
427 430 )
428 431 coreconfigitem('email', 'method',
429 432 default='smtp',
430 433 )
431 434 coreconfigitem('email', 'reply-to',
432 435 default=None,
433 436 )
434 437 coreconfigitem('email', 'to',
435 438 default=None,
436 439 )
437 440 coreconfigitem('experimental', 'archivemetatemplate',
438 441 default=dynamicdefault,
439 442 )
440 443 coreconfigitem('experimental', 'auto-publish',
441 444 default='publish',
442 445 )
443 446 coreconfigitem('experimental', 'bundle-phases',
444 447 default=False,
445 448 )
446 449 coreconfigitem('experimental', 'bundle2-advertise',
447 450 default=True,
448 451 )
449 452 coreconfigitem('experimental', 'bundle2-output-capture',
450 453 default=False,
451 454 )
452 455 coreconfigitem('experimental', 'bundle2.pushback',
453 456 default=False,
454 457 )
455 458 coreconfigitem('experimental', 'bundle2lazylocking',
456 459 default=False,
457 460 )
458 461 coreconfigitem('experimental', 'bundlecomplevel',
459 462 default=None,
460 463 )
461 464 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
462 465 default=None,
463 466 )
464 467 coreconfigitem('experimental', 'bundlecomplevel.gzip',
465 468 default=None,
466 469 )
467 470 coreconfigitem('experimental', 'bundlecomplevel.none',
468 471 default=None,
469 472 )
470 473 coreconfigitem('experimental', 'bundlecomplevel.zstd',
471 474 default=None,
472 475 )
473 476 coreconfigitem('experimental', 'changegroup3',
474 477 default=False,
475 478 )
476 479 coreconfigitem('experimental', 'cleanup-as-archived',
477 480 default=False,
478 481 )
479 482 coreconfigitem('experimental', 'clientcompressionengines',
480 483 default=list,
481 484 )
482 485 coreconfigitem('experimental', 'copytrace',
483 486 default='on',
484 487 )
485 488 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
486 489 default=100,
487 490 )
488 491 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
489 492 default=100,
490 493 )
491 494 coreconfigitem('experimental', 'copies.read-from',
492 495 default="filelog-only",
493 496 )
494 497 coreconfigitem('experimental', 'copies.write-to',
495 498 default='filelog-only',
496 499 )
497 500 coreconfigitem('experimental', 'crecordtest',
498 501 default=None,
499 502 )
500 503 coreconfigitem('experimental', 'directaccess',
501 504 default=False,
502 505 )
503 506 coreconfigitem('experimental', 'directaccess.revnums',
504 507 default=False,
505 508 )
506 509 coreconfigitem('experimental', 'editortmpinhg',
507 510 default=False,
508 511 )
509 512 coreconfigitem('experimental', 'evolution',
510 513 default=list,
511 514 )
512 515 coreconfigitem('experimental', 'evolution.allowdivergence',
513 516 default=False,
514 517 alias=[('experimental', 'allowdivergence')]
515 518 )
516 519 coreconfigitem('experimental', 'evolution.allowunstable',
517 520 default=None,
518 521 )
519 522 coreconfigitem('experimental', 'evolution.createmarkers',
520 523 default=None,
521 524 )
522 525 coreconfigitem('experimental', 'evolution.effect-flags',
523 526 default=True,
524 527 alias=[('experimental', 'effect-flags')]
525 528 )
526 529 coreconfigitem('experimental', 'evolution.exchange',
527 530 default=None,
528 531 )
529 532 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
530 533 default=False,
531 534 )
532 535 coreconfigitem('experimental', 'log.topo',
533 536 default=False,
534 537 )
535 538 coreconfigitem('experimental', 'evolution.report-instabilities',
536 539 default=True,
537 540 )
538 541 coreconfigitem('experimental', 'evolution.track-operation',
539 542 default=True,
540 543 )
541 544 # repo-level config to exclude a revset visibility
542 545 #
543 546 # The target use case is to use `share` to expose different subset of the same
544 547 # repository, especially server side. See also `server.view`.
545 548 coreconfigitem('experimental', 'extra-filter-revs',
546 549 default=None,
547 550 )
548 551 coreconfigitem('experimental', 'maxdeltachainspan',
549 552 default=-1,
550 553 )
551 554 coreconfigitem('experimental', 'mergetempdirprefix',
552 555 default=None,
553 556 )
554 557 coreconfigitem('experimental', 'mmapindexthreshold',
555 558 default=None,
556 559 )
557 560 coreconfigitem('experimental', 'narrow',
558 561 default=False,
559 562 )
560 563 coreconfigitem('experimental', 'nonnormalparanoidcheck',
561 564 default=False,
562 565 )
563 566 coreconfigitem('experimental', 'exportableenviron',
564 567 default=list,
565 568 )
566 569 coreconfigitem('experimental', 'extendedheader.index',
567 570 default=None,
568 571 )
569 572 coreconfigitem('experimental', 'extendedheader.similarity',
570 573 default=False,
571 574 )
572 575 coreconfigitem('experimental', 'graphshorten',
573 576 default=False,
574 577 )
575 578 coreconfigitem('experimental', 'graphstyle.parent',
576 579 default=dynamicdefault,
577 580 )
578 581 coreconfigitem('experimental', 'graphstyle.missing',
579 582 default=dynamicdefault,
580 583 )
581 584 coreconfigitem('experimental', 'graphstyle.grandparent',
582 585 default=dynamicdefault,
583 586 )
584 587 coreconfigitem('experimental', 'hook-track-tags',
585 588 default=False,
586 589 )
587 590 coreconfigitem('experimental', 'httppeer.advertise-v2',
588 591 default=False,
589 592 )
590 593 coreconfigitem('experimental', 'httppeer.v2-encoder-order',
591 594 default=None,
592 595 )
593 596 coreconfigitem('experimental', 'httppostargs',
594 597 default=False,
595 598 )
596 599 coreconfigitem('experimental', 'mergedriver',
597 600 default=None,
598 601 )
599 602 coreconfigitem('experimental', 'nointerrupt', default=False)
600 603 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
601 604
602 605 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
603 606 default=False,
604 607 )
605 608 coreconfigitem('experimental', 'remotenames',
606 609 default=False,
607 610 )
608 611 coreconfigitem('experimental', 'removeemptydirs',
609 612 default=True,
610 613 )
611 614 coreconfigitem('experimental', 'revert.interactive.select-to-keep',
612 615 default=False,
613 616 )
614 617 coreconfigitem('experimental', 'revisions.prefixhexnode',
615 618 default=False,
616 619 )
617 620 coreconfigitem('experimental', 'revlogv2',
618 621 default=None,
619 622 )
620 623 coreconfigitem('experimental', 'revisions.disambiguatewithin',
621 624 default=None,
622 625 )
623 626 coreconfigitem('experimental', 'server.filesdata.recommended-batch-size',
624 627 default=50000,
625 628 )
626 629 coreconfigitem('experimental', 'server.manifestdata.recommended-batch-size',
627 630 default=100000,
628 631 )
629 632 coreconfigitem('experimental', 'server.stream-narrow-clones',
630 633 default=False,
631 634 )
632 635 coreconfigitem('experimental', 'single-head-per-branch',
633 636 default=False,
634 637 )
635 638 coreconfigitem('experimental', 'sshserver.support-v2',
636 639 default=False,
637 640 )
638 641 coreconfigitem('experimental', 'sparse-read',
639 642 default=False,
640 643 )
641 644 coreconfigitem('experimental', 'sparse-read.density-threshold',
642 645 default=0.50,
643 646 )
644 647 coreconfigitem('experimental', 'sparse-read.min-gap-size',
645 648 default='65K',
646 649 )
647 650 coreconfigitem('experimental', 'treemanifest',
648 651 default=False,
649 652 )
650 653 coreconfigitem('experimental', 'update.atomic-file',
651 654 default=False,
652 655 )
653 656 coreconfigitem('experimental', 'sshpeer.advertise-v2',
654 657 default=False,
655 658 )
656 659 coreconfigitem('experimental', 'web.apiserver',
657 660 default=False,
658 661 )
659 662 coreconfigitem('experimental', 'web.api.http-v2',
660 663 default=False,
661 664 )
662 665 coreconfigitem('experimental', 'web.api.debugreflect',
663 666 default=False,
664 667 )
665 668 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
666 669 default=False,
667 670 )
668 671 coreconfigitem('experimental', 'xdiff',
669 672 default=False,
670 673 )
671 674 coreconfigitem('extensions', '.*',
672 675 default=None,
673 676 generic=True,
674 677 )
675 678 coreconfigitem('extdata', '.*',
676 679 default=None,
677 680 generic=True,
678 681 )
679 682 coreconfigitem('format', 'bookmarks-in-store',
680 683 default=False,
681 684 )
682 685 coreconfigitem('format', 'chunkcachesize',
683 686 default=None,
684 687 )
685 688 coreconfigitem('format', 'dotencode',
686 689 default=True,
687 690 )
688 691 coreconfigitem('format', 'generaldelta',
689 692 default=False,
690 693 )
691 694 coreconfigitem('format', 'manifestcachesize',
692 695 default=None,
693 696 )
694 697 coreconfigitem('format', 'maxchainlen',
695 698 default=dynamicdefault,
696 699 )
697 700 coreconfigitem('format', 'obsstore-version',
698 701 default=None,
699 702 )
700 703 coreconfigitem('format', 'sparse-revlog',
701 704 default=True,
702 705 )
703 706 coreconfigitem('format', 'revlog-compression',
704 707 default='zlib',
705 708 alias=[('experimental', 'format.compression')]
706 709 )
707 710 coreconfigitem('format', 'usefncache',
708 711 default=True,
709 712 )
710 713 coreconfigitem('format', 'usegeneraldelta',
711 714 default=True,
712 715 )
713 716 coreconfigitem('format', 'usestore',
714 717 default=True,
715 718 )
716 719 coreconfigitem('format', 'internal-phase',
717 720 default=False,
718 721 )
719 722 coreconfigitem('fsmonitor', 'warn_when_unused',
720 723 default=True,
721 724 )
722 725 coreconfigitem('fsmonitor', 'warn_update_file_count',
723 726 default=50000,
724 727 )
725 728 coreconfigitem('help', br'hidden-command\..*',
726 729 default=False,
727 730 generic=True,
728 731 )
729 732 coreconfigitem('help', br'hidden-topic\..*',
730 733 default=False,
731 734 generic=True,
732 735 )
733 736 coreconfigitem('hooks', '.*',
734 737 default=dynamicdefault,
735 738 generic=True,
736 739 )
737 740 coreconfigitem('hgweb-paths', '.*',
738 741 default=list,
739 742 generic=True,
740 743 )
741 744 coreconfigitem('hostfingerprints', '.*',
742 745 default=list,
743 746 generic=True,
744 747 )
745 748 coreconfigitem('hostsecurity', 'ciphers',
746 749 default=None,
747 750 )
748 751 coreconfigitem('hostsecurity', 'disabletls10warning',
749 752 default=False,
750 753 )
751 754 coreconfigitem('hostsecurity', 'minimumprotocol',
752 755 default=dynamicdefault,
753 756 )
754 757 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
755 758 default=dynamicdefault,
756 759 generic=True,
757 760 )
758 761 coreconfigitem('hostsecurity', '.*:ciphers$',
759 762 default=dynamicdefault,
760 763 generic=True,
761 764 )
762 765 coreconfigitem('hostsecurity', '.*:fingerprints$',
763 766 default=list,
764 767 generic=True,
765 768 )
766 769 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
767 770 default=None,
768 771 generic=True,
769 772 )
770 773
771 774 coreconfigitem('http_proxy', 'always',
772 775 default=False,
773 776 )
774 777 coreconfigitem('http_proxy', 'host',
775 778 default=None,
776 779 )
777 780 coreconfigitem('http_proxy', 'no',
778 781 default=list,
779 782 )
780 783 coreconfigitem('http_proxy', 'passwd',
781 784 default=None,
782 785 )
783 786 coreconfigitem('http_proxy', 'user',
784 787 default=None,
785 788 )
786 789
787 790 coreconfigitem('http', 'timeout',
788 791 default=None,
789 792 )
790 793
791 794 coreconfigitem('logtoprocess', 'commandexception',
792 795 default=None,
793 796 )
794 797 coreconfigitem('logtoprocess', 'commandfinish',
795 798 default=None,
796 799 )
797 800 coreconfigitem('logtoprocess', 'command',
798 801 default=None,
799 802 )
800 803 coreconfigitem('logtoprocess', 'develwarn',
801 804 default=None,
802 805 )
803 806 coreconfigitem('logtoprocess', 'uiblocked',
804 807 default=None,
805 808 )
806 809 coreconfigitem('merge', 'checkunknown',
807 810 default='abort',
808 811 )
809 812 coreconfigitem('merge', 'checkignored',
810 813 default='abort',
811 814 )
812 815 coreconfigitem('experimental', 'merge.checkpathconflicts',
813 816 default=False,
814 817 )
815 818 coreconfigitem('merge', 'followcopies',
816 819 default=True,
817 820 )
818 821 coreconfigitem('merge', 'on-failure',
819 822 default='continue',
820 823 )
821 824 coreconfigitem('merge', 'preferancestor',
822 825 default=lambda: ['*'],
823 826 )
824 827 coreconfigitem('merge', 'strict-capability-check',
825 828 default=False,
826 829 )
827 830 coreconfigitem('merge-tools', '.*',
828 831 default=None,
829 832 generic=True,
830 833 )
831 834 coreconfigitem('merge-tools', br'.*\.args$',
832 835 default="$local $base $other",
833 836 generic=True,
834 837 priority=-1,
835 838 )
836 839 coreconfigitem('merge-tools', br'.*\.binary$',
837 840 default=False,
838 841 generic=True,
839 842 priority=-1,
840 843 )
841 844 coreconfigitem('merge-tools', br'.*\.check$',
842 845 default=list,
843 846 generic=True,
844 847 priority=-1,
845 848 )
846 849 coreconfigitem('merge-tools', br'.*\.checkchanged$',
847 850 default=False,
848 851 generic=True,
849 852 priority=-1,
850 853 )
851 854 coreconfigitem('merge-tools', br'.*\.executable$',
852 855 default=dynamicdefault,
853 856 generic=True,
854 857 priority=-1,
855 858 )
856 859 coreconfigitem('merge-tools', br'.*\.fixeol$',
857 860 default=False,
858 861 generic=True,
859 862 priority=-1,
860 863 )
861 864 coreconfigitem('merge-tools', br'.*\.gui$',
862 865 default=False,
863 866 generic=True,
864 867 priority=-1,
865 868 )
866 869 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
867 870 default='basic',
868 871 generic=True,
869 872 priority=-1,
870 873 )
871 874 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
872 875 default=dynamicdefault, # take from ui.mergemarkertemplate
873 876 generic=True,
874 877 priority=-1,
875 878 )
876 879 coreconfigitem('merge-tools', br'.*\.priority$',
877 880 default=0,
878 881 generic=True,
879 882 priority=-1,
880 883 )
881 884 coreconfigitem('merge-tools', br'.*\.premerge$',
882 885 default=dynamicdefault,
883 886 generic=True,
884 887 priority=-1,
885 888 )
886 889 coreconfigitem('merge-tools', br'.*\.symlink$',
887 890 default=False,
888 891 generic=True,
889 892 priority=-1,
890 893 )
891 894 coreconfigitem('pager', 'attend-.*',
892 895 default=dynamicdefault,
893 896 generic=True,
894 897 )
895 898 coreconfigitem('pager', 'ignore',
896 899 default=list,
897 900 )
898 901 coreconfigitem('pager', 'pager',
899 902 default=dynamicdefault,
900 903 )
901 904 coreconfigitem('patch', 'eol',
902 905 default='strict',
903 906 )
904 907 coreconfigitem('patch', 'fuzz',
905 908 default=2,
906 909 )
907 910 coreconfigitem('paths', 'default',
908 911 default=None,
909 912 )
910 913 coreconfigitem('paths', 'default-push',
911 914 default=None,
912 915 )
913 916 coreconfigitem('paths', '.*',
914 917 default=None,
915 918 generic=True,
916 919 )
917 920 coreconfigitem('phases', 'checksubrepos',
918 921 default='follow',
919 922 )
920 923 coreconfigitem('phases', 'new-commit',
921 924 default='draft',
922 925 )
923 926 coreconfigitem('phases', 'publish',
924 927 default=True,
925 928 )
926 929 coreconfigitem('profiling', 'enabled',
927 930 default=False,
928 931 )
929 932 coreconfigitem('profiling', 'format',
930 933 default='text',
931 934 )
932 935 coreconfigitem('profiling', 'freq',
933 936 default=1000,
934 937 )
935 938 coreconfigitem('profiling', 'limit',
936 939 default=30,
937 940 )
938 941 coreconfigitem('profiling', 'nested',
939 942 default=0,
940 943 )
941 944 coreconfigitem('profiling', 'output',
942 945 default=None,
943 946 )
944 947 coreconfigitem('profiling', 'showmax',
945 948 default=0.999,
946 949 )
947 950 coreconfigitem('profiling', 'showmin',
948 951 default=dynamicdefault,
949 952 )
950 953 coreconfigitem('profiling', 'showtime',
951 954 default=True,
952 955 )
953 956 coreconfigitem('profiling', 'sort',
954 957 default='inlinetime',
955 958 )
956 959 coreconfigitem('profiling', 'statformat',
957 960 default='hotpath',
958 961 )
959 962 coreconfigitem('profiling', 'time-track',
960 963 default=dynamicdefault,
961 964 )
962 965 coreconfigitem('profiling', 'type',
963 966 default='stat',
964 967 )
965 968 coreconfigitem('progress', 'assume-tty',
966 969 default=False,
967 970 )
968 971 coreconfigitem('progress', 'changedelay',
969 972 default=1,
970 973 )
971 974 coreconfigitem('progress', 'clear-complete',
972 975 default=True,
973 976 )
974 977 coreconfigitem('progress', 'debug',
975 978 default=False,
976 979 )
977 980 coreconfigitem('progress', 'delay',
978 981 default=3,
979 982 )
980 983 coreconfigitem('progress', 'disable',
981 984 default=False,
982 985 )
983 986 coreconfigitem('progress', 'estimateinterval',
984 987 default=60.0,
985 988 )
986 989 coreconfigitem('progress', 'format',
987 990 default=lambda: ['topic', 'bar', 'number', 'estimate'],
988 991 )
989 992 coreconfigitem('progress', 'refresh',
990 993 default=0.1,
991 994 )
992 995 coreconfigitem('progress', 'width',
993 996 default=dynamicdefault,
994 997 )
995 998 coreconfigitem('push', 'pushvars.server',
996 999 default=False,
997 1000 )
998 1001 coreconfigitem('rewrite', 'backup-bundle',
999 1002 default=True,
1000 1003 alias=[('ui', 'history-editing-backup')],
1001 1004 )
1002 1005 coreconfigitem('rewrite', 'update-timestamp',
1003 1006 default=False,
1004 1007 )
1005 1008 coreconfigitem('storage', 'new-repo-backend',
1006 1009 default='revlogv1',
1007 1010 )
1008 1011 coreconfigitem('storage', 'revlog.optimize-delta-parent-choice',
1009 1012 default=True,
1010 1013 alias=[('format', 'aggressivemergedeltas')],
1011 1014 )
1012 1015 coreconfigitem('storage', 'revlog.reuse-external-delta',
1013 1016 default=True,
1014 1017 )
1015 1018 coreconfigitem('storage', 'revlog.reuse-external-delta-parent',
1016 1019 default=None,
1017 1020 )
1018 1021 coreconfigitem('storage', 'revlog.zlib.level',
1019 1022 default=None,
1020 1023 )
1021 1024 coreconfigitem('storage', 'revlog.zstd.level',
1022 1025 default=None,
1023 1026 )
1024 1027 coreconfigitem('server', 'bookmarks-pushkey-compat',
1025 1028 default=True,
1026 1029 )
1027 1030 coreconfigitem('server', 'bundle1',
1028 1031 default=True,
1029 1032 )
1030 1033 coreconfigitem('server', 'bundle1gd',
1031 1034 default=None,
1032 1035 )
1033 1036 coreconfigitem('server', 'bundle1.pull',
1034 1037 default=None,
1035 1038 )
1036 1039 coreconfigitem('server', 'bundle1gd.pull',
1037 1040 default=None,
1038 1041 )
1039 1042 coreconfigitem('server', 'bundle1.push',
1040 1043 default=None,
1041 1044 )
1042 1045 coreconfigitem('server', 'bundle1gd.push',
1043 1046 default=None,
1044 1047 )
1045 1048 coreconfigitem('server', 'bundle2.stream',
1046 1049 default=True,
1047 1050 alias=[('experimental', 'bundle2.stream')]
1048 1051 )
1049 1052 coreconfigitem('server', 'compressionengines',
1050 1053 default=list,
1051 1054 )
1052 1055 coreconfigitem('server', 'concurrent-push-mode',
1053 1056 default='strict',
1054 1057 )
1055 1058 coreconfigitem('server', 'disablefullbundle',
1056 1059 default=False,
1057 1060 )
1058 1061 coreconfigitem('server', 'maxhttpheaderlen',
1059 1062 default=1024,
1060 1063 )
1061 1064 coreconfigitem('server', 'pullbundle',
1062 1065 default=False,
1063 1066 )
1064 1067 coreconfigitem('server', 'preferuncompressed',
1065 1068 default=False,
1066 1069 )
1067 1070 coreconfigitem('server', 'streamunbundle',
1068 1071 default=False,
1069 1072 )
1070 1073 coreconfigitem('server', 'uncompressed',
1071 1074 default=True,
1072 1075 )
1073 1076 coreconfigitem('server', 'uncompressedallowsecret',
1074 1077 default=False,
1075 1078 )
1076 1079 coreconfigitem('server', 'view',
1077 1080 default='served',
1078 1081 )
1079 1082 coreconfigitem('server', 'validate',
1080 1083 default=False,
1081 1084 )
1082 1085 coreconfigitem('server', 'zliblevel',
1083 1086 default=-1,
1084 1087 )
1085 1088 coreconfigitem('server', 'zstdlevel',
1086 1089 default=3,
1087 1090 )
1088 1091 coreconfigitem('share', 'pool',
1089 1092 default=None,
1090 1093 )
1091 1094 coreconfigitem('share', 'poolnaming',
1092 1095 default='identity',
1093 1096 )
1094 1097 coreconfigitem('shelve','maxbackups',
1095 1098 default=10,
1096 1099 )
1097 1100 coreconfigitem('smtp', 'host',
1098 1101 default=None,
1099 1102 )
1100 1103 coreconfigitem('smtp', 'local_hostname',
1101 1104 default=None,
1102 1105 )
1103 1106 coreconfigitem('smtp', 'password',
1104 1107 default=None,
1105 1108 )
1106 1109 coreconfigitem('smtp', 'port',
1107 1110 default=dynamicdefault,
1108 1111 )
1109 1112 coreconfigitem('smtp', 'tls',
1110 1113 default='none',
1111 1114 )
1112 1115 coreconfigitem('smtp', 'username',
1113 1116 default=None,
1114 1117 )
1115 1118 coreconfigitem('sparse', 'missingwarning',
1116 1119 default=True,
1117 1120 )
1118 1121 coreconfigitem('subrepos', 'allowed',
1119 1122 default=dynamicdefault, # to make backporting simpler
1120 1123 )
1121 1124 coreconfigitem('subrepos', 'hg:allowed',
1122 1125 default=dynamicdefault,
1123 1126 )
1124 1127 coreconfigitem('subrepos', 'git:allowed',
1125 1128 default=dynamicdefault,
1126 1129 )
1127 1130 coreconfigitem('subrepos', 'svn:allowed',
1128 1131 default=dynamicdefault,
1129 1132 )
1130 1133 coreconfigitem('templates', '.*',
1131 1134 default=None,
1132 1135 generic=True,
1133 1136 )
1134 1137 coreconfigitem('templateconfig', '.*',
1135 1138 default=dynamicdefault,
1136 1139 generic=True,
1137 1140 )
1138 1141 coreconfigitem('trusted', 'groups',
1139 1142 default=list,
1140 1143 )
1141 1144 coreconfigitem('trusted', 'users',
1142 1145 default=list,
1143 1146 )
1144 1147 coreconfigitem('ui', '_usedassubrepo',
1145 1148 default=False,
1146 1149 )
1147 1150 coreconfigitem('ui', 'allowemptycommit',
1148 1151 default=False,
1149 1152 )
1150 1153 coreconfigitem('ui', 'archivemeta',
1151 1154 default=True,
1152 1155 )
1153 1156 coreconfigitem('ui', 'askusername',
1154 1157 default=False,
1155 1158 )
1156 1159 coreconfigitem('ui', 'clonebundlefallback',
1157 1160 default=False,
1158 1161 )
1159 1162 coreconfigitem('ui', 'clonebundleprefers',
1160 1163 default=list,
1161 1164 )
1162 1165 coreconfigitem('ui', 'clonebundles',
1163 1166 default=True,
1164 1167 )
1165 1168 coreconfigitem('ui', 'color',
1166 1169 default='auto',
1167 1170 )
1168 1171 coreconfigitem('ui', 'commitsubrepos',
1169 1172 default=False,
1170 1173 )
1171 1174 coreconfigitem('ui', 'debug',
1172 1175 default=False,
1173 1176 )
1174 1177 coreconfigitem('ui', 'debugger',
1175 1178 default=None,
1176 1179 )
1177 1180 coreconfigitem('ui', 'editor',
1178 1181 default=dynamicdefault,
1179 1182 )
1180 1183 coreconfigitem('ui', 'fallbackencoding',
1181 1184 default=None,
1182 1185 )
1183 1186 coreconfigitem('ui', 'forcecwd',
1184 1187 default=None,
1185 1188 )
1186 1189 coreconfigitem('ui', 'forcemerge',
1187 1190 default=None,
1188 1191 )
1189 1192 coreconfigitem('ui', 'formatdebug',
1190 1193 default=False,
1191 1194 )
1192 1195 coreconfigitem('ui', 'formatjson',
1193 1196 default=False,
1194 1197 )
1195 1198 coreconfigitem('ui', 'formatted',
1196 1199 default=None,
1197 1200 )
1198 1201 coreconfigitem('ui', 'graphnodetemplate',
1199 1202 default=None,
1200 1203 )
1201 1204 coreconfigitem('ui', 'interactive',
1202 1205 default=None,
1203 1206 )
1204 1207 coreconfigitem('ui', 'interface',
1205 1208 default=None,
1206 1209 )
1207 1210 coreconfigitem('ui', 'interface.chunkselector',
1208 1211 default=None,
1209 1212 )
1210 1213 coreconfigitem('ui', 'large-file-limit',
1211 1214 default=10000000,
1212 1215 )
1213 1216 coreconfigitem('ui', 'logblockedtimes',
1214 1217 default=False,
1215 1218 )
1216 1219 coreconfigitem('ui', 'logtemplate',
1217 1220 default=None,
1218 1221 )
1219 1222 coreconfigitem('ui', 'merge',
1220 1223 default=None,
1221 1224 )
1222 1225 coreconfigitem('ui', 'mergemarkers',
1223 1226 default='basic',
1224 1227 )
1225 1228 coreconfigitem('ui', 'mergemarkertemplate',
1226 1229 default=('{node|short} '
1227 1230 '{ifeq(tags, "tip", "", '
1228 1231 'ifeq(tags, "", "", "{tags} "))}'
1229 1232 '{if(bookmarks, "{bookmarks} ")}'
1230 1233 '{ifeq(branch, "default", "", "{branch} ")}'
1231 1234 '- {author|user}: {desc|firstline}')
1232 1235 )
1233 1236 coreconfigitem('ui', 'message-output',
1234 1237 default='stdio',
1235 1238 )
1236 1239 coreconfigitem('ui', 'nontty',
1237 1240 default=False,
1238 1241 )
1239 1242 coreconfigitem('ui', 'origbackuppath',
1240 1243 default=None,
1241 1244 )
1242 1245 coreconfigitem('ui', 'paginate',
1243 1246 default=True,
1244 1247 )
1245 1248 coreconfigitem('ui', 'patch',
1246 1249 default=None,
1247 1250 )
1248 1251 coreconfigitem('ui', 'pre-merge-tool-output-template',
1249 1252 default=None,
1250 1253 )
1251 1254 coreconfigitem('ui', 'portablefilenames',
1252 1255 default='warn',
1253 1256 )
1254 1257 coreconfigitem('ui', 'promptecho',
1255 1258 default=False,
1256 1259 )
1257 1260 coreconfigitem('ui', 'quiet',
1258 1261 default=False,
1259 1262 )
1260 1263 coreconfigitem('ui', 'quietbookmarkmove',
1261 1264 default=False,
1262 1265 )
1263 1266 coreconfigitem('ui', 'relative-paths',
1264 1267 default='legacy',
1265 1268 )
1266 1269 coreconfigitem('ui', 'remotecmd',
1267 1270 default='hg',
1268 1271 )
1269 1272 coreconfigitem('ui', 'report_untrusted',
1270 1273 default=True,
1271 1274 )
1272 1275 coreconfigitem('ui', 'rollback',
1273 1276 default=True,
1274 1277 )
1275 1278 coreconfigitem('ui', 'signal-safe-lock',
1276 1279 default=True,
1277 1280 )
1278 1281 coreconfigitem('ui', 'slash',
1279 1282 default=False,
1280 1283 )
1281 1284 coreconfigitem('ui', 'ssh',
1282 1285 default='ssh',
1283 1286 )
1284 1287 coreconfigitem('ui', 'ssherrorhint',
1285 1288 default=None,
1286 1289 )
1287 1290 coreconfigitem('ui', 'statuscopies',
1288 1291 default=False,
1289 1292 )
1290 1293 coreconfigitem('ui', 'strict',
1291 1294 default=False,
1292 1295 )
1293 1296 coreconfigitem('ui', 'style',
1294 1297 default='',
1295 1298 )
1296 1299 coreconfigitem('ui', 'supportcontact',
1297 1300 default=None,
1298 1301 )
1299 1302 coreconfigitem('ui', 'textwidth',
1300 1303 default=78,
1301 1304 )
1302 1305 coreconfigitem('ui', 'timeout',
1303 1306 default='600',
1304 1307 )
1305 1308 coreconfigitem('ui', 'timeout.warn',
1306 1309 default=0,
1307 1310 )
1308 1311 coreconfigitem('ui', 'traceback',
1309 1312 default=False,
1310 1313 )
1311 1314 coreconfigitem('ui', 'tweakdefaults',
1312 1315 default=False,
1313 1316 )
1314 1317 coreconfigitem('ui', 'username',
1315 1318 alias=[('ui', 'user')]
1316 1319 )
1317 1320 coreconfigitem('ui', 'verbose',
1318 1321 default=False,
1319 1322 )
1320 1323 coreconfigitem('verify', 'skipflags',
1321 1324 default=None,
1322 1325 )
1323 1326 coreconfigitem('web', 'allowbz2',
1324 1327 default=False,
1325 1328 )
1326 1329 coreconfigitem('web', 'allowgz',
1327 1330 default=False,
1328 1331 )
1329 1332 coreconfigitem('web', 'allow-pull',
1330 1333 alias=[('web', 'allowpull')],
1331 1334 default=True,
1332 1335 )
1333 1336 coreconfigitem('web', 'allow-push',
1334 1337 alias=[('web', 'allow_push')],
1335 1338 default=list,
1336 1339 )
1337 1340 coreconfigitem('web', 'allowzip',
1338 1341 default=False,
1339 1342 )
1340 1343 coreconfigitem('web', 'archivesubrepos',
1341 1344 default=False,
1342 1345 )
1343 1346 coreconfigitem('web', 'cache',
1344 1347 default=True,
1345 1348 )
1346 1349 coreconfigitem('web', 'comparisoncontext',
1347 1350 default=5,
1348 1351 )
1349 1352 coreconfigitem('web', 'contact',
1350 1353 default=None,
1351 1354 )
1352 1355 coreconfigitem('web', 'deny_push',
1353 1356 default=list,
1354 1357 )
1355 1358 coreconfigitem('web', 'guessmime',
1356 1359 default=False,
1357 1360 )
1358 1361 coreconfigitem('web', 'hidden',
1359 1362 default=False,
1360 1363 )
1361 1364 coreconfigitem('web', 'labels',
1362 1365 default=list,
1363 1366 )
1364 1367 coreconfigitem('web', 'logoimg',
1365 1368 default='hglogo.png',
1366 1369 )
1367 1370 coreconfigitem('web', 'logourl',
1368 1371 default='https://mercurial-scm.org/',
1369 1372 )
1370 1373 coreconfigitem('web', 'accesslog',
1371 1374 default='-',
1372 1375 )
1373 1376 coreconfigitem('web', 'address',
1374 1377 default='',
1375 1378 )
1376 1379 coreconfigitem('web', 'allow-archive',
1377 1380 alias=[('web', 'allow_archive')],
1378 1381 default=list,
1379 1382 )
1380 1383 coreconfigitem('web', 'allow_read',
1381 1384 default=list,
1382 1385 )
1383 1386 coreconfigitem('web', 'baseurl',
1384 1387 default=None,
1385 1388 )
1386 1389 coreconfigitem('web', 'cacerts',
1387 1390 default=None,
1388 1391 )
1389 1392 coreconfigitem('web', 'certificate',
1390 1393 default=None,
1391 1394 )
1392 1395 coreconfigitem('web', 'collapse',
1393 1396 default=False,
1394 1397 )
1395 1398 coreconfigitem('web', 'csp',
1396 1399 default=None,
1397 1400 )
1398 1401 coreconfigitem('web', 'deny_read',
1399 1402 default=list,
1400 1403 )
1401 1404 coreconfigitem('web', 'descend',
1402 1405 default=True,
1403 1406 )
1404 1407 coreconfigitem('web', 'description',
1405 1408 default="",
1406 1409 )
1407 1410 coreconfigitem('web', 'encoding',
1408 1411 default=lambda: encoding.encoding,
1409 1412 )
1410 1413 coreconfigitem('web', 'errorlog',
1411 1414 default='-',
1412 1415 )
1413 1416 coreconfigitem('web', 'ipv6',
1414 1417 default=False,
1415 1418 )
1416 1419 coreconfigitem('web', 'maxchanges',
1417 1420 default=10,
1418 1421 )
1419 1422 coreconfigitem('web', 'maxfiles',
1420 1423 default=10,
1421 1424 )
1422 1425 coreconfigitem('web', 'maxshortchanges',
1423 1426 default=60,
1424 1427 )
1425 1428 coreconfigitem('web', 'motd',
1426 1429 default='',
1427 1430 )
1428 1431 coreconfigitem('web', 'name',
1429 1432 default=dynamicdefault,
1430 1433 )
1431 1434 coreconfigitem('web', 'port',
1432 1435 default=8000,
1433 1436 )
1434 1437 coreconfigitem('web', 'prefix',
1435 1438 default='',
1436 1439 )
1437 1440 coreconfigitem('web', 'push_ssl',
1438 1441 default=True,
1439 1442 )
1440 1443 coreconfigitem('web', 'refreshinterval',
1441 1444 default=20,
1442 1445 )
1443 1446 coreconfigitem('web', 'server-header',
1444 1447 default=None,
1445 1448 )
1446 1449 coreconfigitem('web', 'static',
1447 1450 default=None,
1448 1451 )
1449 1452 coreconfigitem('web', 'staticurl',
1450 1453 default=None,
1451 1454 )
1452 1455 coreconfigitem('web', 'stripes',
1453 1456 default=1,
1454 1457 )
1455 1458 coreconfigitem('web', 'style',
1456 1459 default='paper',
1457 1460 )
1458 1461 coreconfigitem('web', 'templates',
1459 1462 default=None,
1460 1463 )
1461 1464 coreconfigitem('web', 'view',
1462 1465 default='served',
1463 1466 )
1464 1467 coreconfigitem('worker', 'backgroundclose',
1465 1468 default=dynamicdefault,
1466 1469 )
1467 1470 # Windows defaults to a limit of 512 open files. A buffer of 128
1468 1471 # should give us enough headway.
1469 1472 coreconfigitem('worker', 'backgroundclosemaxqueue',
1470 1473 default=384,
1471 1474 )
1472 1475 coreconfigitem('worker', 'backgroundcloseminfilecount',
1473 1476 default=2048,
1474 1477 )
1475 1478 coreconfigitem('worker', 'backgroundclosethreadcount',
1476 1479 default=4,
1477 1480 )
1478 1481 coreconfigitem('worker', 'enabled',
1479 1482 default=True,
1480 1483 )
1481 1484 coreconfigitem('worker', 'numcpus',
1482 1485 default=None,
1483 1486 )
1484 1487
1485 1488 # Rebase related configuration moved to core because other extension are doing
1486 1489 # strange things. For example, shelve import the extensions to reuse some bit
1487 1490 # without formally loading it.
1488 1491 coreconfigitem('commands', 'rebase.requiredest',
1489 1492 default=False,
1490 1493 )
1491 1494 coreconfigitem('experimental', 'rebaseskipobsolete',
1492 1495 default=True,
1493 1496 )
1494 1497 coreconfigitem('rebase', 'singletransaction',
1495 1498 default=False,
1496 1499 )
1497 1500 coreconfigitem('rebase', 'experimental.inmemory',
1498 1501 default=False,
1499 1502 )
@@ -1,3197 +1,3206 b''
1 1 # localrepo.py - read/write repository class for mercurial
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 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import hashlib
12 12 import os
13 13 import random
14 14 import sys
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 bin,
21 21 hex,
22 22 nullid,
23 23 nullrev,
24 24 short,
25 25 )
26 26 from . import (
27 27 bookmarks,
28 28 branchmap,
29 29 bundle2,
30 30 changegroup,
31 31 changelog,
32 32 color,
33 33 context,
34 34 dirstate,
35 35 dirstateguard,
36 36 discovery,
37 37 encoding,
38 38 error,
39 39 exchange,
40 40 extensions,
41 41 filelog,
42 42 hook,
43 43 lock as lockmod,
44 44 manifest,
45 45 match as matchmod,
46 46 merge as mergemod,
47 47 mergeutil,
48 48 namespaces,
49 49 narrowspec,
50 50 obsolete,
51 51 pathutil,
52 52 phases,
53 53 pushkey,
54 54 pycompat,
55 55 repository,
56 56 repoview,
57 57 revset,
58 58 revsetlang,
59 59 scmutil,
60 60 sparse,
61 61 store as storemod,
62 62 subrepoutil,
63 63 tags as tagsmod,
64 64 transaction,
65 65 txnutil,
66 66 util,
67 67 vfs as vfsmod,
68 68 )
69 69 from .utils import (
70 70 interfaceutil,
71 71 procutil,
72 72 stringutil,
73 73 )
74 74
75 75 from .revlogutils import (
76 76 constants as revlogconst,
77 77 )
78 78
79 79 release = lockmod.release
80 80 urlerr = util.urlerr
81 81 urlreq = util.urlreq
82 82
83 83 # set of (path, vfs-location) tuples. vfs-location is:
84 84 # - 'plain for vfs relative paths
85 85 # - '' for svfs relative paths
86 86 _cachedfiles = set()
87 87
88 88 class _basefilecache(scmutil.filecache):
89 89 """All filecache usage on repo are done for logic that should be unfiltered
90 90 """
91 91 def __get__(self, repo, type=None):
92 92 if repo is None:
93 93 return self
94 94 # proxy to unfiltered __dict__ since filtered repo has no entry
95 95 unfi = repo.unfiltered()
96 96 try:
97 97 return unfi.__dict__[self.sname]
98 98 except KeyError:
99 99 pass
100 100 return super(_basefilecache, self).__get__(unfi, type)
101 101
102 102 def set(self, repo, value):
103 103 return super(_basefilecache, self).set(repo.unfiltered(), value)
104 104
105 105 class repofilecache(_basefilecache):
106 106 """filecache for files in .hg but outside of .hg/store"""
107 107 def __init__(self, *paths):
108 108 super(repofilecache, self).__init__(*paths)
109 109 for path in paths:
110 110 _cachedfiles.add((path, 'plain'))
111 111
112 112 def join(self, obj, fname):
113 113 return obj.vfs.join(fname)
114 114
115 115 class storecache(_basefilecache):
116 116 """filecache for files in the store"""
117 117 def __init__(self, *paths):
118 118 super(storecache, self).__init__(*paths)
119 119 for path in paths:
120 120 _cachedfiles.add((path, ''))
121 121
122 122 def join(self, obj, fname):
123 123 return obj.sjoin(fname)
124 124
125 125 class mixedrepostorecache(_basefilecache):
126 126 """filecache for a mix files in .hg/store and outside"""
127 127 def __init__(self, *pathsandlocations):
128 128 # scmutil.filecache only uses the path for passing back into our
129 129 # join(), so we can safely pass a list of paths and locations
130 130 super(mixedrepostorecache, self).__init__(*pathsandlocations)
131 131 _cachedfiles.update(pathsandlocations)
132 132
133 133 def join(self, obj, fnameandlocation):
134 134 fname, location = fnameandlocation
135 135 if location == 'plain':
136 136 return obj.vfs.join(fname)
137 137 else:
138 138 if location != '':
139 139 raise error.ProgrammingError('unexpected location: %s' %
140 140 location)
141 141 return obj.sjoin(fname)
142 142
143 143 def isfilecached(repo, name):
144 144 """check if a repo has already cached "name" filecache-ed property
145 145
146 146 This returns (cachedobj-or-None, iscached) tuple.
147 147 """
148 148 cacheentry = repo.unfiltered()._filecache.get(name, None)
149 149 if not cacheentry:
150 150 return None, False
151 151 return cacheentry.obj, True
152 152
153 153 class unfilteredpropertycache(util.propertycache):
154 154 """propertycache that apply to unfiltered repo only"""
155 155
156 156 def __get__(self, repo, type=None):
157 157 unfi = repo.unfiltered()
158 158 if unfi is repo:
159 159 return super(unfilteredpropertycache, self).__get__(unfi)
160 160 return getattr(unfi, self.name)
161 161
162 162 class filteredpropertycache(util.propertycache):
163 163 """propertycache that must take filtering in account"""
164 164
165 165 def cachevalue(self, obj, value):
166 166 object.__setattr__(obj, self.name, value)
167 167
168 168
169 169 def hasunfilteredcache(repo, name):
170 170 """check if a repo has an unfilteredpropertycache value for <name>"""
171 171 return name in vars(repo.unfiltered())
172 172
173 173 def unfilteredmethod(orig):
174 174 """decorate method that always need to be run on unfiltered version"""
175 175 def wrapper(repo, *args, **kwargs):
176 176 return orig(repo.unfiltered(), *args, **kwargs)
177 177 return wrapper
178 178
179 179 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
180 180 'unbundle'}
181 181 legacycaps = moderncaps.union({'changegroupsubset'})
182 182
183 183 @interfaceutil.implementer(repository.ipeercommandexecutor)
184 184 class localcommandexecutor(object):
185 185 def __init__(self, peer):
186 186 self._peer = peer
187 187 self._sent = False
188 188 self._closed = False
189 189
190 190 def __enter__(self):
191 191 return self
192 192
193 193 def __exit__(self, exctype, excvalue, exctb):
194 194 self.close()
195 195
196 196 def callcommand(self, command, args):
197 197 if self._sent:
198 198 raise error.ProgrammingError('callcommand() cannot be used after '
199 199 'sendcommands()')
200 200
201 201 if self._closed:
202 202 raise error.ProgrammingError('callcommand() cannot be used after '
203 203 'close()')
204 204
205 205 # We don't need to support anything fancy. Just call the named
206 206 # method on the peer and return a resolved future.
207 207 fn = getattr(self._peer, pycompat.sysstr(command))
208 208
209 209 f = pycompat.futures.Future()
210 210
211 211 try:
212 212 result = fn(**pycompat.strkwargs(args))
213 213 except Exception:
214 214 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
215 215 else:
216 216 f.set_result(result)
217 217
218 218 return f
219 219
220 220 def sendcommands(self):
221 221 self._sent = True
222 222
223 223 def close(self):
224 224 self._closed = True
225 225
226 226 @interfaceutil.implementer(repository.ipeercommands)
227 227 class localpeer(repository.peer):
228 228 '''peer for a local repo; reflects only the most recent API'''
229 229
230 230 def __init__(self, repo, caps=None):
231 231 super(localpeer, self).__init__()
232 232
233 233 if caps is None:
234 234 caps = moderncaps.copy()
235 235 self._repo = repo.filtered('served')
236 236 self.ui = repo.ui
237 237 self._caps = repo._restrictcapabilities(caps)
238 238
239 239 # Begin of _basepeer interface.
240 240
241 241 def url(self):
242 242 return self._repo.url()
243 243
244 244 def local(self):
245 245 return self._repo
246 246
247 247 def peer(self):
248 248 return self
249 249
250 250 def canpush(self):
251 251 return True
252 252
253 253 def close(self):
254 254 self._repo.close()
255 255
256 256 # End of _basepeer interface.
257 257
258 258 # Begin of _basewirecommands interface.
259 259
260 260 def branchmap(self):
261 261 return self._repo.branchmap()
262 262
263 263 def capabilities(self):
264 264 return self._caps
265 265
266 266 def clonebundles(self):
267 267 return self._repo.tryread('clonebundles.manifest')
268 268
269 269 def debugwireargs(self, one, two, three=None, four=None, five=None):
270 270 """Used to test argument passing over the wire"""
271 271 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
272 272 pycompat.bytestr(four),
273 273 pycompat.bytestr(five))
274 274
275 275 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
276 276 **kwargs):
277 277 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
278 278 common=common, bundlecaps=bundlecaps,
279 279 **kwargs)[1]
280 280 cb = util.chunkbuffer(chunks)
281 281
282 282 if exchange.bundle2requested(bundlecaps):
283 283 # When requesting a bundle2, getbundle returns a stream to make the
284 284 # wire level function happier. We need to build a proper object
285 285 # from it in local peer.
286 286 return bundle2.getunbundler(self.ui, cb)
287 287 else:
288 288 return changegroup.getunbundler('01', cb, None)
289 289
290 290 def heads(self):
291 291 return self._repo.heads()
292 292
293 293 def known(self, nodes):
294 294 return self._repo.known(nodes)
295 295
296 296 def listkeys(self, namespace):
297 297 return self._repo.listkeys(namespace)
298 298
299 299 def lookup(self, key):
300 300 return self._repo.lookup(key)
301 301
302 302 def pushkey(self, namespace, key, old, new):
303 303 return self._repo.pushkey(namespace, key, old, new)
304 304
305 305 def stream_out(self):
306 306 raise error.Abort(_('cannot perform stream clone against local '
307 307 'peer'))
308 308
309 309 def unbundle(self, bundle, heads, url):
310 310 """apply a bundle on a repo
311 311
312 312 This function handles the repo locking itself."""
313 313 try:
314 314 try:
315 315 bundle = exchange.readbundle(self.ui, bundle, None)
316 316 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
317 317 if util.safehasattr(ret, 'getchunks'):
318 318 # This is a bundle20 object, turn it into an unbundler.
319 319 # This little dance should be dropped eventually when the
320 320 # API is finally improved.
321 321 stream = util.chunkbuffer(ret.getchunks())
322 322 ret = bundle2.getunbundler(self.ui, stream)
323 323 return ret
324 324 except Exception as exc:
325 325 # If the exception contains output salvaged from a bundle2
326 326 # reply, we need to make sure it is printed before continuing
327 327 # to fail. So we build a bundle2 with such output and consume
328 328 # it directly.
329 329 #
330 330 # This is not very elegant but allows a "simple" solution for
331 331 # issue4594
332 332 output = getattr(exc, '_bundle2salvagedoutput', ())
333 333 if output:
334 334 bundler = bundle2.bundle20(self._repo.ui)
335 335 for out in output:
336 336 bundler.addpart(out)
337 337 stream = util.chunkbuffer(bundler.getchunks())
338 338 b = bundle2.getunbundler(self.ui, stream)
339 339 bundle2.processbundle(self._repo, b)
340 340 raise
341 341 except error.PushRaced as exc:
342 342 raise error.ResponseError(_('push failed:'),
343 343 stringutil.forcebytestr(exc))
344 344
345 345 # End of _basewirecommands interface.
346 346
347 347 # Begin of peer interface.
348 348
349 349 def commandexecutor(self):
350 350 return localcommandexecutor(self)
351 351
352 352 # End of peer interface.
353 353
354 354 @interfaceutil.implementer(repository.ipeerlegacycommands)
355 355 class locallegacypeer(localpeer):
356 356 '''peer extension which implements legacy methods too; used for tests with
357 357 restricted capabilities'''
358 358
359 359 def __init__(self, repo):
360 360 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
361 361
362 362 # Begin of baselegacywirecommands interface.
363 363
364 364 def between(self, pairs):
365 365 return self._repo.between(pairs)
366 366
367 367 def branches(self, nodes):
368 368 return self._repo.branches(nodes)
369 369
370 370 def changegroup(self, nodes, source):
371 371 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
372 372 missingheads=self._repo.heads())
373 373 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
374 374
375 375 def changegroupsubset(self, bases, heads, source):
376 376 outgoing = discovery.outgoing(self._repo, missingroots=bases,
377 377 missingheads=heads)
378 378 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
379 379
380 380 # End of baselegacywirecommands interface.
381 381
382 382 # Increment the sub-version when the revlog v2 format changes to lock out old
383 383 # clients.
384 384 REVLOGV2_REQUIREMENT = 'exp-revlogv2.1'
385 385
386 386 # A repository with the sparserevlog feature will have delta chains that
387 387 # can spread over a larger span. Sparse reading cuts these large spans into
388 388 # pieces, so that each piece isn't too big.
389 389 # Without the sparserevlog capability, reading from the repository could use
390 390 # huge amounts of memory, because the whole span would be read at once,
391 391 # including all the intermediate revisions that aren't pertinent for the chain.
392 392 # This is why once a repository has enabled sparse-read, it becomes required.
393 393 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
394 394
395 395 # Functions receiving (ui, features) that extensions can register to impact
396 396 # the ability to load repositories with custom requirements. Only
397 397 # functions defined in loaded extensions are called.
398 398 #
399 399 # The function receives a set of requirement strings that the repository
400 400 # is capable of opening. Functions will typically add elements to the
401 401 # set to reflect that the extension knows how to handle that requirements.
402 402 featuresetupfuncs = set()
403 403
404 404 def makelocalrepository(baseui, path, intents=None):
405 405 """Create a local repository object.
406 406
407 407 Given arguments needed to construct a local repository, this function
408 408 performs various early repository loading functionality (such as
409 409 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
410 410 the repository can be opened, derives a type suitable for representing
411 411 that repository, and returns an instance of it.
412 412
413 413 The returned object conforms to the ``repository.completelocalrepository``
414 414 interface.
415 415
416 416 The repository type is derived by calling a series of factory functions
417 417 for each aspect/interface of the final repository. These are defined by
418 418 ``REPO_INTERFACES``.
419 419
420 420 Each factory function is called to produce a type implementing a specific
421 421 interface. The cumulative list of returned types will be combined into a
422 422 new type and that type will be instantiated to represent the local
423 423 repository.
424 424
425 425 The factory functions each receive various state that may be consulted
426 426 as part of deriving a type.
427 427
428 428 Extensions should wrap these factory functions to customize repository type
429 429 creation. Note that an extension's wrapped function may be called even if
430 430 that extension is not loaded for the repo being constructed. Extensions
431 431 should check if their ``__name__`` appears in the
432 432 ``extensionmodulenames`` set passed to the factory function and no-op if
433 433 not.
434 434 """
435 435 ui = baseui.copy()
436 436 # Prevent copying repo configuration.
437 437 ui.copy = baseui.copy
438 438
439 439 # Working directory VFS rooted at repository root.
440 440 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
441 441
442 442 # Main VFS for .hg/ directory.
443 443 hgpath = wdirvfs.join(b'.hg')
444 444 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
445 445
446 446 # The .hg/ path should exist and should be a directory. All other
447 447 # cases are errors.
448 448 if not hgvfs.isdir():
449 449 try:
450 450 hgvfs.stat()
451 451 except OSError as e:
452 452 if e.errno != errno.ENOENT:
453 453 raise
454 454
455 455 raise error.RepoError(_(b'repository %s not found') % path)
456 456
457 457 # .hg/requires file contains a newline-delimited list of
458 458 # features/capabilities the opener (us) must have in order to use
459 459 # the repository. This file was introduced in Mercurial 0.9.2,
460 460 # which means very old repositories may not have one. We assume
461 461 # a missing file translates to no requirements.
462 462 try:
463 463 requirements = set(hgvfs.read(b'requires').splitlines())
464 464 except IOError as e:
465 465 if e.errno != errno.ENOENT:
466 466 raise
467 467 requirements = set()
468 468
469 469 # The .hg/hgrc file may load extensions or contain config options
470 470 # that influence repository construction. Attempt to load it and
471 471 # process any new extensions that it may have pulled in.
472 472 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
473 473 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
474 474 extensions.loadall(ui)
475 475 extensions.populateui(ui)
476 476
477 477 # Set of module names of extensions loaded for this repository.
478 478 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
479 479
480 480 supportedrequirements = gathersupportedrequirements(ui)
481 481
482 482 # We first validate the requirements are known.
483 483 ensurerequirementsrecognized(requirements, supportedrequirements)
484 484
485 485 # Then we validate that the known set is reasonable to use together.
486 486 ensurerequirementscompatible(ui, requirements)
487 487
488 488 # TODO there are unhandled edge cases related to opening repositories with
489 489 # shared storage. If storage is shared, we should also test for requirements
490 490 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
491 491 # that repo, as that repo may load extensions needed to open it. This is a
492 492 # bit complicated because we don't want the other hgrc to overwrite settings
493 493 # in this hgrc.
494 494 #
495 495 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
496 496 # file when sharing repos. But if a requirement is added after the share is
497 497 # performed, thereby introducing a new requirement for the opener, we may
498 498 # will not see that and could encounter a run-time error interacting with
499 499 # that shared store since it has an unknown-to-us requirement.
500 500
501 501 # At this point, we know we should be capable of opening the repository.
502 502 # Now get on with doing that.
503 503
504 504 features = set()
505 505
506 506 # The "store" part of the repository holds versioned data. How it is
507 507 # accessed is determined by various requirements. The ``shared`` or
508 508 # ``relshared`` requirements indicate the store lives in the path contained
509 509 # in the ``.hg/sharedpath`` file. This is an absolute path for
510 510 # ``shared`` and relative to ``.hg/`` for ``relshared``.
511 511 if b'shared' in requirements or b'relshared' in requirements:
512 512 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
513 513 if b'relshared' in requirements:
514 514 sharedpath = hgvfs.join(sharedpath)
515 515
516 516 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
517 517
518 518 if not sharedvfs.exists():
519 519 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
520 520 b'directory %s') % sharedvfs.base)
521 521
522 522 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
523 523
524 524 storebasepath = sharedvfs.base
525 525 cachepath = sharedvfs.join(b'cache')
526 526 else:
527 527 storebasepath = hgvfs.base
528 528 cachepath = hgvfs.join(b'cache')
529 529 wcachepath = hgvfs.join(b'wcache')
530 530
531 531
532 532 # The store has changed over time and the exact layout is dictated by
533 533 # requirements. The store interface abstracts differences across all
534 534 # of them.
535 535 store = makestore(requirements, storebasepath,
536 536 lambda base: vfsmod.vfs(base, cacheaudited=True))
537 537 hgvfs.createmode = store.createmode
538 538
539 539 storevfs = store.vfs
540 540 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
541 541
542 542 # The cache vfs is used to manage cache files.
543 543 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
544 544 cachevfs.createmode = store.createmode
545 545 # The cache vfs is used to manage cache files related to the working copy
546 546 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
547 547 wcachevfs.createmode = store.createmode
548 548
549 549 # Now resolve the type for the repository object. We do this by repeatedly
550 550 # calling a factory function to produces types for specific aspects of the
551 551 # repo's operation. The aggregate returned types are used as base classes
552 552 # for a dynamically-derived type, which will represent our new repository.
553 553
554 554 bases = []
555 555 extrastate = {}
556 556
557 557 for iface, fn in REPO_INTERFACES:
558 558 # We pass all potentially useful state to give extensions tons of
559 559 # flexibility.
560 560 typ = fn()(ui=ui,
561 561 intents=intents,
562 562 requirements=requirements,
563 563 features=features,
564 564 wdirvfs=wdirvfs,
565 565 hgvfs=hgvfs,
566 566 store=store,
567 567 storevfs=storevfs,
568 568 storeoptions=storevfs.options,
569 569 cachevfs=cachevfs,
570 570 wcachevfs=wcachevfs,
571 571 extensionmodulenames=extensionmodulenames,
572 572 extrastate=extrastate,
573 573 baseclasses=bases)
574 574
575 575 if not isinstance(typ, type):
576 576 raise error.ProgrammingError('unable to construct type for %s' %
577 577 iface)
578 578
579 579 bases.append(typ)
580 580
581 581 # type() allows you to use characters in type names that wouldn't be
582 582 # recognized as Python symbols in source code. We abuse that to add
583 583 # rich information about our constructed repo.
584 584 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
585 585 wdirvfs.base,
586 586 b','.join(sorted(requirements))))
587 587
588 588 cls = type(name, tuple(bases), {})
589 589
590 590 return cls(
591 591 baseui=baseui,
592 592 ui=ui,
593 593 origroot=path,
594 594 wdirvfs=wdirvfs,
595 595 hgvfs=hgvfs,
596 596 requirements=requirements,
597 597 supportedrequirements=supportedrequirements,
598 598 sharedpath=storebasepath,
599 599 store=store,
600 600 cachevfs=cachevfs,
601 601 wcachevfs=wcachevfs,
602 602 features=features,
603 603 intents=intents)
604 604
605 605 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
606 606 """Load hgrc files/content into a ui instance.
607 607
608 608 This is called during repository opening to load any additional
609 609 config files or settings relevant to the current repository.
610 610
611 611 Returns a bool indicating whether any additional configs were loaded.
612 612
613 613 Extensions should monkeypatch this function to modify how per-repo
614 614 configs are loaded. For example, an extension may wish to pull in
615 615 configs from alternate files or sources.
616 616 """
617 617 try:
618 618 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
619 619 return True
620 620 except IOError:
621 621 return False
622 622
623 623 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
624 624 """Perform additional actions after .hg/hgrc is loaded.
625 625
626 626 This function is called during repository loading immediately after
627 627 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
628 628
629 629 The function can be used to validate configs, automatically add
630 630 options (including extensions) based on requirements, etc.
631 631 """
632 632
633 633 # Map of requirements to list of extensions to load automatically when
634 634 # requirement is present.
635 635 autoextensions = {
636 636 b'largefiles': [b'largefiles'],
637 637 b'lfs': [b'lfs'],
638 638 }
639 639
640 640 for requirement, names in sorted(autoextensions.items()):
641 641 if requirement not in requirements:
642 642 continue
643 643
644 644 for name in names:
645 645 if not ui.hasconfig(b'extensions', name):
646 646 ui.setconfig(b'extensions', name, b'', source='autoload')
647 647
648 648 def gathersupportedrequirements(ui):
649 649 """Determine the complete set of recognized requirements."""
650 650 # Start with all requirements supported by this file.
651 651 supported = set(localrepository._basesupported)
652 652
653 653 # Execute ``featuresetupfuncs`` entries if they belong to an extension
654 654 # relevant to this ui instance.
655 655 modules = {m.__name__ for n, m in extensions.extensions(ui)}
656 656
657 657 for fn in featuresetupfuncs:
658 658 if fn.__module__ in modules:
659 659 fn(ui, supported)
660 660
661 661 # Add derived requirements from registered compression engines.
662 662 for name in util.compengines:
663 663 engine = util.compengines[name]
664 664 if engine.available() and engine.revlogheader():
665 665 supported.add(b'exp-compression-%s' % name)
666 666 if engine.name() == 'zstd':
667 667 supported.add(b'revlog-compression-zstd')
668 668
669 669 return supported
670 670
671 671 def ensurerequirementsrecognized(requirements, supported):
672 672 """Validate that a set of local requirements is recognized.
673 673
674 674 Receives a set of requirements. Raises an ``error.RepoError`` if there
675 675 exists any requirement in that set that currently loaded code doesn't
676 676 recognize.
677 677
678 678 Returns a set of supported requirements.
679 679 """
680 680 missing = set()
681 681
682 682 for requirement in requirements:
683 683 if requirement in supported:
684 684 continue
685 685
686 686 if not requirement or not requirement[0:1].isalnum():
687 687 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
688 688
689 689 missing.add(requirement)
690 690
691 691 if missing:
692 692 raise error.RequirementError(
693 693 _(b'repository requires features unknown to this Mercurial: %s') %
694 694 b' '.join(sorted(missing)),
695 695 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
696 696 b'for more information'))
697 697
698 698 def ensurerequirementscompatible(ui, requirements):
699 699 """Validates that a set of recognized requirements is mutually compatible.
700 700
701 701 Some requirements may not be compatible with others or require
702 702 config options that aren't enabled. This function is called during
703 703 repository opening to ensure that the set of requirements needed
704 704 to open a repository is sane and compatible with config options.
705 705
706 706 Extensions can monkeypatch this function to perform additional
707 707 checking.
708 708
709 709 ``error.RepoError`` should be raised on failure.
710 710 """
711 711 if b'exp-sparse' in requirements and not sparse.enabled:
712 712 raise error.RepoError(_(b'repository is using sparse feature but '
713 713 b'sparse is not enabled; enable the '
714 714 b'"sparse" extensions to access'))
715 715
716 716 def makestore(requirements, path, vfstype):
717 717 """Construct a storage object for a repository."""
718 718 if b'store' in requirements:
719 719 if b'fncache' in requirements:
720 720 return storemod.fncachestore(path, vfstype,
721 721 b'dotencode' in requirements)
722 722
723 723 return storemod.encodedstore(path, vfstype)
724 724
725 725 return storemod.basicstore(path, vfstype)
726 726
727 727 def resolvestorevfsoptions(ui, requirements, features):
728 728 """Resolve the options to pass to the store vfs opener.
729 729
730 730 The returned dict is used to influence behavior of the storage layer.
731 731 """
732 732 options = {}
733 733
734 734 if b'treemanifest' in requirements:
735 735 options[b'treemanifest'] = True
736 736
737 737 # experimental config: format.manifestcachesize
738 738 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
739 739 if manifestcachesize is not None:
740 740 options[b'manifestcachesize'] = manifestcachesize
741 741
742 742 # In the absence of another requirement superseding a revlog-related
743 743 # requirement, we have to assume the repo is using revlog version 0.
744 744 # This revlog format is super old and we don't bother trying to parse
745 745 # opener options for it because those options wouldn't do anything
746 746 # meaningful on such old repos.
747 747 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
748 748 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
749 749
750 750 return options
751 751
752 752 def resolverevlogstorevfsoptions(ui, requirements, features):
753 753 """Resolve opener options specific to revlogs."""
754 754
755 755 options = {}
756 756 options[b'flagprocessors'] = {}
757 757
758 758 if b'revlogv1' in requirements:
759 759 options[b'revlogv1'] = True
760 760 if REVLOGV2_REQUIREMENT in requirements:
761 761 options[b'revlogv2'] = True
762 762
763 763 if b'generaldelta' in requirements:
764 764 options[b'generaldelta'] = True
765 765
766 766 # experimental config: format.chunkcachesize
767 767 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
768 768 if chunkcachesize is not None:
769 769 options[b'chunkcachesize'] = chunkcachesize
770 770
771 771 deltabothparents = ui.configbool(b'storage',
772 772 b'revlog.optimize-delta-parent-choice')
773 773 options[b'deltabothparents'] = deltabothparents
774 774
775 775 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
776 776 lazydeltabase = False
777 777 if lazydelta:
778 778 lazydeltabase = ui.configbool(b'storage',
779 779 b'revlog.reuse-external-delta-parent')
780 780 if lazydeltabase is None:
781 781 lazydeltabase = not scmutil.gddeltaconfig(ui)
782 782 options[b'lazydelta'] = lazydelta
783 783 options[b'lazydeltabase'] = lazydeltabase
784 784
785 785 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
786 786 if 0 <= chainspan:
787 787 options[b'maxdeltachainspan'] = chainspan
788 788
789 789 mmapindexthreshold = ui.configbytes(b'experimental',
790 790 b'mmapindexthreshold')
791 791 if mmapindexthreshold is not None:
792 792 options[b'mmapindexthreshold'] = mmapindexthreshold
793 793
794 794 withsparseread = ui.configbool(b'experimental', b'sparse-read')
795 795 srdensitythres = float(ui.config(b'experimental',
796 796 b'sparse-read.density-threshold'))
797 797 srmingapsize = ui.configbytes(b'experimental',
798 798 b'sparse-read.min-gap-size')
799 799 options[b'with-sparse-read'] = withsparseread
800 800 options[b'sparse-read-density-threshold'] = srdensitythres
801 801 options[b'sparse-read-min-gap-size'] = srmingapsize
802 802
803 803 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
804 804 options[b'sparse-revlog'] = sparserevlog
805 805 if sparserevlog:
806 806 options[b'generaldelta'] = True
807 807
808 808 maxchainlen = None
809 809 if sparserevlog:
810 810 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
811 811 # experimental config: format.maxchainlen
812 812 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
813 813 if maxchainlen is not None:
814 814 options[b'maxchainlen'] = maxchainlen
815 815
816 816 for r in requirements:
817 817 # we allow multiple compression engine requirement to co-exist because
818 818 # strickly speaking, revlog seems to support mixed compression style.
819 819 #
820 820 # The compression used for new entries will be "the last one"
821 821 prefix = r.startswith
822 822 if prefix('revlog-compression-') or prefix('exp-compression-'):
823 823 options[b'compengine'] = r.split('-', 2)[2]
824 824
825 825 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
826 826 if options[b'zlib.level'] is not None:
827 827 if not (0 <= options[b'zlib.level'] <= 9):
828 828 msg = _('invalid value for `storage.revlog.zlib.level` config: %d')
829 829 raise error.Abort(msg % options[b'zlib.level'])
830 830 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
831 831 if options[b'zstd.level'] is not None:
832 832 if not (0 <= options[b'zstd.level'] <= 22):
833 833 msg = _('invalid value for `storage.revlog.zstd.level` config: %d')
834 834 raise error.Abort(msg % options[b'zstd.level'])
835 835
836 836 if repository.NARROW_REQUIREMENT in requirements:
837 837 options[b'enableellipsis'] = True
838 838
839 839 return options
840 840
841 841 def makemain(**kwargs):
842 842 """Produce a type conforming to ``ilocalrepositorymain``."""
843 843 return localrepository
844 844
845 845 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
846 846 class revlogfilestorage(object):
847 847 """File storage when using revlogs."""
848 848
849 849 def file(self, path):
850 850 if path[0] == b'/':
851 851 path = path[1:]
852 852
853 853 return filelog.filelog(self.svfs, path)
854 854
855 855 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
856 856 class revlognarrowfilestorage(object):
857 857 """File storage when using revlogs and narrow files."""
858 858
859 859 def file(self, path):
860 860 if path[0] == b'/':
861 861 path = path[1:]
862 862
863 863 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
864 864
865 865 def makefilestorage(requirements, features, **kwargs):
866 866 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
867 867 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
868 868 features.add(repository.REPO_FEATURE_STREAM_CLONE)
869 869
870 870 if repository.NARROW_REQUIREMENT in requirements:
871 871 return revlognarrowfilestorage
872 872 else:
873 873 return revlogfilestorage
874 874
875 875 # List of repository interfaces and factory functions for them. Each
876 876 # will be called in order during ``makelocalrepository()`` to iteratively
877 877 # derive the final type for a local repository instance. We capture the
878 878 # function as a lambda so we don't hold a reference and the module-level
879 879 # functions can be wrapped.
880 880 REPO_INTERFACES = [
881 881 (repository.ilocalrepositorymain, lambda: makemain),
882 882 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
883 883 ]
884 884
885 885 @interfaceutil.implementer(repository.ilocalrepositorymain)
886 886 class localrepository(object):
887 887 """Main class for representing local repositories.
888 888
889 889 All local repositories are instances of this class.
890 890
891 891 Constructed on its own, instances of this class are not usable as
892 892 repository objects. To obtain a usable repository object, call
893 893 ``hg.repository()``, ``localrepo.instance()``, or
894 894 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
895 895 ``instance()`` adds support for creating new repositories.
896 896 ``hg.repository()`` adds more extension integration, including calling
897 897 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
898 898 used.
899 899 """
900 900
901 901 # obsolete experimental requirements:
902 902 # - manifestv2: An experimental new manifest format that allowed
903 903 # for stem compression of long paths. Experiment ended up not
904 904 # being successful (repository sizes went up due to worse delta
905 905 # chains), and the code was deleted in 4.6.
906 906 supportedformats = {
907 907 'revlogv1',
908 908 'generaldelta',
909 909 'treemanifest',
910 910 REVLOGV2_REQUIREMENT,
911 911 SPARSEREVLOG_REQUIREMENT,
912 912 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
913 913 }
914 914 _basesupported = supportedformats | {
915 915 'store',
916 916 'fncache',
917 917 'shared',
918 918 'relshared',
919 919 'dotencode',
920 920 'exp-sparse',
921 921 'internal-phase'
922 922 }
923 923
924 924 # list of prefix for file which can be written without 'wlock'
925 925 # Extensions should extend this list when needed
926 926 _wlockfreeprefix = {
927 927 # We migh consider requiring 'wlock' for the next
928 928 # two, but pretty much all the existing code assume
929 929 # wlock is not needed so we keep them excluded for
930 930 # now.
931 931 'hgrc',
932 932 'requires',
933 933 # XXX cache is a complicatged business someone
934 934 # should investigate this in depth at some point
935 935 'cache/',
936 936 # XXX shouldn't be dirstate covered by the wlock?
937 937 'dirstate',
938 938 # XXX bisect was still a bit too messy at the time
939 939 # this changeset was introduced. Someone should fix
940 940 # the remainig bit and drop this line
941 941 'bisect.state',
942 942 }
943 943
944 944 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
945 945 supportedrequirements, sharedpath, store, cachevfs, wcachevfs,
946 946 features, intents=None):
947 947 """Create a new local repository instance.
948 948
949 949 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
950 950 or ``localrepo.makelocalrepository()`` for obtaining a new repository
951 951 object.
952 952
953 953 Arguments:
954 954
955 955 baseui
956 956 ``ui.ui`` instance that ``ui`` argument was based off of.
957 957
958 958 ui
959 959 ``ui.ui`` instance for use by the repository.
960 960
961 961 origroot
962 962 ``bytes`` path to working directory root of this repository.
963 963
964 964 wdirvfs
965 965 ``vfs.vfs`` rooted at the working directory.
966 966
967 967 hgvfs
968 968 ``vfs.vfs`` rooted at .hg/
969 969
970 970 requirements
971 971 ``set`` of bytestrings representing repository opening requirements.
972 972
973 973 supportedrequirements
974 974 ``set`` of bytestrings representing repository requirements that we
975 975 know how to open. May be a supetset of ``requirements``.
976 976
977 977 sharedpath
978 978 ``bytes`` Defining path to storage base directory. Points to a
979 979 ``.hg/`` directory somewhere.
980 980
981 981 store
982 982 ``store.basicstore`` (or derived) instance providing access to
983 983 versioned storage.
984 984
985 985 cachevfs
986 986 ``vfs.vfs`` used for cache files.
987 987
988 988 wcachevfs
989 989 ``vfs.vfs`` used for cache files related to the working copy.
990 990
991 991 features
992 992 ``set`` of bytestrings defining features/capabilities of this
993 993 instance.
994 994
995 995 intents
996 996 ``set`` of system strings indicating what this repo will be used
997 997 for.
998 998 """
999 999 self.baseui = baseui
1000 1000 self.ui = ui
1001 1001 self.origroot = origroot
1002 1002 # vfs rooted at working directory.
1003 1003 self.wvfs = wdirvfs
1004 1004 self.root = wdirvfs.base
1005 1005 # vfs rooted at .hg/. Used to access most non-store paths.
1006 1006 self.vfs = hgvfs
1007 1007 self.path = hgvfs.base
1008 1008 self.requirements = requirements
1009 1009 self.supported = supportedrequirements
1010 1010 self.sharedpath = sharedpath
1011 1011 self.store = store
1012 1012 self.cachevfs = cachevfs
1013 1013 self.wcachevfs = wcachevfs
1014 1014 self.features = features
1015 1015
1016 1016 self.filtername = None
1017 1017
1018 1018 if (self.ui.configbool('devel', 'all-warnings') or
1019 1019 self.ui.configbool('devel', 'check-locks')):
1020 1020 self.vfs.audit = self._getvfsward(self.vfs.audit)
1021 1021 # A list of callback to shape the phase if no data were found.
1022 1022 # Callback are in the form: func(repo, roots) --> processed root.
1023 1023 # This list it to be filled by extension during repo setup
1024 1024 self._phasedefaults = []
1025 1025
1026 1026 color.setup(self.ui)
1027 1027
1028 1028 self.spath = self.store.path
1029 1029 self.svfs = self.store.vfs
1030 1030 self.sjoin = self.store.join
1031 1031 if (self.ui.configbool('devel', 'all-warnings') or
1032 1032 self.ui.configbool('devel', 'check-locks')):
1033 1033 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
1034 1034 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1035 1035 else: # standard vfs
1036 1036 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1037 1037
1038 1038 self._dirstatevalidatewarned = False
1039 1039
1040 1040 self._branchcaches = branchmap.BranchMapCache()
1041 1041 self._revbranchcache = None
1042 1042 self._filterpats = {}
1043 1043 self._datafilters = {}
1044 1044 self._transref = self._lockref = self._wlockref = None
1045 1045
1046 1046 # A cache for various files under .hg/ that tracks file changes,
1047 1047 # (used by the filecache decorator)
1048 1048 #
1049 1049 # Maps a property name to its util.filecacheentry
1050 1050 self._filecache = {}
1051 1051
1052 1052 # hold sets of revision to be filtered
1053 1053 # should be cleared when something might have changed the filter value:
1054 1054 # - new changesets,
1055 1055 # - phase change,
1056 1056 # - new obsolescence marker,
1057 1057 # - working directory parent change,
1058 1058 # - bookmark changes
1059 1059 self.filteredrevcache = {}
1060 1060
1061 1061 # post-dirstate-status hooks
1062 1062 self._postdsstatus = []
1063 1063
1064 1064 # generic mapping between names and nodes
1065 1065 self.names = namespaces.namespaces()
1066 1066
1067 1067 # Key to signature value.
1068 1068 self._sparsesignaturecache = {}
1069 1069 # Signature to cached matcher instance.
1070 1070 self._sparsematchercache = {}
1071 1071
1072 1072 self._extrafilterid = repoview.extrafilter(ui)
1073 1073
1074 1074 def _getvfsward(self, origfunc):
1075 1075 """build a ward for self.vfs"""
1076 1076 rref = weakref.ref(self)
1077 1077 def checkvfs(path, mode=None):
1078 1078 ret = origfunc(path, mode=mode)
1079 1079 repo = rref()
1080 1080 if (repo is None
1081 1081 or not util.safehasattr(repo, '_wlockref')
1082 1082 or not util.safehasattr(repo, '_lockref')):
1083 1083 return
1084 1084 if mode in (None, 'r', 'rb'):
1085 1085 return
1086 1086 if path.startswith(repo.path):
1087 1087 # truncate name relative to the repository (.hg)
1088 1088 path = path[len(repo.path) + 1:]
1089 1089 if path.startswith('cache/'):
1090 1090 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1091 1091 repo.ui.develwarn(msg % path, stacklevel=3, config="cache-vfs")
1092 1092 if path.startswith('journal.') or path.startswith('undo.'):
1093 1093 # journal is covered by 'lock'
1094 1094 if repo._currentlock(repo._lockref) is None:
1095 1095 repo.ui.develwarn('write with no lock: "%s"' % path,
1096 1096 stacklevel=3, config='check-locks')
1097 1097 elif repo._currentlock(repo._wlockref) is None:
1098 1098 # rest of vfs files are covered by 'wlock'
1099 1099 #
1100 1100 # exclude special files
1101 1101 for prefix in self._wlockfreeprefix:
1102 1102 if path.startswith(prefix):
1103 1103 return
1104 1104 repo.ui.develwarn('write with no wlock: "%s"' % path,
1105 1105 stacklevel=3, config='check-locks')
1106 1106 return ret
1107 1107 return checkvfs
1108 1108
1109 1109 def _getsvfsward(self, origfunc):
1110 1110 """build a ward for self.svfs"""
1111 1111 rref = weakref.ref(self)
1112 1112 def checksvfs(path, mode=None):
1113 1113 ret = origfunc(path, mode=mode)
1114 1114 repo = rref()
1115 1115 if repo is None or not util.safehasattr(repo, '_lockref'):
1116 1116 return
1117 1117 if mode in (None, 'r', 'rb'):
1118 1118 return
1119 1119 if path.startswith(repo.sharedpath):
1120 1120 # truncate name relative to the repository (.hg)
1121 1121 path = path[len(repo.sharedpath) + 1:]
1122 1122 if repo._currentlock(repo._lockref) is None:
1123 1123 repo.ui.develwarn('write with no lock: "%s"' % path,
1124 1124 stacklevel=4)
1125 1125 return ret
1126 1126 return checksvfs
1127 1127
1128 1128 def close(self):
1129 1129 self._writecaches()
1130 1130
1131 1131 def _writecaches(self):
1132 1132 if self._revbranchcache:
1133 1133 self._revbranchcache.write()
1134 1134
1135 1135 def _restrictcapabilities(self, caps):
1136 1136 if self.ui.configbool('experimental', 'bundle2-advertise'):
1137 1137 caps = set(caps)
1138 1138 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1139 1139 role='client'))
1140 1140 caps.add('bundle2=' + urlreq.quote(capsblob))
1141 1141 return caps
1142 1142
1143 1143 def _writerequirements(self):
1144 1144 scmutil.writerequires(self.vfs, self.requirements)
1145 1145
1146 1146 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1147 1147 # self -> auditor -> self._checknested -> self
1148 1148
1149 1149 @property
1150 1150 def auditor(self):
1151 1151 # This is only used by context.workingctx.match in order to
1152 1152 # detect files in subrepos.
1153 1153 return pathutil.pathauditor(self.root, callback=self._checknested)
1154 1154
1155 1155 @property
1156 1156 def nofsauditor(self):
1157 1157 # This is only used by context.basectx.match in order to detect
1158 1158 # files in subrepos.
1159 1159 return pathutil.pathauditor(self.root, callback=self._checknested,
1160 1160 realfs=False, cached=True)
1161 1161
1162 1162 def _checknested(self, path):
1163 1163 """Determine if path is a legal nested repository."""
1164 1164 if not path.startswith(self.root):
1165 1165 return False
1166 1166 subpath = path[len(self.root) + 1:]
1167 1167 normsubpath = util.pconvert(subpath)
1168 1168
1169 1169 # XXX: Checking against the current working copy is wrong in
1170 1170 # the sense that it can reject things like
1171 1171 #
1172 1172 # $ hg cat -r 10 sub/x.txt
1173 1173 #
1174 1174 # if sub/ is no longer a subrepository in the working copy
1175 1175 # parent revision.
1176 1176 #
1177 1177 # However, it can of course also allow things that would have
1178 1178 # been rejected before, such as the above cat command if sub/
1179 1179 # is a subrepository now, but was a normal directory before.
1180 1180 # The old path auditor would have rejected by mistake since it
1181 1181 # panics when it sees sub/.hg/.
1182 1182 #
1183 1183 # All in all, checking against the working copy seems sensible
1184 1184 # since we want to prevent access to nested repositories on
1185 1185 # the filesystem *now*.
1186 1186 ctx = self[None]
1187 1187 parts = util.splitpath(subpath)
1188 1188 while parts:
1189 1189 prefix = '/'.join(parts)
1190 1190 if prefix in ctx.substate:
1191 1191 if prefix == normsubpath:
1192 1192 return True
1193 1193 else:
1194 1194 sub = ctx.sub(prefix)
1195 1195 return sub.checknested(subpath[len(prefix) + 1:])
1196 1196 else:
1197 1197 parts.pop()
1198 1198 return False
1199 1199
1200 1200 def peer(self):
1201 1201 return localpeer(self) # not cached to avoid reference cycle
1202 1202
1203 1203 def unfiltered(self):
1204 1204 """Return unfiltered version of the repository
1205 1205
1206 1206 Intended to be overwritten by filtered repo."""
1207 1207 return self
1208 1208
1209 1209 def filtered(self, name, visibilityexceptions=None):
1210 1210 """Return a filtered version of a repository
1211 1211
1212 1212 The `name` parameter is the identifier of the requested view. This
1213 1213 will return a repoview object set "exactly" to the specified view.
1214 1214
1215 1215 This function does not apply recursive filtering to a repository. For
1216 1216 example calling `repo.filtered("served")` will return a repoview using
1217 1217 the "served" view, regardless of the initial view used by `repo`.
1218 1218
1219 1219 In other word, there is always only one level of `repoview` "filtering".
1220 1220 """
1221 1221 if self._extrafilterid is not None and '%' not in name:
1222 1222 name = name + '%' + self._extrafilterid
1223 1223
1224 1224 cls = repoview.newtype(self.unfiltered().__class__)
1225 1225 return cls(self, name, visibilityexceptions)
1226 1226
1227 1227 @mixedrepostorecache(('bookmarks', 'plain'), ('bookmarks.current', 'plain'),
1228 1228 ('bookmarks', ''), ('00changelog.i', ''))
1229 1229 def _bookmarks(self):
1230 1230 return bookmarks.bmstore(self)
1231 1231
1232 1232 def _refreshchangelog(self):
1233 1233 """make sure the in memory changelog match the on-disk one"""
1234 1234 if ('changelog' in vars(self) and self.currenttransaction() is None):
1235 1235 del self.changelog
1236 1236
1237 1237 @property
1238 1238 def _activebookmark(self):
1239 1239 return self._bookmarks.active
1240 1240
1241 1241 # _phasesets depend on changelog. what we need is to call
1242 1242 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1243 1243 # can't be easily expressed in filecache mechanism.
1244 1244 @storecache('phaseroots', '00changelog.i')
1245 1245 def _phasecache(self):
1246 1246 return phases.phasecache(self, self._phasedefaults)
1247 1247
1248 1248 @storecache('obsstore')
1249 1249 def obsstore(self):
1250 1250 return obsolete.makestore(self.ui, self)
1251 1251
1252 1252 @storecache('00changelog.i')
1253 1253 def changelog(self):
1254 1254 return changelog.changelog(self.svfs,
1255 1255 trypending=txnutil.mayhavepending(self.root))
1256 1256
1257 1257 @storecache('00manifest.i')
1258 1258 def manifestlog(self):
1259 1259 rootstore = manifest.manifestrevlog(self.svfs)
1260 1260 return manifest.manifestlog(self.svfs, self, rootstore,
1261 1261 self._storenarrowmatch)
1262 1262
1263 1263 @repofilecache('dirstate')
1264 1264 def dirstate(self):
1265 1265 return self._makedirstate()
1266 1266
1267 1267 def _makedirstate(self):
1268 1268 """Extension point for wrapping the dirstate per-repo."""
1269 1269 sparsematchfn = lambda: sparse.matcher(self)
1270 1270
1271 1271 return dirstate.dirstate(self.vfs, self.ui, self.root,
1272 1272 self._dirstatevalidate, sparsematchfn)
1273 1273
1274 1274 def _dirstatevalidate(self, node):
1275 1275 try:
1276 1276 self.changelog.rev(node)
1277 1277 return node
1278 1278 except error.LookupError:
1279 1279 if not self._dirstatevalidatewarned:
1280 1280 self._dirstatevalidatewarned = True
1281 1281 self.ui.warn(_("warning: ignoring unknown"
1282 1282 " working parent %s!\n") % short(node))
1283 1283 return nullid
1284 1284
1285 1285 @storecache(narrowspec.FILENAME)
1286 1286 def narrowpats(self):
1287 1287 """matcher patterns for this repository's narrowspec
1288 1288
1289 1289 A tuple of (includes, excludes).
1290 1290 """
1291 1291 return narrowspec.load(self)
1292 1292
1293 1293 @storecache(narrowspec.FILENAME)
1294 1294 def _storenarrowmatch(self):
1295 1295 if repository.NARROW_REQUIREMENT not in self.requirements:
1296 1296 return matchmod.always()
1297 1297 include, exclude = self.narrowpats
1298 1298 return narrowspec.match(self.root, include=include, exclude=exclude)
1299 1299
1300 1300 @storecache(narrowspec.FILENAME)
1301 1301 def _narrowmatch(self):
1302 1302 if repository.NARROW_REQUIREMENT not in self.requirements:
1303 1303 return matchmod.always()
1304 1304 narrowspec.checkworkingcopynarrowspec(self)
1305 1305 include, exclude = self.narrowpats
1306 1306 return narrowspec.match(self.root, include=include, exclude=exclude)
1307 1307
1308 1308 def narrowmatch(self, match=None, includeexact=False):
1309 1309 """matcher corresponding the the repo's narrowspec
1310 1310
1311 1311 If `match` is given, then that will be intersected with the narrow
1312 1312 matcher.
1313 1313
1314 1314 If `includeexact` is True, then any exact matches from `match` will
1315 1315 be included even if they're outside the narrowspec.
1316 1316 """
1317 1317 if match:
1318 1318 if includeexact and not self._narrowmatch.always():
1319 1319 # do not exclude explicitly-specified paths so that they can
1320 1320 # be warned later on
1321 1321 em = matchmod.exact(match.files())
1322 1322 nm = matchmod.unionmatcher([self._narrowmatch, em])
1323 1323 return matchmod.intersectmatchers(match, nm)
1324 1324 return matchmod.intersectmatchers(match, self._narrowmatch)
1325 1325 return self._narrowmatch
1326 1326
1327 1327 def setnarrowpats(self, newincludes, newexcludes):
1328 1328 narrowspec.save(self, newincludes, newexcludes)
1329 1329 self.invalidate(clearfilecache=True)
1330 1330
1331 1331 def __getitem__(self, changeid):
1332 1332 if changeid is None:
1333 1333 return context.workingctx(self)
1334 1334 if isinstance(changeid, context.basectx):
1335 1335 return changeid
1336 1336 if isinstance(changeid, slice):
1337 1337 # wdirrev isn't contiguous so the slice shouldn't include it
1338 1338 return [self[i]
1339 1339 for i in pycompat.xrange(*changeid.indices(len(self)))
1340 1340 if i not in self.changelog.filteredrevs]
1341 1341 try:
1342 1342 if isinstance(changeid, int):
1343 1343 node = self.changelog.node(changeid)
1344 1344 rev = changeid
1345 1345 elif changeid == 'null':
1346 1346 node = nullid
1347 1347 rev = nullrev
1348 1348 elif changeid == 'tip':
1349 1349 node = self.changelog.tip()
1350 1350 rev = self.changelog.rev(node)
1351 1351 elif changeid == '.':
1352 1352 # this is a hack to delay/avoid loading obsmarkers
1353 1353 # when we know that '.' won't be hidden
1354 1354 node = self.dirstate.p1()
1355 1355 rev = self.unfiltered().changelog.rev(node)
1356 1356 elif len(changeid) == 20:
1357 1357 try:
1358 1358 node = changeid
1359 1359 rev = self.changelog.rev(changeid)
1360 1360 except error.FilteredLookupError:
1361 1361 changeid = hex(changeid) # for the error message
1362 1362 raise
1363 1363 except LookupError:
1364 1364 # check if it might have come from damaged dirstate
1365 1365 #
1366 1366 # XXX we could avoid the unfiltered if we had a recognizable
1367 1367 # exception for filtered changeset access
1368 1368 if (self.local()
1369 1369 and changeid in self.unfiltered().dirstate.parents()):
1370 1370 msg = _("working directory has unknown parent '%s'!")
1371 1371 raise error.Abort(msg % short(changeid))
1372 1372 changeid = hex(changeid) # for the error message
1373 1373 raise
1374 1374
1375 1375 elif len(changeid) == 40:
1376 1376 node = bin(changeid)
1377 1377 rev = self.changelog.rev(node)
1378 1378 else:
1379 1379 raise error.ProgrammingError(
1380 1380 "unsupported changeid '%s' of type %s" %
1381 1381 (changeid, type(changeid)))
1382 1382
1383 1383 return context.changectx(self, rev, node)
1384 1384
1385 1385 except (error.FilteredIndexError, error.FilteredLookupError):
1386 1386 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1387 1387 % pycompat.bytestr(changeid))
1388 1388 except (IndexError, LookupError):
1389 1389 raise error.RepoLookupError(
1390 1390 _("unknown revision '%s'") % pycompat.bytestr(changeid))
1391 1391 except error.WdirUnsupported:
1392 1392 return context.workingctx(self)
1393 1393
1394 1394 def __contains__(self, changeid):
1395 1395 """True if the given changeid exists
1396 1396
1397 1397 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1398 1398 specified.
1399 1399 """
1400 1400 try:
1401 1401 self[changeid]
1402 1402 return True
1403 1403 except error.RepoLookupError:
1404 1404 return False
1405 1405
1406 1406 def __nonzero__(self):
1407 1407 return True
1408 1408
1409 1409 __bool__ = __nonzero__
1410 1410
1411 1411 def __len__(self):
1412 1412 # no need to pay the cost of repoview.changelog
1413 1413 unfi = self.unfiltered()
1414 1414 return len(unfi.changelog)
1415 1415
1416 1416 def __iter__(self):
1417 1417 return iter(self.changelog)
1418 1418
1419 1419 def revs(self, expr, *args):
1420 1420 '''Find revisions matching a revset.
1421 1421
1422 1422 The revset is specified as a string ``expr`` that may contain
1423 1423 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1424 1424
1425 1425 Revset aliases from the configuration are not expanded. To expand
1426 1426 user aliases, consider calling ``scmutil.revrange()`` or
1427 1427 ``repo.anyrevs([expr], user=True)``.
1428 1428
1429 1429 Returns a revset.abstractsmartset, which is a list-like interface
1430 1430 that contains integer revisions.
1431 1431 '''
1432 1432 tree = revsetlang.spectree(expr, *args)
1433 1433 return revset.makematcher(tree)(self)
1434 1434
1435 1435 def set(self, expr, *args):
1436 1436 '''Find revisions matching a revset and emit changectx instances.
1437 1437
1438 1438 This is a convenience wrapper around ``revs()`` that iterates the
1439 1439 result and is a generator of changectx instances.
1440 1440
1441 1441 Revset aliases from the configuration are not expanded. To expand
1442 1442 user aliases, consider calling ``scmutil.revrange()``.
1443 1443 '''
1444 1444 for r in self.revs(expr, *args):
1445 1445 yield self[r]
1446 1446
1447 1447 def anyrevs(self, specs, user=False, localalias=None):
1448 1448 '''Find revisions matching one of the given revsets.
1449 1449
1450 1450 Revset aliases from the configuration are not expanded by default. To
1451 1451 expand user aliases, specify ``user=True``. To provide some local
1452 1452 definitions overriding user aliases, set ``localalias`` to
1453 1453 ``{name: definitionstring}``.
1454 1454 '''
1455 1455 if user:
1456 1456 m = revset.matchany(self.ui, specs,
1457 1457 lookup=revset.lookupfn(self),
1458 1458 localalias=localalias)
1459 1459 else:
1460 1460 m = revset.matchany(None, specs, localalias=localalias)
1461 1461 return m(self)
1462 1462
1463 1463 def url(self):
1464 1464 return 'file:' + self.root
1465 1465
1466 1466 def hook(self, name, throw=False, **args):
1467 1467 """Call a hook, passing this repo instance.
1468 1468
1469 1469 This a convenience method to aid invoking hooks. Extensions likely
1470 1470 won't call this unless they have registered a custom hook or are
1471 1471 replacing code that is expected to call a hook.
1472 1472 """
1473 1473 return hook.hook(self.ui, self, name, throw, **args)
1474 1474
1475 1475 @filteredpropertycache
1476 1476 def _tagscache(self):
1477 1477 '''Returns a tagscache object that contains various tags related
1478 1478 caches.'''
1479 1479
1480 1480 # This simplifies its cache management by having one decorated
1481 1481 # function (this one) and the rest simply fetch things from it.
1482 1482 class tagscache(object):
1483 1483 def __init__(self):
1484 1484 # These two define the set of tags for this repository. tags
1485 1485 # maps tag name to node; tagtypes maps tag name to 'global' or
1486 1486 # 'local'. (Global tags are defined by .hgtags across all
1487 1487 # heads, and local tags are defined in .hg/localtags.)
1488 1488 # They constitute the in-memory cache of tags.
1489 1489 self.tags = self.tagtypes = None
1490 1490
1491 1491 self.nodetagscache = self.tagslist = None
1492 1492
1493 1493 cache = tagscache()
1494 1494 cache.tags, cache.tagtypes = self._findtags()
1495 1495
1496 1496 return cache
1497 1497
1498 1498 def tags(self):
1499 1499 '''return a mapping of tag to node'''
1500 1500 t = {}
1501 1501 if self.changelog.filteredrevs:
1502 1502 tags, tt = self._findtags()
1503 1503 else:
1504 1504 tags = self._tagscache.tags
1505 1505 rev = self.changelog.rev
1506 1506 for k, v in tags.iteritems():
1507 1507 try:
1508 1508 # ignore tags to unknown nodes
1509 1509 rev(v)
1510 1510 t[k] = v
1511 1511 except (error.LookupError, ValueError):
1512 1512 pass
1513 1513 return t
1514 1514
1515 1515 def _findtags(self):
1516 1516 '''Do the hard work of finding tags. Return a pair of dicts
1517 1517 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1518 1518 maps tag name to a string like \'global\' or \'local\'.
1519 1519 Subclasses or extensions are free to add their own tags, but
1520 1520 should be aware that the returned dicts will be retained for the
1521 1521 duration of the localrepo object.'''
1522 1522
1523 1523 # XXX what tagtype should subclasses/extensions use? Currently
1524 1524 # mq and bookmarks add tags, but do not set the tagtype at all.
1525 1525 # Should each extension invent its own tag type? Should there
1526 1526 # be one tagtype for all such "virtual" tags? Or is the status
1527 1527 # quo fine?
1528 1528
1529 1529
1530 1530 # map tag name to (node, hist)
1531 1531 alltags = tagsmod.findglobaltags(self.ui, self)
1532 1532 # map tag name to tag type
1533 1533 tagtypes = dict((tag, 'global') for tag in alltags)
1534 1534
1535 1535 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1536 1536
1537 1537 # Build the return dicts. Have to re-encode tag names because
1538 1538 # the tags module always uses UTF-8 (in order not to lose info
1539 1539 # writing to the cache), but the rest of Mercurial wants them in
1540 1540 # local encoding.
1541 1541 tags = {}
1542 1542 for (name, (node, hist)) in alltags.iteritems():
1543 1543 if node != nullid:
1544 1544 tags[encoding.tolocal(name)] = node
1545 1545 tags['tip'] = self.changelog.tip()
1546 1546 tagtypes = dict([(encoding.tolocal(name), value)
1547 1547 for (name, value) in tagtypes.iteritems()])
1548 1548 return (tags, tagtypes)
1549 1549
1550 1550 def tagtype(self, tagname):
1551 1551 '''
1552 1552 return the type of the given tag. result can be:
1553 1553
1554 1554 'local' : a local tag
1555 1555 'global' : a global tag
1556 1556 None : tag does not exist
1557 1557 '''
1558 1558
1559 1559 return self._tagscache.tagtypes.get(tagname)
1560 1560
1561 1561 def tagslist(self):
1562 1562 '''return a list of tags ordered by revision'''
1563 1563 if not self._tagscache.tagslist:
1564 1564 l = []
1565 1565 for t, n in self.tags().iteritems():
1566 1566 l.append((self.changelog.rev(n), t, n))
1567 1567 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1568 1568
1569 1569 return self._tagscache.tagslist
1570 1570
1571 1571 def nodetags(self, node):
1572 1572 '''return the tags associated with a node'''
1573 1573 if not self._tagscache.nodetagscache:
1574 1574 nodetagscache = {}
1575 1575 for t, n in self._tagscache.tags.iteritems():
1576 1576 nodetagscache.setdefault(n, []).append(t)
1577 1577 for tags in nodetagscache.itervalues():
1578 1578 tags.sort()
1579 1579 self._tagscache.nodetagscache = nodetagscache
1580 1580 return self._tagscache.nodetagscache.get(node, [])
1581 1581
1582 1582 def nodebookmarks(self, node):
1583 1583 """return the list of bookmarks pointing to the specified node"""
1584 1584 return self._bookmarks.names(node)
1585 1585
1586 1586 def branchmap(self):
1587 1587 '''returns a dictionary {branch: [branchheads]} with branchheads
1588 1588 ordered by increasing revision number'''
1589 1589 return self._branchcaches[self]
1590 1590
1591 1591 @unfilteredmethod
1592 1592 def revbranchcache(self):
1593 1593 if not self._revbranchcache:
1594 1594 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1595 1595 return self._revbranchcache
1596 1596
1597 1597 def branchtip(self, branch, ignoremissing=False):
1598 1598 '''return the tip node for a given branch
1599 1599
1600 1600 If ignoremissing is True, then this method will not raise an error.
1601 1601 This is helpful for callers that only expect None for a missing branch
1602 1602 (e.g. namespace).
1603 1603
1604 1604 '''
1605 1605 try:
1606 1606 return self.branchmap().branchtip(branch)
1607 1607 except KeyError:
1608 1608 if not ignoremissing:
1609 1609 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1610 1610 else:
1611 1611 pass
1612 1612
1613 1613 def lookup(self, key):
1614 1614 node = scmutil.revsymbol(self, key).node()
1615 1615 if node is None:
1616 1616 raise error.RepoLookupError(_("unknown revision '%s'") % key)
1617 1617 return node
1618 1618
1619 1619 def lookupbranch(self, key):
1620 1620 if self.branchmap().hasbranch(key):
1621 1621 return key
1622 1622
1623 1623 return scmutil.revsymbol(self, key).branch()
1624 1624
1625 1625 def known(self, nodes):
1626 1626 cl = self.changelog
1627 1627 nm = cl.nodemap
1628 1628 filtered = cl.filteredrevs
1629 1629 result = []
1630 1630 for n in nodes:
1631 1631 r = nm.get(n)
1632 1632 resp = not (r is None or r in filtered)
1633 1633 result.append(resp)
1634 1634 return result
1635 1635
1636 1636 def local(self):
1637 1637 return self
1638 1638
1639 1639 def publishing(self):
1640 1640 # it's safe (and desirable) to trust the publish flag unconditionally
1641 1641 # so that we don't finalize changes shared between users via ssh or nfs
1642 1642 return self.ui.configbool('phases', 'publish', untrusted=True)
1643 1643
1644 1644 def cancopy(self):
1645 1645 # so statichttprepo's override of local() works
1646 1646 if not self.local():
1647 1647 return False
1648 1648 if not self.publishing():
1649 1649 return True
1650 1650 # if publishing we can't copy if there is filtered content
1651 1651 return not self.filtered('visible').changelog.filteredrevs
1652 1652
1653 1653 def shared(self):
1654 1654 '''the type of shared repository (None if not shared)'''
1655 1655 if self.sharedpath != self.path:
1656 1656 return 'store'
1657 1657 return None
1658 1658
1659 1659 def wjoin(self, f, *insidef):
1660 1660 return self.vfs.reljoin(self.root, f, *insidef)
1661 1661
1662 1662 def setparents(self, p1, p2=nullid):
1663 1663 with self.dirstate.parentchange():
1664 1664 copies = self.dirstate.setparents(p1, p2)
1665 1665 pctx = self[p1]
1666 1666 if copies:
1667 1667 # Adjust copy records, the dirstate cannot do it, it
1668 1668 # requires access to parents manifests. Preserve them
1669 1669 # only for entries added to first parent.
1670 1670 for f in copies:
1671 1671 if f not in pctx and copies[f] in pctx:
1672 1672 self.dirstate.copy(copies[f], f)
1673 1673 if p2 == nullid:
1674 1674 for f, s in sorted(self.dirstate.copies().items()):
1675 1675 if f not in pctx and s not in pctx:
1676 1676 self.dirstate.copy(None, f)
1677 1677
1678 1678 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1679 1679 """changeid must be a changeset revision, if specified.
1680 1680 fileid can be a file revision or node."""
1681 1681 return context.filectx(self, path, changeid, fileid,
1682 1682 changectx=changectx)
1683 1683
1684 1684 def getcwd(self):
1685 1685 return self.dirstate.getcwd()
1686 1686
1687 1687 def pathto(self, f, cwd=None):
1688 1688 return self.dirstate.pathto(f, cwd)
1689 1689
1690 1690 def _loadfilter(self, filter):
1691 1691 if filter not in self._filterpats:
1692 1692 l = []
1693 1693 for pat, cmd in self.ui.configitems(filter):
1694 1694 if cmd == '!':
1695 1695 continue
1696 1696 mf = matchmod.match(self.root, '', [pat])
1697 1697 fn = None
1698 1698 params = cmd
1699 1699 for name, filterfn in self._datafilters.iteritems():
1700 1700 if cmd.startswith(name):
1701 1701 fn = filterfn
1702 1702 params = cmd[len(name):].lstrip()
1703 1703 break
1704 1704 if not fn:
1705 1705 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1706 1706 # Wrap old filters not supporting keyword arguments
1707 1707 if not pycompat.getargspec(fn)[2]:
1708 1708 oldfn = fn
1709 1709 fn = lambda s, c, **kwargs: oldfn(s, c)
1710 1710 l.append((mf, fn, params))
1711 1711 self._filterpats[filter] = l
1712 1712 return self._filterpats[filter]
1713 1713
1714 1714 def _filter(self, filterpats, filename, data):
1715 1715 for mf, fn, cmd in filterpats:
1716 1716 if mf(filename):
1717 1717 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1718 1718 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1719 1719 break
1720 1720
1721 1721 return data
1722 1722
1723 1723 @unfilteredpropertycache
1724 1724 def _encodefilterpats(self):
1725 1725 return self._loadfilter('encode')
1726 1726
1727 1727 @unfilteredpropertycache
1728 1728 def _decodefilterpats(self):
1729 1729 return self._loadfilter('decode')
1730 1730
1731 1731 def adddatafilter(self, name, filter):
1732 1732 self._datafilters[name] = filter
1733 1733
1734 1734 def wread(self, filename):
1735 1735 if self.wvfs.islink(filename):
1736 1736 data = self.wvfs.readlink(filename)
1737 1737 else:
1738 1738 data = self.wvfs.read(filename)
1739 1739 return self._filter(self._encodefilterpats, filename, data)
1740 1740
1741 1741 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1742 1742 """write ``data`` into ``filename`` in the working directory
1743 1743
1744 1744 This returns length of written (maybe decoded) data.
1745 1745 """
1746 1746 data = self._filter(self._decodefilterpats, filename, data)
1747 1747 if 'l' in flags:
1748 1748 self.wvfs.symlink(data, filename)
1749 1749 else:
1750 1750 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1751 1751 **kwargs)
1752 1752 if 'x' in flags:
1753 1753 self.wvfs.setflags(filename, False, True)
1754 1754 else:
1755 1755 self.wvfs.setflags(filename, False, False)
1756 1756 return len(data)
1757 1757
1758 1758 def wwritedata(self, filename, data):
1759 1759 return self._filter(self._decodefilterpats, filename, data)
1760 1760
1761 1761 def currenttransaction(self):
1762 1762 """return the current transaction or None if non exists"""
1763 1763 if self._transref:
1764 1764 tr = self._transref()
1765 1765 else:
1766 1766 tr = None
1767 1767
1768 1768 if tr and tr.running():
1769 1769 return tr
1770 1770 return None
1771 1771
1772 1772 def transaction(self, desc, report=None):
1773 1773 if (self.ui.configbool('devel', 'all-warnings')
1774 1774 or self.ui.configbool('devel', 'check-locks')):
1775 1775 if self._currentlock(self._lockref) is None:
1776 1776 raise error.ProgrammingError('transaction requires locking')
1777 1777 tr = self.currenttransaction()
1778 1778 if tr is not None:
1779 1779 return tr.nest(name=desc)
1780 1780
1781 1781 # abort here if the journal already exists
1782 1782 if self.svfs.exists("journal"):
1783 1783 raise error.RepoError(
1784 1784 _("abandoned transaction found"),
1785 1785 hint=_("run 'hg recover' to clean up transaction"))
1786 1786
1787 1787 idbase = "%.40f#%f" % (random.random(), time.time())
1788 1788 ha = hex(hashlib.sha1(idbase).digest())
1789 1789 txnid = 'TXN:' + ha
1790 1790 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1791 1791
1792 1792 self._writejournal(desc)
1793 1793 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1794 1794 if report:
1795 1795 rp = report
1796 1796 else:
1797 1797 rp = self.ui.warn
1798 1798 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1799 1799 # we must avoid cyclic reference between repo and transaction.
1800 1800 reporef = weakref.ref(self)
1801 1801 # Code to track tag movement
1802 1802 #
1803 1803 # Since tags are all handled as file content, it is actually quite hard
1804 1804 # to track these movement from a code perspective. So we fallback to a
1805 1805 # tracking at the repository level. One could envision to track changes
1806 1806 # to the '.hgtags' file through changegroup apply but that fails to
1807 1807 # cope with case where transaction expose new heads without changegroup
1808 1808 # being involved (eg: phase movement).
1809 1809 #
1810 1810 # For now, We gate the feature behind a flag since this likely comes
1811 1811 # with performance impacts. The current code run more often than needed
1812 1812 # and do not use caches as much as it could. The current focus is on
1813 1813 # the behavior of the feature so we disable it by default. The flag
1814 1814 # will be removed when we are happy with the performance impact.
1815 1815 #
1816 1816 # Once this feature is no longer experimental move the following
1817 1817 # documentation to the appropriate help section:
1818 1818 #
1819 1819 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1820 1820 # tags (new or changed or deleted tags). In addition the details of
1821 1821 # these changes are made available in a file at:
1822 1822 # ``REPOROOT/.hg/changes/tags.changes``.
1823 1823 # Make sure you check for HG_TAG_MOVED before reading that file as it
1824 1824 # might exist from a previous transaction even if no tag were touched
1825 1825 # in this one. Changes are recorded in a line base format::
1826 1826 #
1827 1827 # <action> <hex-node> <tag-name>\n
1828 1828 #
1829 1829 # Actions are defined as follow:
1830 1830 # "-R": tag is removed,
1831 1831 # "+A": tag is added,
1832 1832 # "-M": tag is moved (old value),
1833 1833 # "+M": tag is moved (new value),
1834 1834 tracktags = lambda x: None
1835 1835 # experimental config: experimental.hook-track-tags
1836 1836 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1837 1837 if desc != 'strip' and shouldtracktags:
1838 1838 oldheads = self.changelog.headrevs()
1839 1839 def tracktags(tr2):
1840 1840 repo = reporef()
1841 1841 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1842 1842 newheads = repo.changelog.headrevs()
1843 1843 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1844 1844 # notes: we compare lists here.
1845 1845 # As we do it only once buiding set would not be cheaper
1846 1846 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1847 1847 if changes:
1848 1848 tr2.hookargs['tag_moved'] = '1'
1849 1849 with repo.vfs('changes/tags.changes', 'w',
1850 1850 atomictemp=True) as changesfile:
1851 1851 # note: we do not register the file to the transaction
1852 1852 # because we needs it to still exist on the transaction
1853 1853 # is close (for txnclose hooks)
1854 1854 tagsmod.writediff(changesfile, changes)
1855 1855 def validate(tr2):
1856 1856 """will run pre-closing hooks"""
1857 1857 # XXX the transaction API is a bit lacking here so we take a hacky
1858 1858 # path for now
1859 1859 #
1860 1860 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1861 1861 # dict is copied before these run. In addition we needs the data
1862 1862 # available to in memory hooks too.
1863 1863 #
1864 1864 # Moreover, we also need to make sure this runs before txnclose
1865 1865 # hooks and there is no "pending" mechanism that would execute
1866 1866 # logic only if hooks are about to run.
1867 1867 #
1868 1868 # Fixing this limitation of the transaction is also needed to track
1869 1869 # other families of changes (bookmarks, phases, obsolescence).
1870 1870 #
1871 1871 # This will have to be fixed before we remove the experimental
1872 1872 # gating.
1873 1873 tracktags(tr2)
1874 1874 repo = reporef()
1875 1875 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1876 1876 scmutil.enforcesinglehead(repo, tr2, desc)
1877 1877 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1878 1878 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1879 1879 args = tr.hookargs.copy()
1880 1880 args.update(bookmarks.preparehookargs(name, old, new))
1881 1881 repo.hook('pretxnclose-bookmark', throw=True,
1882 1882 **pycompat.strkwargs(args))
1883 1883 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1884 1884 cl = repo.unfiltered().changelog
1885 1885 for rev, (old, new) in tr.changes['phases'].items():
1886 1886 args = tr.hookargs.copy()
1887 1887 node = hex(cl.node(rev))
1888 1888 args.update(phases.preparehookargs(node, old, new))
1889 1889 repo.hook('pretxnclose-phase', throw=True,
1890 1890 **pycompat.strkwargs(args))
1891 1891
1892 1892 repo.hook('pretxnclose', throw=True,
1893 1893 **pycompat.strkwargs(tr.hookargs))
1894 1894 def releasefn(tr, success):
1895 1895 repo = reporef()
1896 1896 if success:
1897 1897 # this should be explicitly invoked here, because
1898 1898 # in-memory changes aren't written out at closing
1899 1899 # transaction, if tr.addfilegenerator (via
1900 1900 # dirstate.write or so) isn't invoked while
1901 1901 # transaction running
1902 1902 repo.dirstate.write(None)
1903 1903 else:
1904 1904 # discard all changes (including ones already written
1905 1905 # out) in this transaction
1906 1906 narrowspec.restorebackup(self, 'journal.narrowspec')
1907 1907 narrowspec.restorewcbackup(self, 'journal.narrowspec.dirstate')
1908 1908 repo.dirstate.restorebackup(None, 'journal.dirstate')
1909 1909
1910 1910 repo.invalidate(clearfilecache=True)
1911 1911
1912 1912 tr = transaction.transaction(rp, self.svfs, vfsmap,
1913 1913 "journal",
1914 1914 "undo",
1915 1915 aftertrans(renames),
1916 1916 self.store.createmode,
1917 1917 validator=validate,
1918 1918 releasefn=releasefn,
1919 1919 checkambigfiles=_cachedfiles,
1920 1920 name=desc)
1921 1921 tr.changes['origrepolen'] = len(self)
1922 1922 tr.changes['obsmarkers'] = set()
1923 1923 tr.changes['phases'] = {}
1924 1924 tr.changes['bookmarks'] = {}
1925 1925
1926 1926 tr.hookargs['txnid'] = txnid
1927 1927 tr.hookargs['txnname'] = desc
1928 1928 # note: writing the fncache only during finalize mean that the file is
1929 1929 # outdated when running hooks. As fncache is used for streaming clone,
1930 1930 # this is not expected to break anything that happen during the hooks.
1931 1931 tr.addfinalize('flush-fncache', self.store.write)
1932 1932 def txnclosehook(tr2):
1933 1933 """To be run if transaction is successful, will schedule a hook run
1934 1934 """
1935 1935 # Don't reference tr2 in hook() so we don't hold a reference.
1936 1936 # This reduces memory consumption when there are multiple
1937 1937 # transactions per lock. This can likely go away if issue5045
1938 1938 # fixes the function accumulation.
1939 1939 hookargs = tr2.hookargs
1940 1940
1941 1941 def hookfunc():
1942 1942 repo = reporef()
1943 1943 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1944 1944 bmchanges = sorted(tr.changes['bookmarks'].items())
1945 1945 for name, (old, new) in bmchanges:
1946 1946 args = tr.hookargs.copy()
1947 1947 args.update(bookmarks.preparehookargs(name, old, new))
1948 1948 repo.hook('txnclose-bookmark', throw=False,
1949 1949 **pycompat.strkwargs(args))
1950 1950
1951 1951 if hook.hashook(repo.ui, 'txnclose-phase'):
1952 1952 cl = repo.unfiltered().changelog
1953 1953 phasemv = sorted(tr.changes['phases'].items())
1954 1954 for rev, (old, new) in phasemv:
1955 1955 args = tr.hookargs.copy()
1956 1956 node = hex(cl.node(rev))
1957 1957 args.update(phases.preparehookargs(node, old, new))
1958 1958 repo.hook('txnclose-phase', throw=False,
1959 1959 **pycompat.strkwargs(args))
1960 1960
1961 1961 repo.hook('txnclose', throw=False,
1962 1962 **pycompat.strkwargs(hookargs))
1963 1963 reporef()._afterlock(hookfunc)
1964 1964 tr.addfinalize('txnclose-hook', txnclosehook)
1965 1965 # Include a leading "-" to make it happen before the transaction summary
1966 1966 # reports registered via scmutil.registersummarycallback() whose names
1967 1967 # are 00-txnreport etc. That way, the caches will be warm when the
1968 1968 # callbacks run.
1969 1969 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
1970 1970 def txnaborthook(tr2):
1971 1971 """To be run if transaction is aborted
1972 1972 """
1973 1973 reporef().hook('txnabort', throw=False,
1974 1974 **pycompat.strkwargs(tr2.hookargs))
1975 1975 tr.addabort('txnabort-hook', txnaborthook)
1976 1976 # avoid eager cache invalidation. in-memory data should be identical
1977 1977 # to stored data if transaction has no error.
1978 1978 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1979 1979 self._transref = weakref.ref(tr)
1980 1980 scmutil.registersummarycallback(self, tr, desc)
1981 1981 return tr
1982 1982
1983 1983 def _journalfiles(self):
1984 1984 return ((self.svfs, 'journal'),
1985 1985 (self.svfs, 'journal.narrowspec'),
1986 1986 (self.vfs, 'journal.narrowspec.dirstate'),
1987 1987 (self.vfs, 'journal.dirstate'),
1988 1988 (self.vfs, 'journal.branch'),
1989 1989 (self.vfs, 'journal.desc'),
1990 1990 (bookmarks.bookmarksvfs(self), 'journal.bookmarks'),
1991 1991 (self.svfs, 'journal.phaseroots'))
1992 1992
1993 1993 def undofiles(self):
1994 1994 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1995 1995
1996 1996 @unfilteredmethod
1997 1997 def _writejournal(self, desc):
1998 1998 self.dirstate.savebackup(None, 'journal.dirstate')
1999 1999 narrowspec.savewcbackup(self, 'journal.narrowspec.dirstate')
2000 2000 narrowspec.savebackup(self, 'journal.narrowspec')
2001 2001 self.vfs.write("journal.branch",
2002 2002 encoding.fromlocal(self.dirstate.branch()))
2003 2003 self.vfs.write("journal.desc",
2004 2004 "%d\n%s\n" % (len(self), desc))
2005 2005 bookmarksvfs = bookmarks.bookmarksvfs(self)
2006 2006 bookmarksvfs.write("journal.bookmarks",
2007 2007 bookmarksvfs.tryread("bookmarks"))
2008 2008 self.svfs.write("journal.phaseroots",
2009 2009 self.svfs.tryread("phaseroots"))
2010 2010
2011 2011 def recover(self):
2012 2012 with self.lock():
2013 2013 if self.svfs.exists("journal"):
2014 2014 self.ui.status(_("rolling back interrupted transaction\n"))
2015 2015 vfsmap = {'': self.svfs,
2016 2016 'plain': self.vfs,}
2017 2017 transaction.rollback(self.svfs, vfsmap, "journal",
2018 2018 self.ui.warn,
2019 2019 checkambigfiles=_cachedfiles)
2020 2020 self.invalidate()
2021 2021 return True
2022 2022 else:
2023 2023 self.ui.warn(_("no interrupted transaction available\n"))
2024 2024 return False
2025 2025
2026 2026 def rollback(self, dryrun=False, force=False):
2027 2027 wlock = lock = dsguard = None
2028 2028 try:
2029 2029 wlock = self.wlock()
2030 2030 lock = self.lock()
2031 2031 if self.svfs.exists("undo"):
2032 2032 dsguard = dirstateguard.dirstateguard(self, 'rollback')
2033 2033
2034 2034 return self._rollback(dryrun, force, dsguard)
2035 2035 else:
2036 2036 self.ui.warn(_("no rollback information available\n"))
2037 2037 return 1
2038 2038 finally:
2039 2039 release(dsguard, lock, wlock)
2040 2040
2041 2041 @unfilteredmethod # Until we get smarter cache management
2042 2042 def _rollback(self, dryrun, force, dsguard):
2043 2043 ui = self.ui
2044 2044 try:
2045 2045 args = self.vfs.read('undo.desc').splitlines()
2046 2046 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2047 2047 if len(args) >= 3:
2048 2048 detail = args[2]
2049 2049 oldtip = oldlen - 1
2050 2050
2051 2051 if detail and ui.verbose:
2052 2052 msg = (_('repository tip rolled back to revision %d'
2053 2053 ' (undo %s: %s)\n')
2054 2054 % (oldtip, desc, detail))
2055 2055 else:
2056 2056 msg = (_('repository tip rolled back to revision %d'
2057 2057 ' (undo %s)\n')
2058 2058 % (oldtip, desc))
2059 2059 except IOError:
2060 2060 msg = _('rolling back unknown transaction\n')
2061 2061 desc = None
2062 2062
2063 2063 if not force and self['.'] != self['tip'] and desc == 'commit':
2064 2064 raise error.Abort(
2065 2065 _('rollback of last commit while not checked out '
2066 2066 'may lose data'), hint=_('use -f to force'))
2067 2067
2068 2068 ui.status(msg)
2069 2069 if dryrun:
2070 2070 return 0
2071 2071
2072 2072 parents = self.dirstate.parents()
2073 2073 self.destroying()
2074 2074 vfsmap = {'plain': self.vfs, '': self.svfs}
2075 2075 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
2076 2076 checkambigfiles=_cachedfiles)
2077 2077 bookmarksvfs = bookmarks.bookmarksvfs(self)
2078 2078 if bookmarksvfs.exists('undo.bookmarks'):
2079 2079 bookmarksvfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
2080 2080 if self.svfs.exists('undo.phaseroots'):
2081 2081 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
2082 2082 self.invalidate()
2083 2083
2084 2084 parentgone = any(p not in self.changelog.nodemap for p in parents)
2085 2085 if parentgone:
2086 2086 # prevent dirstateguard from overwriting already restored one
2087 2087 dsguard.close()
2088 2088
2089 2089 narrowspec.restorebackup(self, 'undo.narrowspec')
2090 2090 narrowspec.restorewcbackup(self, 'undo.narrowspec.dirstate')
2091 2091 self.dirstate.restorebackup(None, 'undo.dirstate')
2092 2092 try:
2093 2093 branch = self.vfs.read('undo.branch')
2094 2094 self.dirstate.setbranch(encoding.tolocal(branch))
2095 2095 except IOError:
2096 2096 ui.warn(_('named branch could not be reset: '
2097 2097 'current branch is still \'%s\'\n')
2098 2098 % self.dirstate.branch())
2099 2099
2100 2100 parents = tuple([p.rev() for p in self[None].parents()])
2101 2101 if len(parents) > 1:
2102 2102 ui.status(_('working directory now based on '
2103 2103 'revisions %d and %d\n') % parents)
2104 2104 else:
2105 2105 ui.status(_('working directory now based on '
2106 2106 'revision %d\n') % parents)
2107 2107 mergemod.mergestate.clean(self, self['.'].node())
2108 2108
2109 2109 # TODO: if we know which new heads may result from this rollback, pass
2110 2110 # them to destroy(), which will prevent the branchhead cache from being
2111 2111 # invalidated.
2112 2112 self.destroyed()
2113 2113 return 0
2114 2114
2115 2115 def _buildcacheupdater(self, newtransaction):
2116 2116 """called during transaction to build the callback updating cache
2117 2117
2118 2118 Lives on the repository to help extension who might want to augment
2119 2119 this logic. For this purpose, the created transaction is passed to the
2120 2120 method.
2121 2121 """
2122 2122 # we must avoid cyclic reference between repo and transaction.
2123 2123 reporef = weakref.ref(self)
2124 2124 def updater(tr):
2125 2125 repo = reporef()
2126 2126 repo.updatecaches(tr)
2127 2127 return updater
2128 2128
2129 2129 @unfilteredmethod
2130 2130 def updatecaches(self, tr=None, full=False):
2131 2131 """warm appropriate caches
2132 2132
2133 2133 If this function is called after a transaction closed. The transaction
2134 2134 will be available in the 'tr' argument. This can be used to selectively
2135 2135 update caches relevant to the changes in that transaction.
2136 2136
2137 2137 If 'full' is set, make sure all caches the function knows about have
2138 2138 up-to-date data. Even the ones usually loaded more lazily.
2139 2139 """
2140 2140 if tr is not None and tr.hookargs.get('source') == 'strip':
2141 2141 # During strip, many caches are invalid but
2142 2142 # later call to `destroyed` will refresh them.
2143 2143 return
2144 2144
2145 2145 if tr is None or tr.changes['origrepolen'] < len(self):
2146 2146 # accessing the 'ser ved' branchmap should refresh all the others,
2147 2147 self.ui.debug('updating the branch cache\n')
2148 2148 self.filtered('served').branchmap()
2149 2149 self.filtered('served.hidden').branchmap()
2150 2150
2151 2151 if full:
2152 2152 unfi = self.unfiltered()
2153 2153 rbc = unfi.revbranchcache()
2154 2154 for r in unfi.changelog:
2155 2155 rbc.branchinfo(r)
2156 2156 rbc.write()
2157 2157
2158 2158 # ensure the working copy parents are in the manifestfulltextcache
2159 2159 for ctx in self['.'].parents():
2160 2160 ctx.manifest() # accessing the manifest is enough
2161 2161
2162 2162 # accessing fnode cache warms the cache
2163 2163 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2164 2164 # accessing tags warm the cache
2165 2165 self.tags()
2166 2166 self.filtered('served').tags()
2167 2167
2168 2168 def invalidatecaches(self):
2169 2169
2170 2170 if r'_tagscache' in vars(self):
2171 2171 # can't use delattr on proxy
2172 2172 del self.__dict__[r'_tagscache']
2173 2173
2174 2174 self._branchcaches.clear()
2175 2175 self.invalidatevolatilesets()
2176 2176 self._sparsesignaturecache.clear()
2177 2177
2178 2178 def invalidatevolatilesets(self):
2179 2179 self.filteredrevcache.clear()
2180 2180 obsolete.clearobscaches(self)
2181 2181
2182 2182 def invalidatedirstate(self):
2183 2183 '''Invalidates the dirstate, causing the next call to dirstate
2184 2184 to check if it was modified since the last time it was read,
2185 2185 rereading it if it has.
2186 2186
2187 2187 This is different to dirstate.invalidate() that it doesn't always
2188 2188 rereads the dirstate. Use dirstate.invalidate() if you want to
2189 2189 explicitly read the dirstate again (i.e. restoring it to a previous
2190 2190 known good state).'''
2191 2191 if hasunfilteredcache(self, r'dirstate'):
2192 2192 for k in self.dirstate._filecache:
2193 2193 try:
2194 2194 delattr(self.dirstate, k)
2195 2195 except AttributeError:
2196 2196 pass
2197 2197 delattr(self.unfiltered(), r'dirstate')
2198 2198
2199 2199 def invalidate(self, clearfilecache=False):
2200 2200 '''Invalidates both store and non-store parts other than dirstate
2201 2201
2202 2202 If a transaction is running, invalidation of store is omitted,
2203 2203 because discarding in-memory changes might cause inconsistency
2204 2204 (e.g. incomplete fncache causes unintentional failure, but
2205 2205 redundant one doesn't).
2206 2206 '''
2207 2207 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2208 2208 for k in list(self._filecache.keys()):
2209 2209 # dirstate is invalidated separately in invalidatedirstate()
2210 2210 if k == 'dirstate':
2211 2211 continue
2212 2212 if (k == 'changelog' and
2213 2213 self.currenttransaction() and
2214 2214 self.changelog._delayed):
2215 2215 # The changelog object may store unwritten revisions. We don't
2216 2216 # want to lose them.
2217 2217 # TODO: Solve the problem instead of working around it.
2218 2218 continue
2219 2219
2220 2220 if clearfilecache:
2221 2221 del self._filecache[k]
2222 2222 try:
2223 2223 delattr(unfiltered, k)
2224 2224 except AttributeError:
2225 2225 pass
2226 2226 self.invalidatecaches()
2227 2227 if not self.currenttransaction():
2228 2228 # TODO: Changing contents of store outside transaction
2229 2229 # causes inconsistency. We should make in-memory store
2230 2230 # changes detectable, and abort if changed.
2231 2231 self.store.invalidatecaches()
2232 2232
2233 2233 def invalidateall(self):
2234 2234 '''Fully invalidates both store and non-store parts, causing the
2235 2235 subsequent operation to reread any outside changes.'''
2236 2236 # extension should hook this to invalidate its caches
2237 2237 self.invalidate()
2238 2238 self.invalidatedirstate()
2239 2239
2240 2240 @unfilteredmethod
2241 2241 def _refreshfilecachestats(self, tr):
2242 2242 """Reload stats of cached files so that they are flagged as valid"""
2243 2243 for k, ce in self._filecache.items():
2244 2244 k = pycompat.sysstr(k)
2245 2245 if k == r'dirstate' or k not in self.__dict__:
2246 2246 continue
2247 2247 ce.refresh()
2248 2248
2249 2249 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2250 2250 inheritchecker=None, parentenvvar=None):
2251 2251 parentlock = None
2252 2252 # the contents of parentenvvar are used by the underlying lock to
2253 2253 # determine whether it can be inherited
2254 2254 if parentenvvar is not None:
2255 2255 parentlock = encoding.environ.get(parentenvvar)
2256 2256
2257 2257 timeout = 0
2258 2258 warntimeout = 0
2259 2259 if wait:
2260 2260 timeout = self.ui.configint("ui", "timeout")
2261 2261 warntimeout = self.ui.configint("ui", "timeout.warn")
2262 2262 # internal config: ui.signal-safe-lock
2263 2263 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2264 2264
2265 2265 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2266 2266 releasefn=releasefn,
2267 2267 acquirefn=acquirefn, desc=desc,
2268 2268 inheritchecker=inheritchecker,
2269 2269 parentlock=parentlock,
2270 2270 signalsafe=signalsafe)
2271 2271 return l
2272 2272
2273 2273 def _afterlock(self, callback):
2274 2274 """add a callback to be run when the repository is fully unlocked
2275 2275
2276 2276 The callback will be executed when the outermost lock is released
2277 2277 (with wlock being higher level than 'lock')."""
2278 2278 for ref in (self._wlockref, self._lockref):
2279 2279 l = ref and ref()
2280 2280 if l and l.held:
2281 2281 l.postrelease.append(callback)
2282 2282 break
2283 2283 else: # no lock have been found.
2284 2284 callback()
2285 2285
2286 2286 def lock(self, wait=True):
2287 2287 '''Lock the repository store (.hg/store) and return a weak reference
2288 2288 to the lock. Use this before modifying the store (e.g. committing or
2289 2289 stripping). If you are opening a transaction, get a lock as well.)
2290 2290
2291 2291 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2292 2292 'wlock' first to avoid a dead-lock hazard.'''
2293 2293 l = self._currentlock(self._lockref)
2294 2294 if l is not None:
2295 2295 l.lock()
2296 2296 return l
2297 2297
2298 2298 l = self._lock(vfs=self.svfs,
2299 2299 lockname="lock",
2300 2300 wait=wait,
2301 2301 releasefn=None,
2302 2302 acquirefn=self.invalidate,
2303 2303 desc=_('repository %s') % self.origroot)
2304 2304 self._lockref = weakref.ref(l)
2305 2305 return l
2306 2306
2307 2307 def _wlockchecktransaction(self):
2308 2308 if self.currenttransaction() is not None:
2309 2309 raise error.LockInheritanceContractViolation(
2310 2310 'wlock cannot be inherited in the middle of a transaction')
2311 2311
2312 2312 def wlock(self, wait=True):
2313 2313 '''Lock the non-store parts of the repository (everything under
2314 2314 .hg except .hg/store) and return a weak reference to the lock.
2315 2315
2316 2316 Use this before modifying files in .hg.
2317 2317
2318 2318 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2319 2319 'wlock' first to avoid a dead-lock hazard.'''
2320 2320 l = self._wlockref and self._wlockref()
2321 2321 if l is not None and l.held:
2322 2322 l.lock()
2323 2323 return l
2324 2324
2325 2325 # We do not need to check for non-waiting lock acquisition. Such
2326 2326 # acquisition would not cause dead-lock as they would just fail.
2327 2327 if wait and (self.ui.configbool('devel', 'all-warnings')
2328 2328 or self.ui.configbool('devel', 'check-locks')):
2329 2329 if self._currentlock(self._lockref) is not None:
2330 2330 self.ui.develwarn('"wlock" acquired after "lock"')
2331 2331
2332 2332 def unlock():
2333 2333 if self.dirstate.pendingparentchange():
2334 2334 self.dirstate.invalidate()
2335 2335 else:
2336 2336 self.dirstate.write(None)
2337 2337
2338 2338 self._filecache['dirstate'].refresh()
2339 2339
2340 2340 l = self._lock(self.vfs, "wlock", wait, unlock,
2341 2341 self.invalidatedirstate, _('working directory of %s') %
2342 2342 self.origroot,
2343 2343 inheritchecker=self._wlockchecktransaction,
2344 2344 parentenvvar='HG_WLOCK_LOCKER')
2345 2345 self._wlockref = weakref.ref(l)
2346 2346 return l
2347 2347
2348 2348 def _currentlock(self, lockref):
2349 2349 """Returns the lock if it's held, or None if it's not."""
2350 2350 if lockref is None:
2351 2351 return None
2352 2352 l = lockref()
2353 2353 if l is None or not l.held:
2354 2354 return None
2355 2355 return l
2356 2356
2357 2357 def currentwlock(self):
2358 2358 """Returns the wlock if it's held, or None if it's not."""
2359 2359 return self._currentlock(self._wlockref)
2360 2360
2361 2361 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist,
2362 2362 includecopymeta):
2363 2363 """
2364 2364 commit an individual file as part of a larger transaction
2365 2365 """
2366 2366
2367 2367 fname = fctx.path()
2368 2368 fparent1 = manifest1.get(fname, nullid)
2369 2369 fparent2 = manifest2.get(fname, nullid)
2370 2370 if isinstance(fctx, context.filectx):
2371 2371 node = fctx.filenode()
2372 2372 if node in [fparent1, fparent2]:
2373 2373 self.ui.debug('reusing %s filelog entry\n' % fname)
2374 2374 if ((fparent1 != nullid and
2375 2375 manifest1.flags(fname) != fctx.flags()) or
2376 2376 (fparent2 != nullid and
2377 2377 manifest2.flags(fname) != fctx.flags())):
2378 2378 changelist.append(fname)
2379 2379 return node
2380 2380
2381 2381 flog = self.file(fname)
2382 2382 meta = {}
2383 2383 cfname = fctx.copysource()
2384 2384 if cfname and cfname != fname:
2385 2385 # Mark the new revision of this file as a copy of another
2386 2386 # file. This copy data will effectively act as a parent
2387 2387 # of this new revision. If this is a merge, the first
2388 2388 # parent will be the nullid (meaning "look up the copy data")
2389 2389 # and the second one will be the other parent. For example:
2390 2390 #
2391 2391 # 0 --- 1 --- 3 rev1 changes file foo
2392 2392 # \ / rev2 renames foo to bar and changes it
2393 2393 # \- 2 -/ rev3 should have bar with all changes and
2394 2394 # should record that bar descends from
2395 2395 # bar in rev2 and foo in rev1
2396 2396 #
2397 2397 # this allows this merge to succeed:
2398 2398 #
2399 2399 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2400 2400 # \ / merging rev3 and rev4 should use bar@rev2
2401 2401 # \- 2 --- 4 as the merge base
2402 2402 #
2403 2403
2404 2404 cnode = manifest1.get(cfname)
2405 2405 newfparent = fparent2
2406 2406
2407 2407 if manifest2: # branch merge
2408 2408 if fparent2 == nullid or cnode is None: # copied on remote side
2409 2409 if cfname in manifest2:
2410 2410 cnode = manifest2[cfname]
2411 2411 newfparent = fparent1
2412 2412
2413 2413 # Here, we used to search backwards through history to try to find
2414 2414 # where the file copy came from if the source of a copy was not in
2415 2415 # the parent directory. However, this doesn't actually make sense to
2416 2416 # do (what does a copy from something not in your working copy even
2417 2417 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2418 2418 # the user that copy information was dropped, so if they didn't
2419 2419 # expect this outcome it can be fixed, but this is the correct
2420 2420 # behavior in this circumstance.
2421 2421
2422 2422 if cnode:
2423 2423 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
2424 2424 if includecopymeta:
2425 2425 meta["copy"] = cfname
2426 2426 meta["copyrev"] = hex(cnode)
2427 2427 fparent1, fparent2 = nullid, newfparent
2428 2428 else:
2429 2429 self.ui.warn(_("warning: can't find ancestor for '%s' "
2430 2430 "copied from '%s'!\n") % (fname, cfname))
2431 2431
2432 2432 elif fparent1 == nullid:
2433 2433 fparent1, fparent2 = fparent2, nullid
2434 2434 elif fparent2 != nullid:
2435 2435 # is one parent an ancestor of the other?
2436 2436 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2437 2437 if fparent1 in fparentancestors:
2438 2438 fparent1, fparent2 = fparent2, nullid
2439 2439 elif fparent2 in fparentancestors:
2440 2440 fparent2 = nullid
2441 2441
2442 2442 # is the file changed?
2443 2443 text = fctx.data()
2444 2444 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2445 2445 changelist.append(fname)
2446 2446 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2447 2447 # are just the flags changed during merge?
2448 2448 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2449 2449 changelist.append(fname)
2450 2450
2451 2451 return fparent1
2452 2452
2453 2453 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2454 2454 """check for commit arguments that aren't committable"""
2455 2455 if match.isexact() or match.prefix():
2456 2456 matched = set(status.modified + status.added + status.removed)
2457 2457
2458 2458 for f in match.files():
2459 2459 f = self.dirstate.normalize(f)
2460 2460 if f == '.' or f in matched or f in wctx.substate:
2461 2461 continue
2462 2462 if f in status.deleted:
2463 2463 fail(f, _('file not found!'))
2464 2464 if f in vdirs: # visited directory
2465 2465 d = f + '/'
2466 2466 for mf in matched:
2467 2467 if mf.startswith(d):
2468 2468 break
2469 2469 else:
2470 2470 fail(f, _("no match under directory!"))
2471 2471 elif f not in self.dirstate:
2472 2472 fail(f, _("file not tracked!"))
2473 2473
2474 2474 @unfilteredmethod
2475 2475 def commit(self, text="", user=None, date=None, match=None, force=False,
2476 2476 editor=False, extra=None):
2477 2477 """Add a new revision to current repository.
2478 2478
2479 2479 Revision information is gathered from the working directory,
2480 2480 match can be used to filter the committed files. If editor is
2481 2481 supplied, it is called to get a commit message.
2482 2482 """
2483 2483 if extra is None:
2484 2484 extra = {}
2485 2485
2486 2486 def fail(f, msg):
2487 2487 raise error.Abort('%s: %s' % (f, msg))
2488 2488
2489 2489 if not match:
2490 2490 match = matchmod.always()
2491 2491
2492 2492 if not force:
2493 2493 vdirs = []
2494 2494 match.explicitdir = vdirs.append
2495 2495 match.bad = fail
2496 2496
2497 2497 # lock() for recent changelog (see issue4368)
2498 2498 with self.wlock(), self.lock():
2499 2499 wctx = self[None]
2500 2500 merge = len(wctx.parents()) > 1
2501 2501
2502 2502 if not force and merge and not match.always():
2503 2503 raise error.Abort(_('cannot partially commit a merge '
2504 2504 '(do not specify files or patterns)'))
2505 2505
2506 2506 status = self.status(match=match, clean=force)
2507 2507 if force:
2508 2508 status.modified.extend(status.clean) # mq may commit clean files
2509 2509
2510 2510 # check subrepos
2511 2511 subs, commitsubs, newstate = subrepoutil.precommit(
2512 2512 self.ui, wctx, status, match, force=force)
2513 2513
2514 2514 # make sure all explicit patterns are matched
2515 2515 if not force:
2516 2516 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2517 2517
2518 2518 cctx = context.workingcommitctx(self, status,
2519 2519 text, user, date, extra)
2520 2520
2521 2521 # internal config: ui.allowemptycommit
2522 2522 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2523 2523 or extra.get('close') or merge or cctx.files()
2524 2524 or self.ui.configbool('ui', 'allowemptycommit'))
2525 2525 if not allowemptycommit:
2526 2526 return None
2527 2527
2528 2528 if merge and cctx.deleted():
2529 2529 raise error.Abort(_("cannot commit merge with missing files"))
2530 2530
2531 2531 ms = mergemod.mergestate.read(self)
2532 2532 mergeutil.checkunresolved(ms)
2533 2533
2534 2534 if editor:
2535 2535 cctx._text = editor(self, cctx, subs)
2536 2536 edited = (text != cctx._text)
2537 2537
2538 2538 # Save commit message in case this transaction gets rolled back
2539 2539 # (e.g. by a pretxncommit hook). Leave the content alone on
2540 2540 # the assumption that the user will use the same editor again.
2541 2541 msgfn = self.savecommitmessage(cctx._text)
2542 2542
2543 2543 # commit subs and write new state
2544 2544 if subs:
2545 2545 uipathfn = scmutil.getuipathfn(self)
2546 2546 for s in sorted(commitsubs):
2547 2547 sub = wctx.sub(s)
2548 2548 self.ui.status(_('committing subrepository %s\n') %
2549 2549 uipathfn(subrepoutil.subrelpath(sub)))
2550 2550 sr = sub.commit(cctx._text, user, date)
2551 2551 newstate[s] = (newstate[s][0], sr)
2552 2552 subrepoutil.writestate(self, newstate)
2553 2553
2554 2554 p1, p2 = self.dirstate.parents()
2555 2555 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2556 2556 try:
2557 2557 self.hook("precommit", throw=True, parent1=hookp1,
2558 2558 parent2=hookp2)
2559 2559 with self.transaction('commit'):
2560 2560 ret = self.commitctx(cctx, True)
2561 2561 # update bookmarks, dirstate and mergestate
2562 2562 bookmarks.update(self, [p1, p2], ret)
2563 2563 cctx.markcommitted(ret)
2564 2564 ms.reset()
2565 2565 except: # re-raises
2566 2566 if edited:
2567 2567 self.ui.write(
2568 2568 _('note: commit message saved in %s\n') % msgfn)
2569 2569 raise
2570 2570
2571 2571 def commithook():
2572 2572 # hack for command that use a temporary commit (eg: histedit)
2573 2573 # temporary commit got stripped before hook release
2574 2574 if self.changelog.hasnode(ret):
2575 2575 self.hook("commit", node=hex(ret), parent1=hookp1,
2576 2576 parent2=hookp2)
2577 2577 self._afterlock(commithook)
2578 2578 return ret
2579 2579
2580 2580 @unfilteredmethod
2581 def commitctx(self, ctx, error=False):
2581 def commitctx(self, ctx, error=False, origctx=None):
2582 2582 """Add a new revision to current repository.
2583 2583 Revision information is passed via the context argument.
2584 2584
2585 2585 ctx.files() should list all files involved in this commit, i.e.
2586 2586 modified/added/removed files. On merge, it may be wider than the
2587 2587 ctx.files() to be committed, since any file nodes derived directly
2588 2588 from p1 or p2 are excluded from the committed ctx.files().
2589
2590 origctx is for convert to work around the problem that bug
2591 fixes to the files list in changesets change hashes. For
2592 convert to be the identity, it can pass an origctx and this
2593 function will use the same files list when it makes sense to
2594 do so.
2589 2595 """
2590 2596
2591 2597 p1, p2 = ctx.p1(), ctx.p2()
2592 2598 user = ctx.user()
2593 2599
2594 2600 writecopiesto = self.ui.config('experimental', 'copies.write-to')
2595 2601 writefilecopymeta = writecopiesto != 'changeset-only'
2596 2602 writechangesetcopy = (writecopiesto in
2597 2603 ('changeset-only', 'compatibility'))
2598 2604 p1copies, p2copies = None, None
2599 2605 if writechangesetcopy:
2600 2606 p1copies = ctx.p1copies()
2601 2607 p2copies = ctx.p2copies()
2602 2608 filesadded, filesremoved = None, None
2603 2609 with self.lock(), self.transaction("commit") as tr:
2604 2610 trp = weakref.proxy(tr)
2605 2611
2606 2612 if ctx.manifestnode():
2607 2613 # reuse an existing manifest revision
2608 2614 self.ui.debug('reusing known manifest\n')
2609 2615 mn = ctx.manifestnode()
2610 2616 files = ctx.files()
2611 2617 if writechangesetcopy:
2612 2618 filesadded = ctx.filesadded()
2613 2619 filesremoved = ctx.filesremoved()
2614 2620 elif ctx.files():
2615 2621 m1ctx = p1.manifestctx()
2616 2622 m2ctx = p2.manifestctx()
2617 2623 mctx = m1ctx.copy()
2618 2624
2619 2625 m = mctx.read()
2620 2626 m1 = m1ctx.read()
2621 2627 m2 = m2ctx.read()
2622 2628
2623 2629 # check in files
2624 2630 added = []
2625 2631 changed = []
2626 2632 removed = list(ctx.removed())
2627 2633 linkrev = len(self)
2628 2634 self.ui.note(_("committing files:\n"))
2629 2635 uipathfn = scmutil.getuipathfn(self)
2630 2636 for f in sorted(ctx.modified() + ctx.added()):
2631 2637 self.ui.note(uipathfn(f) + "\n")
2632 2638 try:
2633 2639 fctx = ctx[f]
2634 2640 if fctx is None:
2635 2641 removed.append(f)
2636 2642 else:
2637 2643 added.append(f)
2638 2644 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2639 2645 trp, changed,
2640 2646 writefilecopymeta)
2641 2647 m.setflag(f, fctx.flags())
2642 2648 except OSError:
2643 2649 self.ui.warn(_("trouble committing %s!\n") %
2644 2650 uipathfn(f))
2645 2651 raise
2646 2652 except IOError as inst:
2647 2653 errcode = getattr(inst, 'errno', errno.ENOENT)
2648 2654 if error or errcode and errcode != errno.ENOENT:
2649 2655 self.ui.warn(_("trouble committing %s!\n") %
2650 2656 uipathfn(f))
2651 2657 raise
2652 2658
2653 2659 # update manifest
2654 2660 removed = [f for f in removed if f in m1 or f in m2]
2655 2661 drop = sorted([f for f in removed if f in m])
2656 2662 for f in drop:
2657 2663 del m[f]
2658 2664 files = changed + removed
2659 2665 md = None
2660 2666 if not files:
2661 2667 # if no "files" actually changed in terms of the changelog,
2662 2668 # try hard to detect unmodified manifest entry so that the
2663 2669 # exact same commit can be reproduced later on convert.
2664 2670 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2665 2671 if not files and md:
2666 2672 self.ui.debug('not reusing manifest (no file change in '
2667 2673 'changelog, but manifest differs)\n')
2668 2674 if files or md:
2669 2675 self.ui.note(_("committing manifest\n"))
2670 2676 # we're using narrowmatch here since it's already applied at
2671 2677 # other stages (such as dirstate.walk), so we're already
2672 2678 # ignoring things outside of narrowspec in most cases. The
2673 2679 # one case where we might have files outside the narrowspec
2674 2680 # at this point is merges, and we already error out in the
2675 2681 # case where the merge has files outside of the narrowspec,
2676 2682 # so this is safe.
2677 2683 mn = mctx.write(trp, linkrev,
2678 2684 p1.manifestnode(), p2.manifestnode(),
2679 2685 added, drop, match=self.narrowmatch())
2680 2686
2681 2687 if writechangesetcopy:
2682 2688 filesadded = [f for f in changed
2683 2689 if not (f in m1 or f in m2)]
2684 2690 filesremoved = removed
2685 2691 else:
2686 2692 self.ui.debug('reusing manifest from p1 (listed files '
2687 2693 'actually unchanged)\n')
2688 2694 mn = p1.manifestnode()
2689 2695 else:
2690 2696 self.ui.debug('reusing manifest from p1 (no file change)\n')
2691 2697 mn = p1.manifestnode()
2692 2698 files = []
2693 2699
2694 2700 if writecopiesto == 'changeset-only':
2695 2701 # If writing only to changeset extras, use None to indicate that
2696 2702 # no entry should be written. If writing to both, write an empty
2697 2703 # entry to prevent the reader from falling back to reading
2698 2704 # filelogs.
2699 2705 p1copies = p1copies or None
2700 2706 p2copies = p2copies or None
2701 2707 filesadded = filesadded or None
2702 2708 filesremoved = filesremoved or None
2703 2709
2710 if origctx and origctx.manifestnode() == mn:
2711 files = origctx.files()
2712
2704 2713 # update changelog
2705 2714 self.ui.note(_("committing changelog\n"))
2706 2715 self.changelog.delayupdate(tr)
2707 2716 n = self.changelog.add(mn, files, ctx.description(),
2708 2717 trp, p1.node(), p2.node(),
2709 2718 user, ctx.date(), ctx.extra().copy(),
2710 2719 p1copies, p2copies, filesadded, filesremoved)
2711 2720 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2712 2721 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2713 2722 parent2=xp2)
2714 2723 # set the new commit is proper phase
2715 2724 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2716 2725 if targetphase:
2717 2726 # retract boundary do not alter parent changeset.
2718 2727 # if a parent have higher the resulting phase will
2719 2728 # be compliant anyway
2720 2729 #
2721 2730 # if minimal phase was 0 we don't need to retract anything
2722 2731 phases.registernew(self, tr, targetphase, [n])
2723 2732 return n
2724 2733
2725 2734 @unfilteredmethod
2726 2735 def destroying(self):
2727 2736 '''Inform the repository that nodes are about to be destroyed.
2728 2737 Intended for use by strip and rollback, so there's a common
2729 2738 place for anything that has to be done before destroying history.
2730 2739
2731 2740 This is mostly useful for saving state that is in memory and waiting
2732 2741 to be flushed when the current lock is released. Because a call to
2733 2742 destroyed is imminent, the repo will be invalidated causing those
2734 2743 changes to stay in memory (waiting for the next unlock), or vanish
2735 2744 completely.
2736 2745 '''
2737 2746 # When using the same lock to commit and strip, the phasecache is left
2738 2747 # dirty after committing. Then when we strip, the repo is invalidated,
2739 2748 # causing those changes to disappear.
2740 2749 if '_phasecache' in vars(self):
2741 2750 self._phasecache.write()
2742 2751
2743 2752 @unfilteredmethod
2744 2753 def destroyed(self):
2745 2754 '''Inform the repository that nodes have been destroyed.
2746 2755 Intended for use by strip and rollback, so there's a common
2747 2756 place for anything that has to be done after destroying history.
2748 2757 '''
2749 2758 # When one tries to:
2750 2759 # 1) destroy nodes thus calling this method (e.g. strip)
2751 2760 # 2) use phasecache somewhere (e.g. commit)
2752 2761 #
2753 2762 # then 2) will fail because the phasecache contains nodes that were
2754 2763 # removed. We can either remove phasecache from the filecache,
2755 2764 # causing it to reload next time it is accessed, or simply filter
2756 2765 # the removed nodes now and write the updated cache.
2757 2766 self._phasecache.filterunknown(self)
2758 2767 self._phasecache.write()
2759 2768
2760 2769 # refresh all repository caches
2761 2770 self.updatecaches()
2762 2771
2763 2772 # Ensure the persistent tag cache is updated. Doing it now
2764 2773 # means that the tag cache only has to worry about destroyed
2765 2774 # heads immediately after a strip/rollback. That in turn
2766 2775 # guarantees that "cachetip == currenttip" (comparing both rev
2767 2776 # and node) always means no nodes have been added or destroyed.
2768 2777
2769 2778 # XXX this is suboptimal when qrefresh'ing: we strip the current
2770 2779 # head, refresh the tag cache, then immediately add a new head.
2771 2780 # But I think doing it this way is necessary for the "instant
2772 2781 # tag cache retrieval" case to work.
2773 2782 self.invalidate()
2774 2783
2775 2784 def status(self, node1='.', node2=None, match=None,
2776 2785 ignored=False, clean=False, unknown=False,
2777 2786 listsubrepos=False):
2778 2787 '''a convenience method that calls node1.status(node2)'''
2779 2788 return self[node1].status(node2, match, ignored, clean, unknown,
2780 2789 listsubrepos)
2781 2790
2782 2791 def addpostdsstatus(self, ps):
2783 2792 """Add a callback to run within the wlock, at the point at which status
2784 2793 fixups happen.
2785 2794
2786 2795 On status completion, callback(wctx, status) will be called with the
2787 2796 wlock held, unless the dirstate has changed from underneath or the wlock
2788 2797 couldn't be grabbed.
2789 2798
2790 2799 Callbacks should not capture and use a cached copy of the dirstate --
2791 2800 it might change in the meanwhile. Instead, they should access the
2792 2801 dirstate via wctx.repo().dirstate.
2793 2802
2794 2803 This list is emptied out after each status run -- extensions should
2795 2804 make sure it adds to this list each time dirstate.status is called.
2796 2805 Extensions should also make sure they don't call this for statuses
2797 2806 that don't involve the dirstate.
2798 2807 """
2799 2808
2800 2809 # The list is located here for uniqueness reasons -- it is actually
2801 2810 # managed by the workingctx, but that isn't unique per-repo.
2802 2811 self._postdsstatus.append(ps)
2803 2812
2804 2813 def postdsstatus(self):
2805 2814 """Used by workingctx to get the list of post-dirstate-status hooks."""
2806 2815 return self._postdsstatus
2807 2816
2808 2817 def clearpostdsstatus(self):
2809 2818 """Used by workingctx to clear post-dirstate-status hooks."""
2810 2819 del self._postdsstatus[:]
2811 2820
2812 2821 def heads(self, start=None):
2813 2822 if start is None:
2814 2823 cl = self.changelog
2815 2824 headrevs = reversed(cl.headrevs())
2816 2825 return [cl.node(rev) for rev in headrevs]
2817 2826
2818 2827 heads = self.changelog.heads(start)
2819 2828 # sort the output in rev descending order
2820 2829 return sorted(heads, key=self.changelog.rev, reverse=True)
2821 2830
2822 2831 def branchheads(self, branch=None, start=None, closed=False):
2823 2832 '''return a (possibly filtered) list of heads for the given branch
2824 2833
2825 2834 Heads are returned in topological order, from newest to oldest.
2826 2835 If branch is None, use the dirstate branch.
2827 2836 If start is not None, return only heads reachable from start.
2828 2837 If closed is True, return heads that are marked as closed as well.
2829 2838 '''
2830 2839 if branch is None:
2831 2840 branch = self[None].branch()
2832 2841 branches = self.branchmap()
2833 2842 if not branches.hasbranch(branch):
2834 2843 return []
2835 2844 # the cache returns heads ordered lowest to highest
2836 2845 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2837 2846 if start is not None:
2838 2847 # filter out the heads that cannot be reached from startrev
2839 2848 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2840 2849 bheads = [h for h in bheads if h in fbheads]
2841 2850 return bheads
2842 2851
2843 2852 def branches(self, nodes):
2844 2853 if not nodes:
2845 2854 nodes = [self.changelog.tip()]
2846 2855 b = []
2847 2856 for n in nodes:
2848 2857 t = n
2849 2858 while True:
2850 2859 p = self.changelog.parents(n)
2851 2860 if p[1] != nullid or p[0] == nullid:
2852 2861 b.append((t, n, p[0], p[1]))
2853 2862 break
2854 2863 n = p[0]
2855 2864 return b
2856 2865
2857 2866 def between(self, pairs):
2858 2867 r = []
2859 2868
2860 2869 for top, bottom in pairs:
2861 2870 n, l, i = top, [], 0
2862 2871 f = 1
2863 2872
2864 2873 while n != bottom and n != nullid:
2865 2874 p = self.changelog.parents(n)[0]
2866 2875 if i == f:
2867 2876 l.append(n)
2868 2877 f = f * 2
2869 2878 n = p
2870 2879 i += 1
2871 2880
2872 2881 r.append(l)
2873 2882
2874 2883 return r
2875 2884
2876 2885 def checkpush(self, pushop):
2877 2886 """Extensions can override this function if additional checks have
2878 2887 to be performed before pushing, or call it if they override push
2879 2888 command.
2880 2889 """
2881 2890
2882 2891 @unfilteredpropertycache
2883 2892 def prepushoutgoinghooks(self):
2884 2893 """Return util.hooks consists of a pushop with repo, remote, outgoing
2885 2894 methods, which are called before pushing changesets.
2886 2895 """
2887 2896 return util.hooks()
2888 2897
2889 2898 def pushkey(self, namespace, key, old, new):
2890 2899 try:
2891 2900 tr = self.currenttransaction()
2892 2901 hookargs = {}
2893 2902 if tr is not None:
2894 2903 hookargs.update(tr.hookargs)
2895 2904 hookargs = pycompat.strkwargs(hookargs)
2896 2905 hookargs[r'namespace'] = namespace
2897 2906 hookargs[r'key'] = key
2898 2907 hookargs[r'old'] = old
2899 2908 hookargs[r'new'] = new
2900 2909 self.hook('prepushkey', throw=True, **hookargs)
2901 2910 except error.HookAbort as exc:
2902 2911 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2903 2912 if exc.hint:
2904 2913 self.ui.write_err(_("(%s)\n") % exc.hint)
2905 2914 return False
2906 2915 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2907 2916 ret = pushkey.push(self, namespace, key, old, new)
2908 2917 def runhook():
2909 2918 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2910 2919 ret=ret)
2911 2920 self._afterlock(runhook)
2912 2921 return ret
2913 2922
2914 2923 def listkeys(self, namespace):
2915 2924 self.hook('prelistkeys', throw=True, namespace=namespace)
2916 2925 self.ui.debug('listing keys for "%s"\n' % namespace)
2917 2926 values = pushkey.list(self, namespace)
2918 2927 self.hook('listkeys', namespace=namespace, values=values)
2919 2928 return values
2920 2929
2921 2930 def debugwireargs(self, one, two, three=None, four=None, five=None):
2922 2931 '''used to test argument passing over the wire'''
2923 2932 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
2924 2933 pycompat.bytestr(four),
2925 2934 pycompat.bytestr(five))
2926 2935
2927 2936 def savecommitmessage(self, text):
2928 2937 fp = self.vfs('last-message.txt', 'wb')
2929 2938 try:
2930 2939 fp.write(text)
2931 2940 finally:
2932 2941 fp.close()
2933 2942 return self.pathto(fp.name[len(self.root) + 1:])
2934 2943
2935 2944 # used to avoid circular references so destructors work
2936 2945 def aftertrans(files):
2937 2946 renamefiles = [tuple(t) for t in files]
2938 2947 def a():
2939 2948 for vfs, src, dest in renamefiles:
2940 2949 # if src and dest refer to a same file, vfs.rename is a no-op,
2941 2950 # leaving both src and dest on disk. delete dest to make sure
2942 2951 # the rename couldn't be such a no-op.
2943 2952 vfs.tryunlink(dest)
2944 2953 try:
2945 2954 vfs.rename(src, dest)
2946 2955 except OSError: # journal file does not yet exist
2947 2956 pass
2948 2957 return a
2949 2958
2950 2959 def undoname(fn):
2951 2960 base, name = os.path.split(fn)
2952 2961 assert name.startswith('journal')
2953 2962 return os.path.join(base, name.replace('journal', 'undo', 1))
2954 2963
2955 2964 def instance(ui, path, create, intents=None, createopts=None):
2956 2965 localpath = util.urllocalpath(path)
2957 2966 if create:
2958 2967 createrepository(ui, localpath, createopts=createopts)
2959 2968
2960 2969 return makelocalrepository(ui, localpath, intents=intents)
2961 2970
2962 2971 def islocal(path):
2963 2972 return True
2964 2973
2965 2974 def defaultcreateopts(ui, createopts=None):
2966 2975 """Populate the default creation options for a repository.
2967 2976
2968 2977 A dictionary of explicitly requested creation options can be passed
2969 2978 in. Missing keys will be populated.
2970 2979 """
2971 2980 createopts = dict(createopts or {})
2972 2981
2973 2982 if 'backend' not in createopts:
2974 2983 # experimental config: storage.new-repo-backend
2975 2984 createopts['backend'] = ui.config('storage', 'new-repo-backend')
2976 2985
2977 2986 return createopts
2978 2987
2979 2988 def newreporequirements(ui, createopts):
2980 2989 """Determine the set of requirements for a new local repository.
2981 2990
2982 2991 Extensions can wrap this function to specify custom requirements for
2983 2992 new repositories.
2984 2993 """
2985 2994 # If the repo is being created from a shared repository, we copy
2986 2995 # its requirements.
2987 2996 if 'sharedrepo' in createopts:
2988 2997 requirements = set(createopts['sharedrepo'].requirements)
2989 2998 if createopts.get('sharedrelative'):
2990 2999 requirements.add('relshared')
2991 3000 else:
2992 3001 requirements.add('shared')
2993 3002
2994 3003 return requirements
2995 3004
2996 3005 if 'backend' not in createopts:
2997 3006 raise error.ProgrammingError('backend key not present in createopts; '
2998 3007 'was defaultcreateopts() called?')
2999 3008
3000 3009 if createopts['backend'] != 'revlogv1':
3001 3010 raise error.Abort(_('unable to determine repository requirements for '
3002 3011 'storage backend: %s') % createopts['backend'])
3003 3012
3004 3013 requirements = {'revlogv1'}
3005 3014 if ui.configbool('format', 'usestore'):
3006 3015 requirements.add('store')
3007 3016 if ui.configbool('format', 'usefncache'):
3008 3017 requirements.add('fncache')
3009 3018 if ui.configbool('format', 'dotencode'):
3010 3019 requirements.add('dotencode')
3011 3020
3012 3021 compengine = ui.config('format', 'revlog-compression')
3013 3022 if compengine not in util.compengines:
3014 3023 raise error.Abort(_('compression engine %s defined by '
3015 3024 'format.revlog-compression not available') %
3016 3025 compengine,
3017 3026 hint=_('run "hg debuginstall" to list available '
3018 3027 'compression engines'))
3019 3028
3020 3029 # zlib is the historical default and doesn't need an explicit requirement.
3021 3030 elif compengine == 'zstd':
3022 3031 requirements.add('revlog-compression-zstd')
3023 3032 elif compengine != 'zlib':
3024 3033 requirements.add('exp-compression-%s' % compengine)
3025 3034
3026 3035 if scmutil.gdinitconfig(ui):
3027 3036 requirements.add('generaldelta')
3028 3037 if ui.configbool('format', 'sparse-revlog'):
3029 3038 requirements.add(SPARSEREVLOG_REQUIREMENT)
3030 3039 if ui.configbool('experimental', 'treemanifest'):
3031 3040 requirements.add('treemanifest')
3032 3041
3033 3042 revlogv2 = ui.config('experimental', 'revlogv2')
3034 3043 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
3035 3044 requirements.remove('revlogv1')
3036 3045 # generaldelta is implied by revlogv2.
3037 3046 requirements.discard('generaldelta')
3038 3047 requirements.add(REVLOGV2_REQUIREMENT)
3039 3048 # experimental config: format.internal-phase
3040 3049 if ui.configbool('format', 'internal-phase'):
3041 3050 requirements.add('internal-phase')
3042 3051
3043 3052 if createopts.get('narrowfiles'):
3044 3053 requirements.add(repository.NARROW_REQUIREMENT)
3045 3054
3046 3055 if createopts.get('lfs'):
3047 3056 requirements.add('lfs')
3048 3057
3049 3058 if ui.configbool('format', 'bookmarks-in-store'):
3050 3059 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3051 3060
3052 3061 return requirements
3053 3062
3054 3063 def filterknowncreateopts(ui, createopts):
3055 3064 """Filters a dict of repo creation options against options that are known.
3056 3065
3057 3066 Receives a dict of repo creation options and returns a dict of those
3058 3067 options that we don't know how to handle.
3059 3068
3060 3069 This function is called as part of repository creation. If the
3061 3070 returned dict contains any items, repository creation will not
3062 3071 be allowed, as it means there was a request to create a repository
3063 3072 with options not recognized by loaded code.
3064 3073
3065 3074 Extensions can wrap this function to filter out creation options
3066 3075 they know how to handle.
3067 3076 """
3068 3077 known = {
3069 3078 'backend',
3070 3079 'lfs',
3071 3080 'narrowfiles',
3072 3081 'sharedrepo',
3073 3082 'sharedrelative',
3074 3083 'shareditems',
3075 3084 'shallowfilestore',
3076 3085 }
3077 3086
3078 3087 return {k: v for k, v in createopts.items() if k not in known}
3079 3088
3080 3089 def createrepository(ui, path, createopts=None):
3081 3090 """Create a new repository in a vfs.
3082 3091
3083 3092 ``path`` path to the new repo's working directory.
3084 3093 ``createopts`` options for the new repository.
3085 3094
3086 3095 The following keys for ``createopts`` are recognized:
3087 3096
3088 3097 backend
3089 3098 The storage backend to use.
3090 3099 lfs
3091 3100 Repository will be created with ``lfs`` requirement. The lfs extension
3092 3101 will automatically be loaded when the repository is accessed.
3093 3102 narrowfiles
3094 3103 Set up repository to support narrow file storage.
3095 3104 sharedrepo
3096 3105 Repository object from which storage should be shared.
3097 3106 sharedrelative
3098 3107 Boolean indicating if the path to the shared repo should be
3099 3108 stored as relative. By default, the pointer to the "parent" repo
3100 3109 is stored as an absolute path.
3101 3110 shareditems
3102 3111 Set of items to share to the new repository (in addition to storage).
3103 3112 shallowfilestore
3104 3113 Indicates that storage for files should be shallow (not all ancestor
3105 3114 revisions are known).
3106 3115 """
3107 3116 createopts = defaultcreateopts(ui, createopts=createopts)
3108 3117
3109 3118 unknownopts = filterknowncreateopts(ui, createopts)
3110 3119
3111 3120 if not isinstance(unknownopts, dict):
3112 3121 raise error.ProgrammingError('filterknowncreateopts() did not return '
3113 3122 'a dict')
3114 3123
3115 3124 if unknownopts:
3116 3125 raise error.Abort(_('unable to create repository because of unknown '
3117 3126 'creation option: %s') %
3118 3127 ', '.join(sorted(unknownopts)),
3119 3128 hint=_('is a required extension not loaded?'))
3120 3129
3121 3130 requirements = newreporequirements(ui, createopts=createopts)
3122 3131
3123 3132 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3124 3133
3125 3134 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3126 3135 if hgvfs.exists():
3127 3136 raise error.RepoError(_('repository %s already exists') % path)
3128 3137
3129 3138 if 'sharedrepo' in createopts:
3130 3139 sharedpath = createopts['sharedrepo'].sharedpath
3131 3140
3132 3141 if createopts.get('sharedrelative'):
3133 3142 try:
3134 3143 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3135 3144 except (IOError, ValueError) as e:
3136 3145 # ValueError is raised on Windows if the drive letters differ
3137 3146 # on each path.
3138 3147 raise error.Abort(_('cannot calculate relative path'),
3139 3148 hint=stringutil.forcebytestr(e))
3140 3149
3141 3150 if not wdirvfs.exists():
3142 3151 wdirvfs.makedirs()
3143 3152
3144 3153 hgvfs.makedir(notindexed=True)
3145 3154 if 'sharedrepo' not in createopts:
3146 3155 hgvfs.mkdir(b'cache')
3147 3156 hgvfs.mkdir(b'wcache')
3148 3157
3149 3158 if b'store' in requirements and 'sharedrepo' not in createopts:
3150 3159 hgvfs.mkdir(b'store')
3151 3160
3152 3161 # We create an invalid changelog outside the store so very old
3153 3162 # Mercurial versions (which didn't know about the requirements
3154 3163 # file) encounter an error on reading the changelog. This
3155 3164 # effectively locks out old clients and prevents them from
3156 3165 # mucking with a repo in an unknown format.
3157 3166 #
3158 3167 # The revlog header has version 2, which won't be recognized by
3159 3168 # such old clients.
3160 3169 hgvfs.append(b'00changelog.i',
3161 3170 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3162 3171 b'layout')
3163 3172
3164 3173 scmutil.writerequires(hgvfs, requirements)
3165 3174
3166 3175 # Write out file telling readers where to find the shared store.
3167 3176 if 'sharedrepo' in createopts:
3168 3177 hgvfs.write(b'sharedpath', sharedpath)
3169 3178
3170 3179 if createopts.get('shareditems'):
3171 3180 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3172 3181 hgvfs.write(b'shared', shared)
3173 3182
3174 3183 def poisonrepository(repo):
3175 3184 """Poison a repository instance so it can no longer be used."""
3176 3185 # Perform any cleanup on the instance.
3177 3186 repo.close()
3178 3187
3179 3188 # Our strategy is to replace the type of the object with one that
3180 3189 # has all attribute lookups result in error.
3181 3190 #
3182 3191 # But we have to allow the close() method because some constructors
3183 3192 # of repos call close() on repo references.
3184 3193 class poisonedrepository(object):
3185 3194 def __getattribute__(self, item):
3186 3195 if item == r'close':
3187 3196 return object.__getattribute__(self, item)
3188 3197
3189 3198 raise error.ProgrammingError('repo instances should not be used '
3190 3199 'after unshare')
3191 3200
3192 3201 def close(self):
3193 3202 pass
3194 3203
3195 3204 # We may have a repoview, which intercepts __setattr__. So be sure
3196 3205 # we operate at the lowest level possible.
3197 3206 object.__setattr__(repo, r'__class__', poisonedrepository)
@@ -1,1870 +1,1870 b''
1 1 # repository.py - Interfaces and base classes for repositories and peers.
2 2 #
3 3 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.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 from __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11 from . import (
12 12 error,
13 13 )
14 14 from .utils import (
15 15 interfaceutil,
16 16 )
17 17
18 18 # When narrowing is finalized and no longer subject to format changes,
19 19 # we should move this to just "narrow" or similar.
20 20 NARROW_REQUIREMENT = 'narrowhg-experimental'
21 21
22 22 # Local repository feature string.
23 23
24 24 # Revlogs are being used for file storage.
25 25 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
26 26 # The storage part of the repository is shared from an external source.
27 27 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
28 28 # LFS supported for backing file storage.
29 29 REPO_FEATURE_LFS = b'lfs'
30 30 # Repository supports being stream cloned.
31 31 REPO_FEATURE_STREAM_CLONE = b'streamclone'
32 32 # Files storage may lack data for all ancestors.
33 33 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
34 34
35 35 REVISION_FLAG_CENSORED = 1 << 15
36 36 REVISION_FLAG_ELLIPSIS = 1 << 14
37 37 REVISION_FLAG_EXTSTORED = 1 << 13
38 38
39 39 REVISION_FLAGS_KNOWN = (
40 40 REVISION_FLAG_CENSORED | REVISION_FLAG_ELLIPSIS | REVISION_FLAG_EXTSTORED)
41 41
42 42 CG_DELTAMODE_STD = b'default'
43 43 CG_DELTAMODE_PREV = b'previous'
44 44 CG_DELTAMODE_FULL = b'fulltext'
45 45 CG_DELTAMODE_P1 = b'p1'
46 46
47 47 class ipeerconnection(interfaceutil.Interface):
48 48 """Represents a "connection" to a repository.
49 49
50 50 This is the base interface for representing a connection to a repository.
51 51 It holds basic properties and methods applicable to all peer types.
52 52
53 53 This is not a complete interface definition and should not be used
54 54 outside of this module.
55 55 """
56 56 ui = interfaceutil.Attribute("""ui.ui instance""")
57 57
58 58 def url():
59 59 """Returns a URL string representing this peer.
60 60
61 61 Currently, implementations expose the raw URL used to construct the
62 62 instance. It may contain credentials as part of the URL. The
63 63 expectations of the value aren't well-defined and this could lead to
64 64 data leakage.
65 65
66 66 TODO audit/clean consumers and more clearly define the contents of this
67 67 value.
68 68 """
69 69
70 70 def local():
71 71 """Returns a local repository instance.
72 72
73 73 If the peer represents a local repository, returns an object that
74 74 can be used to interface with it. Otherwise returns ``None``.
75 75 """
76 76
77 77 def peer():
78 78 """Returns an object conforming to this interface.
79 79
80 80 Most implementations will ``return self``.
81 81 """
82 82
83 83 def canpush():
84 84 """Returns a boolean indicating if this peer can be pushed to."""
85 85
86 86 def close():
87 87 """Close the connection to this peer.
88 88
89 89 This is called when the peer will no longer be used. Resources
90 90 associated with the peer should be cleaned up.
91 91 """
92 92
93 93 class ipeercapabilities(interfaceutil.Interface):
94 94 """Peer sub-interface related to capabilities."""
95 95
96 96 def capable(name):
97 97 """Determine support for a named capability.
98 98
99 99 Returns ``False`` if capability not supported.
100 100
101 101 Returns ``True`` if boolean capability is supported. Returns a string
102 102 if capability support is non-boolean.
103 103
104 104 Capability strings may or may not map to wire protocol capabilities.
105 105 """
106 106
107 107 def requirecap(name, purpose):
108 108 """Require a capability to be present.
109 109
110 110 Raises a ``CapabilityError`` if the capability isn't present.
111 111 """
112 112
113 113 class ipeercommands(interfaceutil.Interface):
114 114 """Client-side interface for communicating over the wire protocol.
115 115
116 116 This interface is used as a gateway to the Mercurial wire protocol.
117 117 methods commonly call wire protocol commands of the same name.
118 118 """
119 119
120 120 def branchmap():
121 121 """Obtain heads in named branches.
122 122
123 123 Returns a dict mapping branch name to an iterable of nodes that are
124 124 heads on that branch.
125 125 """
126 126
127 127 def capabilities():
128 128 """Obtain capabilities of the peer.
129 129
130 130 Returns a set of string capabilities.
131 131 """
132 132
133 133 def clonebundles():
134 134 """Obtains the clone bundles manifest for the repo.
135 135
136 136 Returns the manifest as unparsed bytes.
137 137 """
138 138
139 139 def debugwireargs(one, two, three=None, four=None, five=None):
140 140 """Used to facilitate debugging of arguments passed over the wire."""
141 141
142 142 def getbundle(source, **kwargs):
143 143 """Obtain remote repository data as a bundle.
144 144
145 145 This command is how the bulk of repository data is transferred from
146 146 the peer to the local repository
147 147
148 148 Returns a generator of bundle data.
149 149 """
150 150
151 151 def heads():
152 152 """Determine all known head revisions in the peer.
153 153
154 154 Returns an iterable of binary nodes.
155 155 """
156 156
157 157 def known(nodes):
158 158 """Determine whether multiple nodes are known.
159 159
160 160 Accepts an iterable of nodes whose presence to check for.
161 161
162 162 Returns an iterable of booleans indicating of the corresponding node
163 163 at that index is known to the peer.
164 164 """
165 165
166 166 def listkeys(namespace):
167 167 """Obtain all keys in a pushkey namespace.
168 168
169 169 Returns an iterable of key names.
170 170 """
171 171
172 172 def lookup(key):
173 173 """Resolve a value to a known revision.
174 174
175 175 Returns a binary node of the resolved revision on success.
176 176 """
177 177
178 178 def pushkey(namespace, key, old, new):
179 179 """Set a value using the ``pushkey`` protocol.
180 180
181 181 Arguments correspond to the pushkey namespace and key to operate on and
182 182 the old and new values for that key.
183 183
184 184 Returns a string with the peer result. The value inside varies by the
185 185 namespace.
186 186 """
187 187
188 188 def stream_out():
189 189 """Obtain streaming clone data.
190 190
191 191 Successful result should be a generator of data chunks.
192 192 """
193 193
194 194 def unbundle(bundle, heads, url):
195 195 """Transfer repository data to the peer.
196 196
197 197 This is how the bulk of data during a push is transferred.
198 198
199 199 Returns the integer number of heads added to the peer.
200 200 """
201 201
202 202 class ipeerlegacycommands(interfaceutil.Interface):
203 203 """Interface for implementing support for legacy wire protocol commands.
204 204
205 205 Wire protocol commands transition to legacy status when they are no longer
206 206 used by modern clients. To facilitate identifying which commands are
207 207 legacy, the interfaces are split.
208 208 """
209 209
210 210 def between(pairs):
211 211 """Obtain nodes between pairs of nodes.
212 212
213 213 ``pairs`` is an iterable of node pairs.
214 214
215 215 Returns an iterable of iterables of nodes corresponding to each
216 216 requested pair.
217 217 """
218 218
219 219 def branches(nodes):
220 220 """Obtain ancestor changesets of specific nodes back to a branch point.
221 221
222 222 For each requested node, the peer finds the first ancestor node that is
223 223 a DAG root or is a merge.
224 224
225 225 Returns an iterable of iterables with the resolved values for each node.
226 226 """
227 227
228 228 def changegroup(nodes, source):
229 229 """Obtain a changegroup with data for descendants of specified nodes."""
230 230
231 231 def changegroupsubset(bases, heads, source):
232 232 pass
233 233
234 234 class ipeercommandexecutor(interfaceutil.Interface):
235 235 """Represents a mechanism to execute remote commands.
236 236
237 237 This is the primary interface for requesting that wire protocol commands
238 238 be executed. Instances of this interface are active in a context manager
239 239 and have a well-defined lifetime. When the context manager exits, all
240 240 outstanding requests are waited on.
241 241 """
242 242
243 243 def callcommand(name, args):
244 244 """Request that a named command be executed.
245 245
246 246 Receives the command name and a dictionary of command arguments.
247 247
248 248 Returns a ``concurrent.futures.Future`` that will resolve to the
249 249 result of that command request. That exact value is left up to
250 250 the implementation and possibly varies by command.
251 251
252 252 Not all commands can coexist with other commands in an executor
253 253 instance: it depends on the underlying wire protocol transport being
254 254 used and the command itself.
255 255
256 256 Implementations MAY call ``sendcommands()`` automatically if the
257 257 requested command can not coexist with other commands in this executor.
258 258
259 259 Implementations MAY call ``sendcommands()`` automatically when the
260 260 future's ``result()`` is called. So, consumers using multiple
261 261 commands with an executor MUST ensure that ``result()`` is not called
262 262 until all command requests have been issued.
263 263 """
264 264
265 265 def sendcommands():
266 266 """Trigger submission of queued command requests.
267 267
268 268 Not all transports submit commands as soon as they are requested to
269 269 run. When called, this method forces queued command requests to be
270 270 issued. It will no-op if all commands have already been sent.
271 271
272 272 When called, no more new commands may be issued with this executor.
273 273 """
274 274
275 275 def close():
276 276 """Signal that this command request is finished.
277 277
278 278 When called, no more new commands may be issued. All outstanding
279 279 commands that have previously been issued are waited on before
280 280 returning. This not only includes waiting for the futures to resolve,
281 281 but also waiting for all response data to arrive. In other words,
282 282 calling this waits for all on-wire state for issued command requests
283 283 to finish.
284 284
285 285 When used as a context manager, this method is called when exiting the
286 286 context manager.
287 287
288 288 This method may call ``sendcommands()`` if there are buffered commands.
289 289 """
290 290
291 291 class ipeerrequests(interfaceutil.Interface):
292 292 """Interface for executing commands on a peer."""
293 293
294 294 limitedarguments = interfaceutil.Attribute(
295 295 """True if the peer cannot receive large argument value for commands."""
296 296 )
297 297
298 298 def commandexecutor():
299 299 """A context manager that resolves to an ipeercommandexecutor.
300 300
301 301 The object this resolves to can be used to issue command requests
302 302 to the peer.
303 303
304 304 Callers should call its ``callcommand`` method to issue command
305 305 requests.
306 306
307 307 A new executor should be obtained for each distinct set of commands
308 308 (possibly just a single command) that the consumer wants to execute
309 309 as part of a single operation or round trip. This is because some
310 310 peers are half-duplex and/or don't support persistent connections.
311 311 e.g. in the case of HTTP peers, commands sent to an executor represent
312 312 a single HTTP request. While some peers may support multiple command
313 313 sends over the wire per executor, consumers need to code to the least
314 314 capable peer. So it should be assumed that command executors buffer
315 315 called commands until they are told to send them and that each
316 316 command executor could result in a new connection or wire-level request
317 317 being issued.
318 318 """
319 319
320 320 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
321 321 """Unified interface for peer repositories.
322 322
323 323 All peer instances must conform to this interface.
324 324 """
325 325
326 326 class ipeerv2(ipeerconnection, ipeercapabilities, ipeerrequests):
327 327 """Unified peer interface for wire protocol version 2 peers."""
328 328
329 329 apidescriptor = interfaceutil.Attribute(
330 330 """Data structure holding description of server API.""")
331 331
332 332 @interfaceutil.implementer(ipeerbase)
333 333 class peer(object):
334 334 """Base class for peer repositories."""
335 335
336 336 limitedarguments = False
337 337
338 338 def capable(self, name):
339 339 caps = self.capabilities()
340 340 if name in caps:
341 341 return True
342 342
343 343 name = '%s=' % name
344 344 for cap in caps:
345 345 if cap.startswith(name):
346 346 return cap[len(name):]
347 347
348 348 return False
349 349
350 350 def requirecap(self, name, purpose):
351 351 if self.capable(name):
352 352 return
353 353
354 354 raise error.CapabilityError(
355 355 _('cannot %s; remote repository does not support the '
356 356 '\'%s\' capability') % (purpose, name))
357 357
358 358 class iverifyproblem(interfaceutil.Interface):
359 359 """Represents a problem with the integrity of the repository.
360 360
361 361 Instances of this interface are emitted to describe an integrity issue
362 362 with a repository (e.g. corrupt storage, missing data, etc).
363 363
364 364 Instances are essentially messages associated with severity.
365 365 """
366 366 warning = interfaceutil.Attribute(
367 367 """Message indicating a non-fatal problem.""")
368 368
369 369 error = interfaceutil.Attribute(
370 370 """Message indicating a fatal problem.""")
371 371
372 372 node = interfaceutil.Attribute(
373 373 """Revision encountering the problem.
374 374
375 375 ``None`` means the problem doesn't apply to a single revision.
376 376 """)
377 377
378 378 class irevisiondelta(interfaceutil.Interface):
379 379 """Represents a delta between one revision and another.
380 380
381 381 Instances convey enough information to allow a revision to be exchanged
382 382 with another repository.
383 383
384 384 Instances represent the fulltext revision data or a delta against
385 385 another revision. Therefore the ``revision`` and ``delta`` attributes
386 386 are mutually exclusive.
387 387
388 388 Typically used for changegroup generation.
389 389 """
390 390
391 391 node = interfaceutil.Attribute(
392 392 """20 byte node of this revision.""")
393 393
394 394 p1node = interfaceutil.Attribute(
395 395 """20 byte node of 1st parent of this revision.""")
396 396
397 397 p2node = interfaceutil.Attribute(
398 398 """20 byte node of 2nd parent of this revision.""")
399 399
400 400 linknode = interfaceutil.Attribute(
401 401 """20 byte node of the changelog revision this node is linked to.""")
402 402
403 403 flags = interfaceutil.Attribute(
404 404 """2 bytes of integer flags that apply to this revision.
405 405
406 406 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
407 407 """)
408 408
409 409 basenode = interfaceutil.Attribute(
410 410 """20 byte node of the revision this data is a delta against.
411 411
412 412 ``nullid`` indicates that the revision is a full revision and not
413 413 a delta.
414 414 """)
415 415
416 416 baserevisionsize = interfaceutil.Attribute(
417 417 """Size of base revision this delta is against.
418 418
419 419 May be ``None`` if ``basenode`` is ``nullid``.
420 420 """)
421 421
422 422 revision = interfaceutil.Attribute(
423 423 """Raw fulltext of revision data for this node.""")
424 424
425 425 delta = interfaceutil.Attribute(
426 426 """Delta between ``basenode`` and ``node``.
427 427
428 428 Stored in the bdiff delta format.
429 429 """)
430 430
431 431 class ifilerevisionssequence(interfaceutil.Interface):
432 432 """Contains index data for all revisions of a file.
433 433
434 434 Types implementing this behave like lists of tuples. The index
435 435 in the list corresponds to the revision number. The values contain
436 436 index metadata.
437 437
438 438 The *null* revision (revision number -1) is always the last item
439 439 in the index.
440 440 """
441 441
442 442 def __len__():
443 443 """The total number of revisions."""
444 444
445 445 def __getitem__(rev):
446 446 """Returns the object having a specific revision number.
447 447
448 448 Returns an 8-tuple with the following fields:
449 449
450 450 offset+flags
451 451 Contains the offset and flags for the revision. 64-bit unsigned
452 452 integer where first 6 bytes are the offset and the next 2 bytes
453 453 are flags. The offset can be 0 if it is not used by the store.
454 454 compressed size
455 455 Size of the revision data in the store. It can be 0 if it isn't
456 456 needed by the store.
457 457 uncompressed size
458 458 Fulltext size. It can be 0 if it isn't needed by the store.
459 459 base revision
460 460 Revision number of revision the delta for storage is encoded
461 461 against. -1 indicates not encoded against a base revision.
462 462 link revision
463 463 Revision number of changelog revision this entry is related to.
464 464 p1 revision
465 465 Revision number of 1st parent. -1 if no 1st parent.
466 466 p2 revision
467 467 Revision number of 2nd parent. -1 if no 1st parent.
468 468 node
469 469 Binary node value for this revision number.
470 470
471 471 Negative values should index off the end of the sequence. ``-1``
472 472 should return the null revision. ``-2`` should return the most
473 473 recent revision.
474 474 """
475 475
476 476 def __contains__(rev):
477 477 """Whether a revision number exists."""
478 478
479 479 def insert(self, i, entry):
480 480 """Add an item to the index at specific revision."""
481 481
482 482 class ifileindex(interfaceutil.Interface):
483 483 """Storage interface for index data of a single file.
484 484
485 485 File storage data is divided into index metadata and data storage.
486 486 This interface defines the index portion of the interface.
487 487
488 488 The index logically consists of:
489 489
490 490 * A mapping between revision numbers and nodes.
491 491 * DAG data (storing and querying the relationship between nodes).
492 492 * Metadata to facilitate storage.
493 493 """
494 494 def __len__():
495 495 """Obtain the number of revisions stored for this file."""
496 496
497 497 def __iter__():
498 498 """Iterate over revision numbers for this file."""
499 499
500 500 def hasnode(node):
501 501 """Returns a bool indicating if a node is known to this store.
502 502
503 503 Implementations must only return True for full, binary node values:
504 504 hex nodes, revision numbers, and partial node matches must be
505 505 rejected.
506 506
507 507 The null node is never present.
508 508 """
509 509
510 510 def revs(start=0, stop=None):
511 511 """Iterate over revision numbers for this file, with control."""
512 512
513 513 def parents(node):
514 514 """Returns a 2-tuple of parent nodes for a revision.
515 515
516 516 Values will be ``nullid`` if the parent is empty.
517 517 """
518 518
519 519 def parentrevs(rev):
520 520 """Like parents() but operates on revision numbers."""
521 521
522 522 def rev(node):
523 523 """Obtain the revision number given a node.
524 524
525 525 Raises ``error.LookupError`` if the node is not known.
526 526 """
527 527
528 528 def node(rev):
529 529 """Obtain the node value given a revision number.
530 530
531 531 Raises ``IndexError`` if the node is not known.
532 532 """
533 533
534 534 def lookup(node):
535 535 """Attempt to resolve a value to a node.
536 536
537 537 Value can be a binary node, hex node, revision number, or a string
538 538 that can be converted to an integer.
539 539
540 540 Raises ``error.LookupError`` if a node could not be resolved.
541 541 """
542 542
543 543 def linkrev(rev):
544 544 """Obtain the changeset revision number a revision is linked to."""
545 545
546 546 def iscensored(rev):
547 547 """Return whether a revision's content has been censored."""
548 548
549 549 def commonancestorsheads(node1, node2):
550 550 """Obtain an iterable of nodes containing heads of common ancestors.
551 551
552 552 See ``ancestor.commonancestorsheads()``.
553 553 """
554 554
555 555 def descendants(revs):
556 556 """Obtain descendant revision numbers for a set of revision numbers.
557 557
558 558 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
559 559 """
560 560
561 561 def heads(start=None, stop=None):
562 562 """Obtain a list of nodes that are DAG heads, with control.
563 563
564 564 The set of revisions examined can be limited by specifying
565 565 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
566 566 iterable of nodes. DAG traversal starts at earlier revision
567 567 ``start`` and iterates forward until any node in ``stop`` is
568 568 encountered.
569 569 """
570 570
571 571 def children(node):
572 572 """Obtain nodes that are children of a node.
573 573
574 574 Returns a list of nodes.
575 575 """
576 576
577 577 class ifiledata(interfaceutil.Interface):
578 578 """Storage interface for data storage of a specific file.
579 579
580 580 This complements ``ifileindex`` and provides an interface for accessing
581 581 data for a tracked file.
582 582 """
583 583 def size(rev):
584 584 """Obtain the fulltext size of file data.
585 585
586 586 Any metadata is excluded from size measurements.
587 587 """
588 588
589 589 def revision(node, raw=False):
590 590 """"Obtain fulltext data for a node.
591 591
592 592 By default, any storage transformations are applied before the data
593 593 is returned. If ``raw`` is True, non-raw storage transformations
594 594 are not applied.
595 595
596 596 The fulltext data may contain a header containing metadata. Most
597 597 consumers should use ``read()`` to obtain the actual file data.
598 598 """
599 599
600 600 def read(node):
601 601 """Resolve file fulltext data.
602 602
603 603 This is similar to ``revision()`` except any metadata in the data
604 604 headers is stripped.
605 605 """
606 606
607 607 def renamed(node):
608 608 """Obtain copy metadata for a node.
609 609
610 610 Returns ``False`` if no copy metadata is stored or a 2-tuple of
611 611 (path, node) from which this revision was copied.
612 612 """
613 613
614 614 def cmp(node, fulltext):
615 615 """Compare fulltext to another revision.
616 616
617 617 Returns True if the fulltext is different from what is stored.
618 618
619 619 This takes copy metadata into account.
620 620
621 621 TODO better document the copy metadata and censoring logic.
622 622 """
623 623
624 624 def emitrevisions(nodes,
625 625 nodesorder=None,
626 626 revisiondata=False,
627 627 assumehaveparentrevisions=False,
628 628 deltamode=CG_DELTAMODE_STD):
629 629 """Produce ``irevisiondelta`` for revisions.
630 630
631 631 Given an iterable of nodes, emits objects conforming to the
632 632 ``irevisiondelta`` interface that describe revisions in storage.
633 633
634 634 This method is a generator.
635 635
636 636 The input nodes may be unordered. Implementations must ensure that a
637 637 node's parents are emitted before the node itself. Transitively, this
638 638 means that a node may only be emitted once all its ancestors in
639 639 ``nodes`` have also been emitted.
640 640
641 641 By default, emits "index" data (the ``node``, ``p1node``, and
642 642 ``p2node`` attributes). If ``revisiondata`` is set, revision data
643 643 will also be present on the emitted objects.
644 644
645 645 With default argument values, implementations can choose to emit
646 646 either fulltext revision data or a delta. When emitting deltas,
647 647 implementations must consider whether the delta's base revision
648 648 fulltext is available to the receiver.
649 649
650 650 The base revision fulltext is guaranteed to be available if any of
651 651 the following are met:
652 652
653 653 * Its fulltext revision was emitted by this method call.
654 654 * A delta for that revision was emitted by this method call.
655 655 * ``assumehaveparentrevisions`` is True and the base revision is a
656 656 parent of the node.
657 657
658 658 ``nodesorder`` can be used to control the order that revisions are
659 659 emitted. By default, revisions can be reordered as long as they are
660 660 in DAG topological order (see above). If the value is ``nodes``,
661 661 the iteration order from ``nodes`` should be used. If the value is
662 662 ``storage``, then the native order from the backing storage layer
663 663 is used. (Not all storage layers will have strong ordering and behavior
664 664 of this mode is storage-dependent.) ``nodes`` ordering can force
665 665 revisions to be emitted before their ancestors, so consumers should
666 666 use it with care.
667 667
668 668 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
669 669 be set and it is the caller's responsibility to resolve it, if needed.
670 670
671 671 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
672 672 all revision data should be emitted as deltas against the revision
673 673 emitted just prior. The initial revision should be a delta against its
674 674 1st parent.
675 675 """
676 676
677 677 class ifilemutation(interfaceutil.Interface):
678 678 """Storage interface for mutation events of a tracked file."""
679 679
680 680 def add(filedata, meta, transaction, linkrev, p1, p2):
681 681 """Add a new revision to the store.
682 682
683 683 Takes file data, dictionary of metadata, a transaction, linkrev,
684 684 and parent nodes.
685 685
686 686 Returns the node that was added.
687 687
688 688 May no-op if a revision matching the supplied data is already stored.
689 689 """
690 690
691 691 def addrevision(revisiondata, transaction, linkrev, p1, p2, node=None,
692 692 flags=0, cachedelta=None):
693 693 """Add a new revision to the store.
694 694
695 695 This is similar to ``add()`` except it operates at a lower level.
696 696
697 697 The data passed in already contains a metadata header, if any.
698 698
699 699 ``node`` and ``flags`` can be used to define the expected node and
700 700 the flags to use with storage. ``flags`` is a bitwise value composed
701 701 of the various ``REVISION_FLAG_*`` constants.
702 702
703 703 ``add()`` is usually called when adding files from e.g. the working
704 704 directory. ``addrevision()`` is often called by ``add()`` and for
705 705 scenarios where revision data has already been computed, such as when
706 706 applying raw data from a peer repo.
707 707 """
708 708
709 709 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None,
710 710 maybemissingparents=False):
711 711 """Process a series of deltas for storage.
712 712
713 713 ``deltas`` is an iterable of 7-tuples of
714 714 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
715 715 to add.
716 716
717 717 The ``delta`` field contains ``mpatch`` data to apply to a base
718 718 revision, identified by ``deltabase``. The base node can be
719 719 ``nullid``, in which case the header from the delta can be ignored
720 720 and the delta used as the fulltext.
721 721
722 722 ``addrevisioncb`` should be called for each node as it is committed.
723 723
724 724 ``maybemissingparents`` is a bool indicating whether the incoming
725 725 data may reference parents/ancestor revisions that aren't present.
726 726 This flag is set when receiving data into a "shallow" store that
727 727 doesn't hold all history.
728 728
729 729 Returns a list of nodes that were processed. A node will be in the list
730 730 even if it existed in the store previously.
731 731 """
732 732
733 733 def censorrevision(tr, node, tombstone=b''):
734 734 """Remove the content of a single revision.
735 735
736 736 The specified ``node`` will have its content purged from storage.
737 737 Future attempts to access the revision data for this node will
738 738 result in failure.
739 739
740 740 A ``tombstone`` message can optionally be stored. This message may be
741 741 displayed to users when they attempt to access the missing revision
742 742 data.
743 743
744 744 Storage backends may have stored deltas against the previous content
745 745 in this revision. As part of censoring a revision, these storage
746 746 backends are expected to rewrite any internally stored deltas such
747 747 that they no longer reference the deleted content.
748 748 """
749 749
750 750 def getstrippoint(minlink):
751 751 """Find the minimum revision that must be stripped to strip a linkrev.
752 752
753 753 Returns a 2-tuple containing the minimum revision number and a set
754 754 of all revisions numbers that would be broken by this strip.
755 755
756 756 TODO this is highly revlog centric and should be abstracted into
757 757 a higher-level deletion API. ``repair.strip()`` relies on this.
758 758 """
759 759
760 760 def strip(minlink, transaction):
761 761 """Remove storage of items starting at a linkrev.
762 762
763 763 This uses ``getstrippoint()`` to determine the first node to remove.
764 764 Then it effectively truncates storage for all revisions after that.
765 765
766 766 TODO this is highly revlog centric and should be abstracted into a
767 767 higher-level deletion API.
768 768 """
769 769
770 770 class ifilestorage(ifileindex, ifiledata, ifilemutation):
771 771 """Complete storage interface for a single tracked file."""
772 772
773 773 def files():
774 774 """Obtain paths that are backing storage for this file.
775 775
776 776 TODO this is used heavily by verify code and there should probably
777 777 be a better API for that.
778 778 """
779 779
780 780 def storageinfo(exclusivefiles=False, sharedfiles=False,
781 781 revisionscount=False, trackedsize=False,
782 782 storedsize=False):
783 783 """Obtain information about storage for this file's data.
784 784
785 785 Returns a dict describing storage for this tracked path. The keys
786 786 in the dict map to arguments of the same. The arguments are bools
787 787 indicating whether to calculate and obtain that data.
788 788
789 789 exclusivefiles
790 790 Iterable of (vfs, path) describing files that are exclusively
791 791 used to back storage for this tracked path.
792 792
793 793 sharedfiles
794 794 Iterable of (vfs, path) describing files that are used to back
795 795 storage for this tracked path. Those files may also provide storage
796 796 for other stored entities.
797 797
798 798 revisionscount
799 799 Number of revisions available for retrieval.
800 800
801 801 trackedsize
802 802 Total size in bytes of all tracked revisions. This is a sum of the
803 803 length of the fulltext of all revisions.
804 804
805 805 storedsize
806 806 Total size in bytes used to store data for all tracked revisions.
807 807 This is commonly less than ``trackedsize`` due to internal usage
808 808 of deltas rather than fulltext revisions.
809 809
810 810 Not all storage backends may support all queries are have a reasonable
811 811 value to use. In that case, the value should be set to ``None`` and
812 812 callers are expected to handle this special value.
813 813 """
814 814
815 815 def verifyintegrity(state):
816 816 """Verifies the integrity of file storage.
817 817
818 818 ``state`` is a dict holding state of the verifier process. It can be
819 819 used to communicate data between invocations of multiple storage
820 820 primitives.
821 821
822 822 If individual revisions cannot have their revision content resolved,
823 823 the method is expected to set the ``skipread`` key to a set of nodes
824 824 that encountered problems.
825 825
826 826 The method yields objects conforming to the ``iverifyproblem``
827 827 interface.
828 828 """
829 829
830 830 class idirs(interfaceutil.Interface):
831 831 """Interface representing a collection of directories from paths.
832 832
833 833 This interface is essentially a derived data structure representing
834 834 directories from a collection of paths.
835 835 """
836 836
837 837 def addpath(path):
838 838 """Add a path to the collection.
839 839
840 840 All directories in the path will be added to the collection.
841 841 """
842 842
843 843 def delpath(path):
844 844 """Remove a path from the collection.
845 845
846 846 If the removal was the last path in a particular directory, the
847 847 directory is removed from the collection.
848 848 """
849 849
850 850 def __iter__():
851 851 """Iterate over the directories in this collection of paths."""
852 852
853 853 def __contains__(path):
854 854 """Whether a specific directory is in this collection."""
855 855
856 856 class imanifestdict(interfaceutil.Interface):
857 857 """Interface representing a manifest data structure.
858 858
859 859 A manifest is effectively a dict mapping paths to entries. Each entry
860 860 consists of a binary node and extra flags affecting that entry.
861 861 """
862 862
863 863 def __getitem__(path):
864 864 """Returns the binary node value for a path in the manifest.
865 865
866 866 Raises ``KeyError`` if the path does not exist in the manifest.
867 867
868 868 Equivalent to ``self.find(path)[0]``.
869 869 """
870 870
871 871 def find(path):
872 872 """Returns the entry for a path in the manifest.
873 873
874 874 Returns a 2-tuple of (node, flags).
875 875
876 876 Raises ``KeyError`` if the path does not exist in the manifest.
877 877 """
878 878
879 879 def __len__():
880 880 """Return the number of entries in the manifest."""
881 881
882 882 def __nonzero__():
883 883 """Returns True if the manifest has entries, False otherwise."""
884 884
885 885 __bool__ = __nonzero__
886 886
887 887 def __setitem__(path, node):
888 888 """Define the node value for a path in the manifest.
889 889
890 890 If the path is already in the manifest, its flags will be copied to
891 891 the new entry.
892 892 """
893 893
894 894 def __contains__(path):
895 895 """Whether a path exists in the manifest."""
896 896
897 897 def __delitem__(path):
898 898 """Remove a path from the manifest.
899 899
900 900 Raises ``KeyError`` if the path is not in the manifest.
901 901 """
902 902
903 903 def __iter__():
904 904 """Iterate over paths in the manifest."""
905 905
906 906 def iterkeys():
907 907 """Iterate over paths in the manifest."""
908 908
909 909 def keys():
910 910 """Obtain a list of paths in the manifest."""
911 911
912 912 def filesnotin(other, match=None):
913 913 """Obtain the set of paths in this manifest but not in another.
914 914
915 915 ``match`` is an optional matcher function to be applied to both
916 916 manifests.
917 917
918 918 Returns a set of paths.
919 919 """
920 920
921 921 def dirs():
922 922 """Returns an object implementing the ``idirs`` interface."""
923 923
924 924 def hasdir(dir):
925 925 """Returns a bool indicating if a directory is in this manifest."""
926 926
927 927 def matches(match):
928 928 """Generate a new manifest filtered through a matcher.
929 929
930 930 Returns an object conforming to the ``imanifestdict`` interface.
931 931 """
932 932
933 933 def walk(match):
934 934 """Generator of paths in manifest satisfying a matcher.
935 935
936 936 This is equivalent to ``self.matches(match).iterkeys()`` except a new
937 937 manifest object is not created.
938 938
939 939 If the matcher has explicit files listed and they don't exist in
940 940 the manifest, ``match.bad()`` is called for each missing file.
941 941 """
942 942
943 943 def diff(other, match=None, clean=False):
944 944 """Find differences between this manifest and another.
945 945
946 946 This manifest is compared to ``other``.
947 947
948 948 If ``match`` is provided, the two manifests are filtered against this
949 949 matcher and only entries satisfying the matcher are compared.
950 950
951 951 If ``clean`` is True, unchanged files are included in the returned
952 952 object.
953 953
954 954 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
955 955 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
956 956 represents the node and flags for this manifest and ``(node2, flag2)``
957 957 are the same for the other manifest.
958 958 """
959 959
960 960 def setflag(path, flag):
961 961 """Set the flag value for a given path.
962 962
963 963 Raises ``KeyError`` if the path is not already in the manifest.
964 964 """
965 965
966 966 def get(path, default=None):
967 967 """Obtain the node value for a path or a default value if missing."""
968 968
969 969 def flags(path, default=''):
970 970 """Return the flags value for a path or a default value if missing."""
971 971
972 972 def copy():
973 973 """Return a copy of this manifest."""
974 974
975 975 def items():
976 976 """Returns an iterable of (path, node) for items in this manifest."""
977 977
978 978 def iteritems():
979 979 """Identical to items()."""
980 980
981 981 def iterentries():
982 982 """Returns an iterable of (path, node, flags) for this manifest.
983 983
984 984 Similar to ``iteritems()`` except items are a 3-tuple and include
985 985 flags.
986 986 """
987 987
988 988 def text():
989 989 """Obtain the raw data representation for this manifest.
990 990
991 991 Result is used to create a manifest revision.
992 992 """
993 993
994 994 def fastdelta(base, changes):
995 995 """Obtain a delta between this manifest and another given changes.
996 996
997 997 ``base`` in the raw data representation for another manifest.
998 998
999 999 ``changes`` is an iterable of ``(path, to_delete)``.
1000 1000
1001 1001 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1002 1002 delta between ``base`` and this manifest.
1003 1003 """
1004 1004
1005 1005 class imanifestrevisionbase(interfaceutil.Interface):
1006 1006 """Base interface representing a single revision of a manifest.
1007 1007
1008 1008 Should not be used as a primary interface: should always be inherited
1009 1009 as part of a larger interface.
1010 1010 """
1011 1011
1012 1012 def new():
1013 1013 """Obtain a new manifest instance.
1014 1014
1015 1015 Returns an object conforming to the ``imanifestrevisionwritable``
1016 1016 interface. The instance will be associated with the same
1017 1017 ``imanifestlog`` collection as this instance.
1018 1018 """
1019 1019
1020 1020 def copy():
1021 1021 """Obtain a copy of this manifest instance.
1022 1022
1023 1023 Returns an object conforming to the ``imanifestrevisionwritable``
1024 1024 interface. The instance will be associated with the same
1025 1025 ``imanifestlog`` collection as this instance.
1026 1026 """
1027 1027
1028 1028 def read():
1029 1029 """Obtain the parsed manifest data structure.
1030 1030
1031 1031 The returned object conforms to the ``imanifestdict`` interface.
1032 1032 """
1033 1033
1034 1034 class imanifestrevisionstored(imanifestrevisionbase):
1035 1035 """Interface representing a manifest revision committed to storage."""
1036 1036
1037 1037 def node():
1038 1038 """The binary node for this manifest."""
1039 1039
1040 1040 parents = interfaceutil.Attribute(
1041 1041 """List of binary nodes that are parents for this manifest revision."""
1042 1042 )
1043 1043
1044 1044 def readdelta(shallow=False):
1045 1045 """Obtain the manifest data structure representing changes from parent.
1046 1046
1047 1047 This manifest is compared to its 1st parent. A new manifest representing
1048 1048 those differences is constructed.
1049 1049
1050 1050 The returned object conforms to the ``imanifestdict`` interface.
1051 1051 """
1052 1052
1053 1053 def readfast(shallow=False):
1054 1054 """Calls either ``read()`` or ``readdelta()``.
1055 1055
1056 1056 The faster of the two options is called.
1057 1057 """
1058 1058
1059 1059 def find(key):
1060 1060 """Calls self.read().find(key)``.
1061 1061
1062 1062 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1063 1063 """
1064 1064
1065 1065 class imanifestrevisionwritable(imanifestrevisionbase):
1066 1066 """Interface representing a manifest revision that can be committed."""
1067 1067
1068 1068 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1069 1069 """Add this revision to storage.
1070 1070
1071 1071 Takes a transaction object, the changeset revision number it will
1072 1072 be associated with, its parent nodes, and lists of added and
1073 1073 removed paths.
1074 1074
1075 1075 If match is provided, storage can choose not to inspect or write out
1076 1076 items that do not match. Storage is still required to be able to provide
1077 1077 the full manifest in the future for any directories written (these
1078 1078 manifests should not be "narrowed on disk").
1079 1079
1080 1080 Returns the binary node of the created revision.
1081 1081 """
1082 1082
1083 1083 class imanifeststorage(interfaceutil.Interface):
1084 1084 """Storage interface for manifest data."""
1085 1085
1086 1086 tree = interfaceutil.Attribute(
1087 1087 """The path to the directory this manifest tracks.
1088 1088
1089 1089 The empty bytestring represents the root manifest.
1090 1090 """)
1091 1091
1092 1092 index = interfaceutil.Attribute(
1093 1093 """An ``ifilerevisionssequence`` instance.""")
1094 1094
1095 1095 indexfile = interfaceutil.Attribute(
1096 1096 """Path of revlog index file.
1097 1097
1098 1098 TODO this is revlog specific and should not be exposed.
1099 1099 """)
1100 1100
1101 1101 opener = interfaceutil.Attribute(
1102 1102 """VFS opener to use to access underlying files used for storage.
1103 1103
1104 1104 TODO this is revlog specific and should not be exposed.
1105 1105 """)
1106 1106
1107 1107 version = interfaceutil.Attribute(
1108 1108 """Revlog version number.
1109 1109
1110 1110 TODO this is revlog specific and should not be exposed.
1111 1111 """)
1112 1112
1113 1113 _generaldelta = interfaceutil.Attribute(
1114 1114 """Whether generaldelta storage is being used.
1115 1115
1116 1116 TODO this is revlog specific and should not be exposed.
1117 1117 """)
1118 1118
1119 1119 fulltextcache = interfaceutil.Attribute(
1120 1120 """Dict with cache of fulltexts.
1121 1121
1122 1122 TODO this doesn't feel appropriate for the storage interface.
1123 1123 """)
1124 1124
1125 1125 def __len__():
1126 1126 """Obtain the number of revisions stored for this manifest."""
1127 1127
1128 1128 def __iter__():
1129 1129 """Iterate over revision numbers for this manifest."""
1130 1130
1131 1131 def rev(node):
1132 1132 """Obtain the revision number given a binary node.
1133 1133
1134 1134 Raises ``error.LookupError`` if the node is not known.
1135 1135 """
1136 1136
1137 1137 def node(rev):
1138 1138 """Obtain the node value given a revision number.
1139 1139
1140 1140 Raises ``error.LookupError`` if the revision is not known.
1141 1141 """
1142 1142
1143 1143 def lookup(value):
1144 1144 """Attempt to resolve a value to a node.
1145 1145
1146 1146 Value can be a binary node, hex node, revision number, or a bytes
1147 1147 that can be converted to an integer.
1148 1148
1149 1149 Raises ``error.LookupError`` if a ndoe could not be resolved.
1150 1150 """
1151 1151
1152 1152 def parents(node):
1153 1153 """Returns a 2-tuple of parent nodes for a node.
1154 1154
1155 1155 Values will be ``nullid`` if the parent is empty.
1156 1156 """
1157 1157
1158 1158 def parentrevs(rev):
1159 1159 """Like parents() but operates on revision numbers."""
1160 1160
1161 1161 def linkrev(rev):
1162 1162 """Obtain the changeset revision number a revision is linked to."""
1163 1163
1164 1164 def revision(node, _df=None, raw=False):
1165 1165 """Obtain fulltext data for a node."""
1166 1166
1167 1167 def revdiff(rev1, rev2):
1168 1168 """Obtain a delta between two revision numbers.
1169 1169
1170 1170 The returned data is the result of ``bdiff.bdiff()`` on the raw
1171 1171 revision data.
1172 1172 """
1173 1173
1174 1174 def cmp(node, fulltext):
1175 1175 """Compare fulltext to another revision.
1176 1176
1177 1177 Returns True if the fulltext is different from what is stored.
1178 1178 """
1179 1179
1180 1180 def emitrevisions(nodes,
1181 1181 nodesorder=None,
1182 1182 revisiondata=False,
1183 1183 assumehaveparentrevisions=False):
1184 1184 """Produce ``irevisiondelta`` describing revisions.
1185 1185
1186 1186 See the documentation for ``ifiledata`` for more.
1187 1187 """
1188 1188
1189 1189 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
1190 1190 """Process a series of deltas for storage.
1191 1191
1192 1192 See the documentation in ``ifilemutation`` for more.
1193 1193 """
1194 1194
1195 1195 def rawsize(rev):
1196 1196 """Obtain the size of tracked data.
1197 1197
1198 1198 Is equivalent to ``len(m.revision(node, raw=True))``.
1199 1199
1200 1200 TODO this method is only used by upgrade code and may be removed.
1201 1201 """
1202 1202
1203 1203 def getstrippoint(minlink):
1204 1204 """Find minimum revision that must be stripped to strip a linkrev.
1205 1205
1206 1206 See the documentation in ``ifilemutation`` for more.
1207 1207 """
1208 1208
1209 1209 def strip(minlink, transaction):
1210 1210 """Remove storage of items starting at a linkrev.
1211 1211
1212 1212 See the documentation in ``ifilemutation`` for more.
1213 1213 """
1214 1214
1215 1215 def checksize():
1216 1216 """Obtain the expected sizes of backing files.
1217 1217
1218 1218 TODO this is used by verify and it should not be part of the interface.
1219 1219 """
1220 1220
1221 1221 def files():
1222 1222 """Obtain paths that are backing storage for this manifest.
1223 1223
1224 1224 TODO this is used by verify and there should probably be a better API
1225 1225 for this functionality.
1226 1226 """
1227 1227
1228 1228 def deltaparent(rev):
1229 1229 """Obtain the revision that a revision is delta'd against.
1230 1230
1231 1231 TODO delta encoding is an implementation detail of storage and should
1232 1232 not be exposed to the storage interface.
1233 1233 """
1234 1234
1235 1235 def clone(tr, dest, **kwargs):
1236 1236 """Clone this instance to another."""
1237 1237
1238 1238 def clearcaches(clear_persisted_data=False):
1239 1239 """Clear any caches associated with this instance."""
1240 1240
1241 1241 def dirlog(d):
1242 1242 """Obtain a manifest storage instance for a tree."""
1243 1243
1244 1244 def add(m, transaction, link, p1, p2, added, removed, readtree=None,
1245 1245 match=None):
1246 1246 """Add a revision to storage.
1247 1247
1248 1248 ``m`` is an object conforming to ``imanifestdict``.
1249 1249
1250 1250 ``link`` is the linkrev revision number.
1251 1251
1252 1252 ``p1`` and ``p2`` are the parent revision numbers.
1253 1253
1254 1254 ``added`` and ``removed`` are iterables of added and removed paths,
1255 1255 respectively.
1256 1256
1257 1257 ``readtree`` is a function that can be used to read the child tree(s)
1258 1258 when recursively writing the full tree structure when using
1259 1259 treemanifets.
1260 1260
1261 1261 ``match`` is a matcher that can be used to hint to storage that not all
1262 1262 paths must be inspected; this is an optimization and can be safely
1263 1263 ignored. Note that the storage must still be able to reproduce a full
1264 1264 manifest including files that did not match.
1265 1265 """
1266 1266
1267 1267 def storageinfo(exclusivefiles=False, sharedfiles=False,
1268 1268 revisionscount=False, trackedsize=False,
1269 1269 storedsize=False):
1270 1270 """Obtain information about storage for this manifest's data.
1271 1271
1272 1272 See ``ifilestorage.storageinfo()`` for a description of this method.
1273 1273 This one behaves the same way, except for manifest data.
1274 1274 """
1275 1275
1276 1276 class imanifestlog(interfaceutil.Interface):
1277 1277 """Interface representing a collection of manifest snapshots.
1278 1278
1279 1279 Represents the root manifest in a repository.
1280 1280
1281 1281 Also serves as a means to access nested tree manifests and to cache
1282 1282 tree manifests.
1283 1283 """
1284 1284
1285 1285 def __getitem__(node):
1286 1286 """Obtain a manifest instance for a given binary node.
1287 1287
1288 1288 Equivalent to calling ``self.get('', node)``.
1289 1289
1290 1290 The returned object conforms to the ``imanifestrevisionstored``
1291 1291 interface.
1292 1292 """
1293 1293
1294 1294 def get(tree, node, verify=True):
1295 1295 """Retrieve the manifest instance for a given directory and binary node.
1296 1296
1297 1297 ``node`` always refers to the node of the root manifest (which will be
1298 1298 the only manifest if flat manifests are being used).
1299 1299
1300 1300 If ``tree`` is the empty string, the root manifest is returned.
1301 1301 Otherwise the manifest for the specified directory will be returned
1302 1302 (requires tree manifests).
1303 1303
1304 1304 If ``verify`` is True, ``LookupError`` is raised if the node is not
1305 1305 known.
1306 1306
1307 1307 The returned object conforms to the ``imanifestrevisionstored``
1308 1308 interface.
1309 1309 """
1310 1310
1311 1311 def getstorage(tree):
1312 1312 """Retrieve an interface to storage for a particular tree.
1313 1313
1314 1314 If ``tree`` is the empty bytestring, storage for the root manifest will
1315 1315 be returned. Otherwise storage for a tree manifest is returned.
1316 1316
1317 1317 TODO formalize interface for returned object.
1318 1318 """
1319 1319
1320 1320 def clearcaches():
1321 1321 """Clear caches associated with this collection."""
1322 1322
1323 1323 def rev(node):
1324 1324 """Obtain the revision number for a binary node.
1325 1325
1326 1326 Raises ``error.LookupError`` if the node is not known.
1327 1327 """
1328 1328
1329 1329 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1330 1330 """Local repository sub-interface providing access to tracked file storage.
1331 1331
1332 1332 This interface defines how a repository accesses storage for a single
1333 1333 tracked file path.
1334 1334 """
1335 1335
1336 1336 def file(f):
1337 1337 """Obtain a filelog for a tracked path.
1338 1338
1339 1339 The returned type conforms to the ``ifilestorage`` interface.
1340 1340 """
1341 1341
1342 1342 class ilocalrepositorymain(interfaceutil.Interface):
1343 1343 """Main interface for local repositories.
1344 1344
1345 1345 This currently captures the reality of things - not how things should be.
1346 1346 """
1347 1347
1348 1348 supportedformats = interfaceutil.Attribute(
1349 1349 """Set of requirements that apply to stream clone.
1350 1350
1351 1351 This is actually a class attribute and is shared among all instances.
1352 1352 """)
1353 1353
1354 1354 supported = interfaceutil.Attribute(
1355 1355 """Set of requirements that this repo is capable of opening.""")
1356 1356
1357 1357 requirements = interfaceutil.Attribute(
1358 1358 """Set of requirements this repo uses.""")
1359 1359
1360 1360 features = interfaceutil.Attribute(
1361 1361 """Set of "features" this repository supports.
1362 1362
1363 1363 A "feature" is a loosely-defined term. It can refer to a feature
1364 1364 in the classical sense or can describe an implementation detail
1365 1365 of the repository. For example, a ``readonly`` feature may denote
1366 1366 the repository as read-only. Or a ``revlogfilestore`` feature may
1367 1367 denote that the repository is using revlogs for file storage.
1368 1368
1369 1369 The intent of features is to provide a machine-queryable mechanism
1370 1370 for repo consumers to test for various repository characteristics.
1371 1371
1372 1372 Features are similar to ``requirements``. The main difference is that
1373 1373 requirements are stored on-disk and represent requirements to open the
1374 1374 repository. Features are more run-time capabilities of the repository
1375 1375 and more granular capabilities (which may be derived from requirements).
1376 1376 """)
1377 1377
1378 1378 filtername = interfaceutil.Attribute(
1379 1379 """Name of the repoview that is active on this repo.""")
1380 1380
1381 1381 wvfs = interfaceutil.Attribute(
1382 1382 """VFS used to access the working directory.""")
1383 1383
1384 1384 vfs = interfaceutil.Attribute(
1385 1385 """VFS rooted at the .hg directory.
1386 1386
1387 1387 Used to access repository data not in the store.
1388 1388 """)
1389 1389
1390 1390 svfs = interfaceutil.Attribute(
1391 1391 """VFS rooted at the store.
1392 1392
1393 1393 Used to access repository data in the store. Typically .hg/store.
1394 1394 But can point elsewhere if the store is shared.
1395 1395 """)
1396 1396
1397 1397 root = interfaceutil.Attribute(
1398 1398 """Path to the root of the working directory.""")
1399 1399
1400 1400 path = interfaceutil.Attribute(
1401 1401 """Path to the .hg directory.""")
1402 1402
1403 1403 origroot = interfaceutil.Attribute(
1404 1404 """The filesystem path that was used to construct the repo.""")
1405 1405
1406 1406 auditor = interfaceutil.Attribute(
1407 1407 """A pathauditor for the working directory.
1408 1408
1409 1409 This checks if a path refers to a nested repository.
1410 1410
1411 1411 Operates on the filesystem.
1412 1412 """)
1413 1413
1414 1414 nofsauditor = interfaceutil.Attribute(
1415 1415 """A pathauditor for the working directory.
1416 1416
1417 1417 This is like ``auditor`` except it doesn't do filesystem checks.
1418 1418 """)
1419 1419
1420 1420 baseui = interfaceutil.Attribute(
1421 1421 """Original ui instance passed into constructor.""")
1422 1422
1423 1423 ui = interfaceutil.Attribute(
1424 1424 """Main ui instance for this instance.""")
1425 1425
1426 1426 sharedpath = interfaceutil.Attribute(
1427 1427 """Path to the .hg directory of the repo this repo was shared from.""")
1428 1428
1429 1429 store = interfaceutil.Attribute(
1430 1430 """A store instance.""")
1431 1431
1432 1432 spath = interfaceutil.Attribute(
1433 1433 """Path to the store.""")
1434 1434
1435 1435 sjoin = interfaceutil.Attribute(
1436 1436 """Alias to self.store.join.""")
1437 1437
1438 1438 cachevfs = interfaceutil.Attribute(
1439 1439 """A VFS used to access the cache directory.
1440 1440
1441 1441 Typically .hg/cache.
1442 1442 """)
1443 1443
1444 1444 wcachevfs = interfaceutil.Attribute(
1445 1445 """A VFS used to access the cache directory dedicated to working copy
1446 1446
1447 1447 Typically .hg/wcache.
1448 1448 """)
1449 1449
1450 1450 filteredrevcache = interfaceutil.Attribute(
1451 1451 """Holds sets of revisions to be filtered.""")
1452 1452
1453 1453 names = interfaceutil.Attribute(
1454 1454 """A ``namespaces`` instance.""")
1455 1455
1456 1456 def close():
1457 1457 """Close the handle on this repository."""
1458 1458
1459 1459 def peer():
1460 1460 """Obtain an object conforming to the ``peer`` interface."""
1461 1461
1462 1462 def unfiltered():
1463 1463 """Obtain an unfiltered/raw view of this repo."""
1464 1464
1465 1465 def filtered(name, visibilityexceptions=None):
1466 1466 """Obtain a named view of this repository."""
1467 1467
1468 1468 obsstore = interfaceutil.Attribute(
1469 1469 """A store of obsolescence data.""")
1470 1470
1471 1471 changelog = interfaceutil.Attribute(
1472 1472 """A handle on the changelog revlog.""")
1473 1473
1474 1474 manifestlog = interfaceutil.Attribute(
1475 1475 """An instance conforming to the ``imanifestlog`` interface.
1476 1476
1477 1477 Provides access to manifests for the repository.
1478 1478 """)
1479 1479
1480 1480 dirstate = interfaceutil.Attribute(
1481 1481 """Working directory state.""")
1482 1482
1483 1483 narrowpats = interfaceutil.Attribute(
1484 1484 """Matcher patterns for this repository's narrowspec.""")
1485 1485
1486 1486 def narrowmatch(match=None, includeexact=False):
1487 1487 """Obtain a matcher for the narrowspec."""
1488 1488
1489 1489 def setnarrowpats(newincludes, newexcludes):
1490 1490 """Define the narrowspec for this repository."""
1491 1491
1492 1492 def __getitem__(changeid):
1493 1493 """Try to resolve a changectx."""
1494 1494
1495 1495 def __contains__(changeid):
1496 1496 """Whether a changeset exists."""
1497 1497
1498 1498 def __nonzero__():
1499 1499 """Always returns True."""
1500 1500 return True
1501 1501
1502 1502 __bool__ = __nonzero__
1503 1503
1504 1504 def __len__():
1505 1505 """Returns the number of changesets in the repo."""
1506 1506
1507 1507 def __iter__():
1508 1508 """Iterate over revisions in the changelog."""
1509 1509
1510 1510 def revs(expr, *args):
1511 1511 """Evaluate a revset.
1512 1512
1513 1513 Emits revisions.
1514 1514 """
1515 1515
1516 1516 def set(expr, *args):
1517 1517 """Evaluate a revset.
1518 1518
1519 1519 Emits changectx instances.
1520 1520 """
1521 1521
1522 1522 def anyrevs(specs, user=False, localalias=None):
1523 1523 """Find revisions matching one of the given revsets."""
1524 1524
1525 1525 def url():
1526 1526 """Returns a string representing the location of this repo."""
1527 1527
1528 1528 def hook(name, throw=False, **args):
1529 1529 """Call a hook."""
1530 1530
1531 1531 def tags():
1532 1532 """Return a mapping of tag to node."""
1533 1533
1534 1534 def tagtype(tagname):
1535 1535 """Return the type of a given tag."""
1536 1536
1537 1537 def tagslist():
1538 1538 """Return a list of tags ordered by revision."""
1539 1539
1540 1540 def nodetags(node):
1541 1541 """Return the tags associated with a node."""
1542 1542
1543 1543 def nodebookmarks(node):
1544 1544 """Return the list of bookmarks pointing to the specified node."""
1545 1545
1546 1546 def branchmap():
1547 1547 """Return a mapping of branch to heads in that branch."""
1548 1548
1549 1549 def revbranchcache():
1550 1550 pass
1551 1551
1552 1552 def branchtip(branchtip, ignoremissing=False):
1553 1553 """Return the tip node for a given branch."""
1554 1554
1555 1555 def lookup(key):
1556 1556 """Resolve the node for a revision."""
1557 1557
1558 1558 def lookupbranch(key):
1559 1559 """Look up the branch name of the given revision or branch name."""
1560 1560
1561 1561 def known(nodes):
1562 1562 """Determine whether a series of nodes is known.
1563 1563
1564 1564 Returns a list of bools.
1565 1565 """
1566 1566
1567 1567 def local():
1568 1568 """Whether the repository is local."""
1569 1569 return True
1570 1570
1571 1571 def publishing():
1572 1572 """Whether the repository is a publishing repository."""
1573 1573
1574 1574 def cancopy():
1575 1575 pass
1576 1576
1577 1577 def shared():
1578 1578 """The type of shared repository or None."""
1579 1579
1580 1580 def wjoin(f, *insidef):
1581 1581 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1582 1582
1583 1583 def setparents(p1, p2):
1584 1584 """Set the parent nodes of the working directory."""
1585 1585
1586 1586 def filectx(path, changeid=None, fileid=None):
1587 1587 """Obtain a filectx for the given file revision."""
1588 1588
1589 1589 def getcwd():
1590 1590 """Obtain the current working directory from the dirstate."""
1591 1591
1592 1592 def pathto(f, cwd=None):
1593 1593 """Obtain the relative path to a file."""
1594 1594
1595 1595 def adddatafilter(name, fltr):
1596 1596 pass
1597 1597
1598 1598 def wread(filename):
1599 1599 """Read a file from wvfs, using data filters."""
1600 1600
1601 1601 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1602 1602 """Write data to a file in the wvfs, using data filters."""
1603 1603
1604 1604 def wwritedata(filename, data):
1605 1605 """Resolve data for writing to the wvfs, using data filters."""
1606 1606
1607 1607 def currenttransaction():
1608 1608 """Obtain the current transaction instance or None."""
1609 1609
1610 1610 def transaction(desc, report=None):
1611 1611 """Open a new transaction to write to the repository."""
1612 1612
1613 1613 def undofiles():
1614 1614 """Returns a list of (vfs, path) for files to undo transactions."""
1615 1615
1616 1616 def recover():
1617 1617 """Roll back an interrupted transaction."""
1618 1618
1619 1619 def rollback(dryrun=False, force=False):
1620 1620 """Undo the last transaction.
1621 1621
1622 1622 DANGEROUS.
1623 1623 """
1624 1624
1625 1625 def updatecaches(tr=None, full=False):
1626 1626 """Warm repo caches."""
1627 1627
1628 1628 def invalidatecaches():
1629 1629 """Invalidate cached data due to the repository mutating."""
1630 1630
1631 1631 def invalidatevolatilesets():
1632 1632 pass
1633 1633
1634 1634 def invalidatedirstate():
1635 1635 """Invalidate the dirstate."""
1636 1636
1637 1637 def invalidate(clearfilecache=False):
1638 1638 pass
1639 1639
1640 1640 def invalidateall():
1641 1641 pass
1642 1642
1643 1643 def lock(wait=True):
1644 1644 """Lock the repository store and return a lock instance."""
1645 1645
1646 1646 def wlock(wait=True):
1647 1647 """Lock the non-store parts of the repository."""
1648 1648
1649 1649 def currentwlock():
1650 1650 """Return the wlock if it's held or None."""
1651 1651
1652 1652 def checkcommitpatterns(wctx, vdirs, match, status, fail):
1653 1653 pass
1654 1654
1655 1655 def commit(text='', user=None, date=None, match=None, force=False,
1656 1656 editor=False, extra=None):
1657 1657 """Add a new revision to the repository."""
1658 1658
1659 def commitctx(ctx, error=False):
1659 def commitctx(ctx, error=False, origctx=None):
1660 1660 """Commit a commitctx instance to the repository."""
1661 1661
1662 1662 def destroying():
1663 1663 """Inform the repository that nodes are about to be destroyed."""
1664 1664
1665 1665 def destroyed():
1666 1666 """Inform the repository that nodes have been destroyed."""
1667 1667
1668 1668 def status(node1='.', node2=None, match=None, ignored=False,
1669 1669 clean=False, unknown=False, listsubrepos=False):
1670 1670 """Convenience method to call repo[x].status()."""
1671 1671
1672 1672 def addpostdsstatus(ps):
1673 1673 pass
1674 1674
1675 1675 def postdsstatus():
1676 1676 pass
1677 1677
1678 1678 def clearpostdsstatus():
1679 1679 pass
1680 1680
1681 1681 def heads(start=None):
1682 1682 """Obtain list of nodes that are DAG heads."""
1683 1683
1684 1684 def branchheads(branch=None, start=None, closed=False):
1685 1685 pass
1686 1686
1687 1687 def branches(nodes):
1688 1688 pass
1689 1689
1690 1690 def between(pairs):
1691 1691 pass
1692 1692
1693 1693 def checkpush(pushop):
1694 1694 pass
1695 1695
1696 1696 prepushoutgoinghooks = interfaceutil.Attribute(
1697 1697 """util.hooks instance.""")
1698 1698
1699 1699 def pushkey(namespace, key, old, new):
1700 1700 pass
1701 1701
1702 1702 def listkeys(namespace):
1703 1703 pass
1704 1704
1705 1705 def debugwireargs(one, two, three=None, four=None, five=None):
1706 1706 pass
1707 1707
1708 1708 def savecommitmessage(text):
1709 1709 pass
1710 1710
1711 1711 class completelocalrepository(ilocalrepositorymain,
1712 1712 ilocalrepositoryfilestorage):
1713 1713 """Complete interface for a local repository."""
1714 1714
1715 1715 class iwireprotocolcommandcacher(interfaceutil.Interface):
1716 1716 """Represents a caching backend for wire protocol commands.
1717 1717
1718 1718 Wire protocol version 2 supports transparent caching of many commands.
1719 1719 To leverage this caching, servers can activate objects that cache
1720 1720 command responses. Objects handle both cache writing and reading.
1721 1721 This interface defines how that response caching mechanism works.
1722 1722
1723 1723 Wire protocol version 2 commands emit a series of objects that are
1724 1724 serialized and sent to the client. The caching layer exists between
1725 1725 the invocation of the command function and the sending of its output
1726 1726 objects to an output layer.
1727 1727
1728 1728 Instances of this interface represent a binding to a cache that
1729 1729 can serve a response (in place of calling a command function) and/or
1730 1730 write responses to a cache for subsequent use.
1731 1731
1732 1732 When a command request arrives, the following happens with regards
1733 1733 to this interface:
1734 1734
1735 1735 1. The server determines whether the command request is cacheable.
1736 1736 2. If it is, an instance of this interface is spawned.
1737 1737 3. The cacher is activated in a context manager (``__enter__`` is called).
1738 1738 4. A cache *key* for that request is derived. This will call the
1739 1739 instance's ``adjustcachekeystate()`` method so the derivation
1740 1740 can be influenced.
1741 1741 5. The cacher is informed of the derived cache key via a call to
1742 1742 ``setcachekey()``.
1743 1743 6. The cacher's ``lookup()`` method is called to test for presence of
1744 1744 the derived key in the cache.
1745 1745 7. If ``lookup()`` returns a hit, that cached result is used in place
1746 1746 of invoking the command function. ``__exit__`` is called and the instance
1747 1747 is discarded.
1748 1748 8. The command function is invoked.
1749 1749 9. ``onobject()`` is called for each object emitted by the command
1750 1750 function.
1751 1751 10. After the final object is seen, ``onfinished()`` is called.
1752 1752 11. ``__exit__`` is called to signal the end of use of the instance.
1753 1753
1754 1754 Cache *key* derivation can be influenced by the instance.
1755 1755
1756 1756 Cache keys are initially derived by a deterministic representation of
1757 1757 the command request. This includes the command name, arguments, protocol
1758 1758 version, etc. This initial key derivation is performed by CBOR-encoding a
1759 1759 data structure and feeding that output into a hasher.
1760 1760
1761 1761 Instances of this interface can influence this initial key derivation
1762 1762 via ``adjustcachekeystate()``.
1763 1763
1764 1764 The instance is informed of the derived cache key via a call to
1765 1765 ``setcachekey()``. The instance must store the key locally so it can
1766 1766 be consulted on subsequent operations that may require it.
1767 1767
1768 1768 When constructed, the instance has access to a callable that can be used
1769 1769 for encoding response objects. This callable receives as its single
1770 1770 argument an object emitted by a command function. It returns an iterable
1771 1771 of bytes chunks representing the encoded object. Unless the cacher is
1772 1772 caching native Python objects in memory or has a way of reconstructing
1773 1773 the original Python objects, implementations typically call this function
1774 1774 to produce bytes from the output objects and then store those bytes in
1775 1775 the cache. When it comes time to re-emit those bytes, they are wrapped
1776 1776 in a ``wireprototypes.encodedresponse`` instance to tell the output
1777 1777 layer that they are pre-encoded.
1778 1778
1779 1779 When receiving the objects emitted by the command function, instances
1780 1780 can choose what to do with those objects. The simplest thing to do is
1781 1781 re-emit the original objects. They will be forwarded to the output
1782 1782 layer and will be processed as if the cacher did not exist.
1783 1783
1784 1784 Implementations could also choose to not emit objects - instead locally
1785 1785 buffering objects or their encoded representation. They could then emit
1786 1786 a single "coalesced" object when ``onfinished()`` is called. In
1787 1787 this way, the implementation would function as a filtering layer of
1788 1788 sorts.
1789 1789
1790 1790 When caching objects, typically the encoded form of the object will
1791 1791 be stored. Keep in mind that if the original object is forwarded to
1792 1792 the output layer, it will need to be encoded there as well. For large
1793 1793 output, this redundant encoding could add overhead. Implementations
1794 1794 could wrap the encoded object data in ``wireprototypes.encodedresponse``
1795 1795 instances to avoid this overhead.
1796 1796 """
1797 1797 def __enter__():
1798 1798 """Marks the instance as active.
1799 1799
1800 1800 Should return self.
1801 1801 """
1802 1802
1803 1803 def __exit__(exctype, excvalue, exctb):
1804 1804 """Called when cacher is no longer used.
1805 1805
1806 1806 This can be used by implementations to perform cleanup actions (e.g.
1807 1807 disconnecting network sockets, aborting a partially cached response.
1808 1808 """
1809 1809
1810 1810 def adjustcachekeystate(state):
1811 1811 """Influences cache key derivation by adjusting state to derive key.
1812 1812
1813 1813 A dict defining the state used to derive the cache key is passed.
1814 1814
1815 1815 Implementations can modify this dict to record additional state that
1816 1816 is wanted to influence key derivation.
1817 1817
1818 1818 Implementations are *highly* encouraged to not modify or delete
1819 1819 existing keys.
1820 1820 """
1821 1821
1822 1822 def setcachekey(key):
1823 1823 """Record the derived cache key for this request.
1824 1824
1825 1825 Instances may mutate the key for internal usage, as desired. e.g.
1826 1826 instances may wish to prepend the repo name, introduce path
1827 1827 components for filesystem or URL addressing, etc. Behavior is up to
1828 1828 the cache.
1829 1829
1830 1830 Returns a bool indicating if the request is cacheable by this
1831 1831 instance.
1832 1832 """
1833 1833
1834 1834 def lookup():
1835 1835 """Attempt to resolve an entry in the cache.
1836 1836
1837 1837 The instance is instructed to look for the cache key that it was
1838 1838 informed about via the call to ``setcachekey()``.
1839 1839
1840 1840 If there's no cache hit or the cacher doesn't wish to use the cached
1841 1841 entry, ``None`` should be returned.
1842 1842
1843 1843 Else, a dict defining the cached result should be returned. The
1844 1844 dict may have the following keys:
1845 1845
1846 1846 objs
1847 1847 An iterable of objects that should be sent to the client. That
1848 1848 iterable of objects is expected to be what the command function
1849 1849 would return if invoked or an equivalent representation thereof.
1850 1850 """
1851 1851
1852 1852 def onobject(obj):
1853 1853 """Called when a new object is emitted from the command function.
1854 1854
1855 1855 Receives as its argument the object that was emitted from the
1856 1856 command function.
1857 1857
1858 1858 This method returns an iterator of objects to forward to the output
1859 1859 layer. The easiest implementation is a generator that just
1860 1860 ``yield obj``.
1861 1861 """
1862 1862
1863 1863 def onfinished():
1864 1864 """Called after all objects have been emitted from the command function.
1865 1865
1866 1866 Implementations should return an iterator of objects to forward to
1867 1867 the output layer.
1868 1868
1869 1869 This method can be a generator.
1870 1870 """
@@ -1,1105 +1,1105 b''
1 1 #if windows
2 2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 3 #else
4 4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 5 #endif
6 6 $ export PYTHONPATH
7 7
8 8 typical client does not want echo-back messages, so test without it:
9 9
10 10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 11 $ mv $HGRCPATH.new $HGRCPATH
12 12
13 13 $ hg init repo
14 14 $ cd repo
15 15
16 16 >>> from __future__ import absolute_import
17 17 >>> import os
18 18 >>> import sys
19 19 >>> from hgclient import bprint, check, readchannel, runcommand
20 20 >>> @check
21 21 ... def hellomessage(server):
22 22 ... ch, data = readchannel(server)
23 23 ... bprint(b'%c, %r' % (ch, data))
24 24 ... # run an arbitrary command to make sure the next thing the server
25 25 ... # sends isn't part of the hello message
26 26 ... runcommand(server, [b'id'])
27 27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
28 28 *** runcommand id
29 29 000000000000 tip
30 30
31 31 >>> from hgclient import check
32 32 >>> @check
33 33 ... def unknowncommand(server):
34 34 ... server.stdin.write(b'unknowncommand\n')
35 35 abort: unknown command unknowncommand
36 36
37 37 >>> from hgclient import check, readchannel, runcommand
38 38 >>> @check
39 39 ... def checkruncommand(server):
40 40 ... # hello block
41 41 ... readchannel(server)
42 42 ...
43 43 ... # no args
44 44 ... runcommand(server, [])
45 45 ...
46 46 ... # global options
47 47 ... runcommand(server, [b'id', b'--quiet'])
48 48 ...
49 49 ... # make sure global options don't stick through requests
50 50 ... runcommand(server, [b'id'])
51 51 ...
52 52 ... # --config
53 53 ... runcommand(server, [b'id', b'--config', b'ui.quiet=True'])
54 54 ...
55 55 ... # make sure --config doesn't stick
56 56 ... runcommand(server, [b'id'])
57 57 ...
58 58 ... # negative return code should be masked
59 59 ... runcommand(server, [b'id', b'-runknown'])
60 60 *** runcommand
61 61 Mercurial Distributed SCM
62 62
63 63 basic commands:
64 64
65 65 add add the specified files on the next commit
66 66 annotate show changeset information by line for each file
67 67 clone make a copy of an existing repository
68 68 commit commit the specified files or all outstanding changes
69 69 diff diff repository (or selected files)
70 70 export dump the header and diffs for one or more changesets
71 71 forget forget the specified files on the next commit
72 72 init create a new repository in the given directory
73 73 log show revision history of entire repository or files
74 74 merge merge another revision into working directory
75 75 pull pull changes from the specified source
76 76 push push changes to the specified destination
77 77 remove remove the specified files on the next commit
78 78 serve start stand-alone webserver
79 79 status show changed files in the working directory
80 80 summary summarize working directory state
81 81 update update working directory (or switch revisions)
82 82
83 83 (use 'hg help' for the full list of commands or 'hg -v' for details)
84 84 *** runcommand id --quiet
85 85 000000000000
86 86 *** runcommand id
87 87 000000000000 tip
88 88 *** runcommand id --config ui.quiet=True
89 89 000000000000
90 90 *** runcommand id
91 91 000000000000 tip
92 92 *** runcommand id -runknown
93 93 abort: unknown revision 'unknown'!
94 94 [255]
95 95
96 96 >>> from hgclient import bprint, check, readchannel
97 97 >>> @check
98 98 ... def inputeof(server):
99 99 ... readchannel(server)
100 100 ... server.stdin.write(b'runcommand\n')
101 101 ... # close stdin while server is waiting for input
102 102 ... server.stdin.close()
103 103 ...
104 104 ... # server exits with 1 if the pipe closed while reading the command
105 105 ... bprint(b'server exit code =', b'%d' % server.wait())
106 106 server exit code = 1
107 107
108 108 >>> from hgclient import check, readchannel, runcommand, stringio
109 109 >>> @check
110 110 ... def serverinput(server):
111 111 ... readchannel(server)
112 112 ...
113 113 ... patch = b"""
114 114 ... # HG changeset patch
115 115 ... # User test
116 116 ... # Date 0 0
117 117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
118 118 ... # Parent 0000000000000000000000000000000000000000
119 119 ... 1
120 120 ...
121 121 ... diff -r 000000000000 -r c103a3dec114 a
122 122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123 123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 124 ... @@ -0,0 +1,1 @@
125 125 ... +1
126 126 ... """
127 127 ...
128 128 ... runcommand(server, [b'import', b'-'], input=stringio(patch))
129 129 ... runcommand(server, [b'log'])
130 130 *** runcommand import -
131 131 applying patch from stdin
132 132 *** runcommand log
133 133 changeset: 0:eff892de26ec
134 134 tag: tip
135 135 user: test
136 136 date: Thu Jan 01 00:00:00 1970 +0000
137 137 summary: 1
138 138
139 139
140 140 check strict parsing of early options:
141 141
142 142 >>> import os
143 143 >>> from hgclient import check, readchannel, runcommand
144 144 >>> os.environ['HGPLAIN'] = '+strictflags'
145 145 >>> @check
146 146 ... def cwd(server):
147 147 ... readchannel(server)
148 148 ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned',
149 149 ... b'default'])
150 150 *** runcommand log -b --config=alias.log=!echo pwned default
151 151 abort: unknown revision '--config=alias.log=!echo pwned'!
152 152 [255]
153 153
154 154 check that "histedit --commands=-" can read rules from the input channel:
155 155
156 156 >>> from hgclient import check, readchannel, runcommand, stringio
157 157 >>> @check
158 158 ... def serverinput(server):
159 159 ... readchannel(server)
160 160 ... rules = b'pick eff892de26ec\n'
161 161 ... runcommand(server, [b'histedit', b'0', b'--commands=-',
162 162 ... b'--config', b'extensions.histedit='],
163 163 ... input=stringio(rules))
164 164 *** runcommand histedit 0 --commands=- --config extensions.histedit=
165 165
166 166 check that --cwd doesn't persist between requests:
167 167
168 168 $ mkdir foo
169 169 $ touch foo/bar
170 170 >>> from hgclient import check, readchannel, runcommand
171 171 >>> @check
172 172 ... def cwd(server):
173 173 ... readchannel(server)
174 174 ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar'])
175 175 ... runcommand(server, [b'st', b'foo/bar'])
176 176 *** runcommand --cwd foo st bar
177 177 ? bar
178 178 *** runcommand st foo/bar
179 179 ? foo/bar
180 180
181 181 $ rm foo/bar
182 182
183 183
184 184 check that local configs for the cached repo aren't inherited when -R is used:
185 185
186 186 $ cat <<EOF >> .hg/hgrc
187 187 > [ui]
188 188 > foo = bar
189 189 > EOF
190 190
191 191 #if no-extraextensions
192 192
193 193 >>> from hgclient import check, readchannel, runcommand, sep
194 194 >>> @check
195 195 ... def localhgrc(server):
196 196 ... readchannel(server)
197 197 ...
198 198 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
199 199 ... # show it
200 200 ... runcommand(server, [b'showconfig'], outfilter=sep)
201 201 ...
202 202 ... # but not for this repo
203 203 ... runcommand(server, [b'init', b'foo'])
204 204 ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults'])
205 205 *** runcommand showconfig
206 206 bundle.mainreporoot=$TESTTMP/repo
207 207 devel.all-warnings=true
208 208 devel.default-date=0 0
209 209 extensions.fsmonitor= (fsmonitor !)
210 210 largefiles.usercache=$TESTTMP/.cache/largefiles
211 211 lfs.usercache=$TESTTMP/.cache/lfs
212 212 ui.slash=True
213 213 ui.interactive=False
214 214 ui.merge=internal:merge
215 215 ui.mergemarkers=detailed
216 216 ui.foo=bar
217 217 ui.nontty=true
218 218 web.address=localhost
219 219 web\.ipv6=(?:True|False) (re)
220 220 web.server-header=testing stub value
221 221 *** runcommand init foo
222 222 *** runcommand -R foo showconfig ui defaults
223 223 ui.slash=True
224 224 ui.interactive=False
225 225 ui.merge=internal:merge
226 226 ui.mergemarkers=detailed
227 227 ui.nontty=true
228 228 #endif
229 229
230 230 $ rm -R foo
231 231
232 232 #if windows
233 233 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
234 234 #else
235 235 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
236 236 #endif
237 237
238 238 $ cat <<EOF > hook.py
239 239 > import sys
240 240 > from hgclient import bprint
241 241 > def hook(**args):
242 242 > bprint(b'hook talking')
243 243 > bprint(b'now try to read something: %r' % sys.stdin.read())
244 244 > EOF
245 245
246 246 >>> from hgclient import check, readchannel, runcommand, stringio
247 247 >>> @check
248 248 ... def hookoutput(server):
249 249 ... readchannel(server)
250 250 ... runcommand(server, [b'--config',
251 251 ... b'hooks.pre-identify=python:hook.hook',
252 252 ... b'id'],
253 253 ... input=stringio(b'some input'))
254 254 *** runcommand --config hooks.pre-identify=python:hook.hook id
255 255 eff892de26ec tip
256 256 hook talking
257 257 now try to read something: ''
258 258
259 259 Clean hook cached version
260 260 $ rm hook.py*
261 261 $ rm -Rf __pycache__
262 262
263 263 $ echo a >> a
264 264 >>> import os
265 265 >>> from hgclient import check, readchannel, runcommand
266 266 >>> @check
267 267 ... def outsidechanges(server):
268 268 ... readchannel(server)
269 269 ... runcommand(server, [b'status'])
270 270 ... os.system('hg ci -Am2')
271 271 ... runcommand(server, [b'tip'])
272 272 ... runcommand(server, [b'status'])
273 273 *** runcommand status
274 274 M a
275 275 *** runcommand tip
276 276 changeset: 1:d3a0a68be6de
277 277 tag: tip
278 278 user: test
279 279 date: Thu Jan 01 00:00:00 1970 +0000
280 280 summary: 2
281 281
282 282 *** runcommand status
283 283
284 284 >>> import os
285 285 >>> from hgclient import bprint, check, readchannel, runcommand
286 286 >>> @check
287 287 ... def bookmarks(server):
288 288 ... readchannel(server)
289 289 ... runcommand(server, [b'bookmarks'])
290 290 ...
291 291 ... # changes .hg/bookmarks
292 292 ... os.system('hg bookmark -i bm1')
293 293 ... os.system('hg bookmark -i bm2')
294 294 ... runcommand(server, [b'bookmarks'])
295 295 ...
296 296 ... # changes .hg/bookmarks.current
297 297 ... os.system('hg upd bm1 -q')
298 298 ... runcommand(server, [b'bookmarks'])
299 299 ...
300 300 ... runcommand(server, [b'bookmarks', b'bm3'])
301 301 ... f = open('a', 'ab')
302 302 ... f.write(b'a\n') and None
303 303 ... f.close()
304 304 ... runcommand(server, [b'commit', b'-Amm'])
305 305 ... runcommand(server, [b'bookmarks'])
306 306 ... bprint(b'')
307 307 *** runcommand bookmarks
308 308 no bookmarks set
309 309 *** runcommand bookmarks
310 310 bm1 1:d3a0a68be6de
311 311 bm2 1:d3a0a68be6de
312 312 *** runcommand bookmarks
313 313 * bm1 1:d3a0a68be6de
314 314 bm2 1:d3a0a68be6de
315 315 *** runcommand bookmarks bm3
316 316 *** runcommand commit -Amm
317 317 *** runcommand bookmarks
318 318 bm1 1:d3a0a68be6de
319 319 bm2 1:d3a0a68be6de
320 320 * bm3 2:aef17e88f5f0
321 321
322 322
323 323 >>> import os
324 324 >>> from hgclient import check, readchannel, runcommand
325 325 >>> @check
326 326 ... def tagscache(server):
327 327 ... readchannel(server)
328 328 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
329 329 ... os.system('hg tag -r 0 foo')
330 330 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
331 331 *** runcommand id -t -r 0
332 332
333 333 *** runcommand id -t -r 0
334 334 foo
335 335
336 336 >>> import os
337 337 >>> from hgclient import check, readchannel, runcommand
338 338 >>> @check
339 339 ... def setphase(server):
340 340 ... readchannel(server)
341 341 ... runcommand(server, [b'phase', b'-r', b'.'])
342 342 ... os.system('hg phase -r . -p')
343 343 ... runcommand(server, [b'phase', b'-r', b'.'])
344 344 *** runcommand phase -r .
345 345 3: draft
346 346 *** runcommand phase -r .
347 347 3: public
348 348
349 349 $ echo a >> a
350 350 >>> from hgclient import bprint, check, readchannel, runcommand
351 351 >>> @check
352 352 ... def rollback(server):
353 353 ... readchannel(server)
354 354 ... runcommand(server, [b'phase', b'-r', b'.', b'-p'])
355 355 ... runcommand(server, [b'commit', b'-Am.'])
356 356 ... runcommand(server, [b'rollback'])
357 357 ... runcommand(server, [b'phase', b'-r', b'.'])
358 358 ... bprint(b'')
359 359 *** runcommand phase -r . -p
360 360 no phases changed
361 361 *** runcommand commit -Am.
362 362 *** runcommand rollback
363 363 repository tip rolled back to revision 3 (undo commit)
364 364 working directory now based on revision 3
365 365 *** runcommand phase -r .
366 366 3: public
367 367
368 368
369 369 >>> import os
370 370 >>> from hgclient import check, readchannel, runcommand
371 371 >>> @check
372 372 ... def branch(server):
373 373 ... readchannel(server)
374 374 ... runcommand(server, [b'branch'])
375 375 ... os.system('hg branch foo')
376 376 ... runcommand(server, [b'branch'])
377 377 ... os.system('hg branch default')
378 378 *** runcommand branch
379 379 default
380 380 marked working directory as branch foo
381 381 (branches are permanent and global, did you want a bookmark?)
382 382 *** runcommand branch
383 383 foo
384 384 marked working directory as branch default
385 385 (branches are permanent and global, did you want a bookmark?)
386 386
387 387 $ touch .hgignore
388 388 >>> import os
389 389 >>> from hgclient import bprint, check, readchannel, runcommand
390 390 >>> @check
391 391 ... def hgignore(server):
392 392 ... readchannel(server)
393 393 ... runcommand(server, [b'commit', b'-Am.'])
394 394 ... f = open('ignored-file', 'ab')
395 395 ... f.write(b'') and None
396 396 ... f.close()
397 397 ... f = open('.hgignore', 'ab')
398 398 ... f.write(b'ignored-file')
399 399 ... f.close()
400 400 ... runcommand(server, [b'status', b'-i', b'-u'])
401 401 ... bprint(b'')
402 402 *** runcommand commit -Am.
403 403 adding .hgignore
404 404 *** runcommand status -i -u
405 405 I ignored-file
406 406
407 407
408 408 cache of non-public revisions should be invalidated on repository change
409 409 (issue4855):
410 410
411 411 >>> import os
412 412 >>> from hgclient import bprint, check, readchannel, runcommand
413 413 >>> @check
414 414 ... def phasesetscacheaftercommit(server):
415 415 ... readchannel(server)
416 416 ... # load _phasecache._phaserevs and _phasesets
417 417 ... runcommand(server, [b'log', b'-qr', b'draft()'])
418 418 ... # create draft commits by another process
419 419 ... for i in range(5, 7):
420 420 ... f = open('a', 'ab')
421 421 ... f.seek(0, os.SEEK_END)
422 422 ... f.write(b'a\n') and None
423 423 ... f.close()
424 424 ... os.system('hg commit -Aqm%d' % i)
425 425 ... # new commits should be listed as draft revisions
426 426 ... runcommand(server, [b'log', b'-qr', b'draft()'])
427 427 ... bprint(b'')
428 428 *** runcommand log -qr draft()
429 429 4:7966c8e3734d
430 430 *** runcommand log -qr draft()
431 431 4:7966c8e3734d
432 432 5:41f6602d1c4f
433 433 6:10501e202c35
434 434
435 435
436 436 >>> import os
437 437 >>> from hgclient import bprint, check, readchannel, runcommand
438 438 >>> @check
439 439 ... def phasesetscacheafterstrip(server):
440 440 ... readchannel(server)
441 441 ... # load _phasecache._phaserevs and _phasesets
442 442 ... runcommand(server, [b'log', b'-qr', b'draft()'])
443 443 ... # strip cached revisions by another process
444 444 ... os.system('hg --config extensions.strip= strip -q 5')
445 445 ... # shouldn't abort by "unknown revision '6'"
446 446 ... runcommand(server, [b'log', b'-qr', b'draft()'])
447 447 ... bprint(b'')
448 448 *** runcommand log -qr draft()
449 449 4:7966c8e3734d
450 450 5:41f6602d1c4f
451 451 6:10501e202c35
452 452 *** runcommand log -qr draft()
453 453 4:7966c8e3734d
454 454
455 455
456 456 cache of phase roots should be invalidated on strip (issue3827):
457 457
458 458 >>> import os
459 459 >>> from hgclient import check, readchannel, runcommand, sep
460 460 >>> @check
461 461 ... def phasecacheafterstrip(server):
462 462 ... readchannel(server)
463 463 ...
464 464 ... # create new head, 5:731265503d86
465 465 ... runcommand(server, [b'update', b'-C', b'0'])
466 466 ... f = open('a', 'ab')
467 467 ... f.write(b'a\n') and None
468 468 ... f.close()
469 469 ... runcommand(server, [b'commit', b'-Am.', b'a'])
470 470 ... runcommand(server, [b'log', b'-Gq'])
471 471 ...
472 472 ... # make it public; draft marker moves to 4:7966c8e3734d
473 473 ... runcommand(server, [b'phase', b'-p', b'.'])
474 474 ... # load _phasecache.phaseroots
475 475 ... runcommand(server, [b'phase', b'.'], outfilter=sep)
476 476 ...
477 477 ... # strip 1::4 outside server
478 478 ... os.system('hg -q --config extensions.mq= strip 1')
479 479 ...
480 480 ... # shouldn't raise "7966c8e3734d: no node!"
481 481 ... runcommand(server, [b'branches'])
482 482 *** runcommand update -C 0
483 483 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
484 484 (leaving bookmark bm3)
485 485 *** runcommand commit -Am. a
486 486 created new head
487 487 *** runcommand log -Gq
488 488 @ 5:731265503d86
489 489 |
490 490 | o 4:7966c8e3734d
491 491 | |
492 492 | o 3:b9b85890c400
493 493 | |
494 494 | o 2:aef17e88f5f0
495 495 | |
496 496 | o 1:d3a0a68be6de
497 497 |/
498 498 o 0:eff892de26ec
499 499
500 500 *** runcommand phase -p .
501 501 *** runcommand phase .
502 502 5: public
503 503 *** runcommand branches
504 504 default 1:731265503d86
505 505
506 506 in-memory cache must be reloaded if transaction is aborted. otherwise
507 507 changelog and manifest would have invalid node:
508 508
509 509 $ echo a >> a
510 510 >>> from hgclient import check, readchannel, runcommand
511 511 >>> @check
512 512 ... def txabort(server):
513 513 ... readchannel(server)
514 514 ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false',
515 515 ... b'-mfoo'])
516 516 ... runcommand(server, [b'verify'])
517 517 *** runcommand commit --config hooks.pretxncommit=false -mfoo
518 518 transaction abort!
519 519 rollback completed
520 520 abort: pretxncommit hook exited with status 1
521 521 [255]
522 522 *** runcommand verify
523 523 checking changesets
524 524 checking manifests
525 525 crosschecking files in changesets and manifests
526 526 checking files
527 527 checked 2 changesets with 2 changes to 1 files
528 528 $ hg revert --no-backup -aq
529 529
530 530 $ cat >> .hg/hgrc << EOF
531 531 > [experimental]
532 532 > evolution.createmarkers=True
533 533 > EOF
534 534
535 535 >>> import os
536 536 >>> from hgclient import check, readchannel, runcommand
537 537 >>> @check
538 538 ... def obsolete(server):
539 539 ... readchannel(server)
540 540 ...
541 541 ... runcommand(server, [b'up', b'null'])
542 542 ... runcommand(server, [b'phase', b'-df', b'tip'])
543 543 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
544 544 ... if os.name == 'nt':
545 545 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
546 546 ... os.system(cmd)
547 547 ... runcommand(server, [b'log', b'--hidden'])
548 548 ... runcommand(server, [b'log'])
549 549 *** runcommand up null
550 550 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
551 551 *** runcommand phase -df tip
552 552 obsoleted 1 changesets
553 553 *** runcommand log --hidden
554 554 changeset: 1:731265503d86
555 555 tag: tip
556 556 user: test
557 557 date: Thu Jan 01 00:00:00 1970 +0000
558 558 obsolete: pruned
559 559 summary: .
560 560
561 561 changeset: 0:eff892de26ec
562 562 bookmark: bm1
563 563 bookmark: bm2
564 564 bookmark: bm3
565 565 user: test
566 566 date: Thu Jan 01 00:00:00 1970 +0000
567 567 summary: 1
568 568
569 569 *** runcommand log
570 570 changeset: 0:eff892de26ec
571 571 bookmark: bm1
572 572 bookmark: bm2
573 573 bookmark: bm3
574 574 tag: tip
575 575 user: test
576 576 date: Thu Jan 01 00:00:00 1970 +0000
577 577 summary: 1
578 578
579 579
580 580 $ cat <<EOF >> .hg/hgrc
581 581 > [extensions]
582 582 > mq =
583 583 > EOF
584 584
585 585 >>> import os
586 586 >>> from hgclient import check, readchannel, runcommand
587 587 >>> @check
588 588 ... def mqoutsidechanges(server):
589 589 ... readchannel(server)
590 590 ...
591 591 ... # load repo.mq
592 592 ... runcommand(server, [b'qapplied'])
593 593 ... os.system('hg qnew 0.diff')
594 594 ... # repo.mq should be invalidated
595 595 ... runcommand(server, [b'qapplied'])
596 596 ...
597 597 ... runcommand(server, [b'qpop', b'--all'])
598 598 ... os.system('hg qqueue --create foo')
599 599 ... # repo.mq should be recreated to point to new queue
600 600 ... runcommand(server, [b'qqueue', b'--active'])
601 601 *** runcommand qapplied
602 602 *** runcommand qapplied
603 603 0.diff
604 604 *** runcommand qpop --all
605 605 popping 0.diff
606 606 patch queue now empty
607 607 *** runcommand qqueue --active
608 608 foo
609 609
610 610 $ cat <<'EOF' > ../dbgui.py
611 611 > import os
612 612 > import sys
613 613 > from mercurial import commands, registrar
614 614 > cmdtable = {}
615 615 > command = registrar.command(cmdtable)
616 616 > @command(b"debuggetpass", norepo=True)
617 617 > def debuggetpass(ui):
618 618 > ui.write(b"%s\n" % ui.getpass())
619 619 > @command(b"debugprompt", norepo=True)
620 620 > def debugprompt(ui):
621 621 > ui.write(b"%s\n" % ui.prompt(b"prompt:"))
622 622 > @command(b"debugpromptchoice", norepo=True)
623 623 > def debugpromptchoice(ui):
624 624 > msg = b"promptchoice (y/n)? $$ &Yes $$ &No"
625 625 > ui.write(b"%d\n" % ui.promptchoice(msg))
626 626 > @command(b"debugreadstdin", norepo=True)
627 627 > def debugreadstdin(ui):
628 628 > ui.write(b"read: %r\n" % sys.stdin.read(1))
629 629 > @command(b"debugwritestdout", norepo=True)
630 630 > def debugwritestdout(ui):
631 631 > os.write(1, b"low-level stdout fd and\n")
632 632 > sys.stdout.write("stdout should be redirected to stderr\n")
633 633 > sys.stdout.flush()
634 634 > EOF
635 635 $ cat <<EOF >> .hg/hgrc
636 636 > [extensions]
637 637 > dbgui = ../dbgui.py
638 638 > EOF
639 639
640 640 >>> from hgclient import check, readchannel, runcommand, stringio
641 641 >>> @check
642 642 ... def getpass(server):
643 643 ... readchannel(server)
644 644 ... runcommand(server, [b'debuggetpass', b'--config',
645 645 ... b'ui.interactive=True'],
646 646 ... input=stringio(b'1234\n'))
647 647 ... runcommand(server, [b'debuggetpass', b'--config',
648 648 ... b'ui.interactive=True'],
649 649 ... input=stringio(b'\n'))
650 650 ... runcommand(server, [b'debuggetpass', b'--config',
651 651 ... b'ui.interactive=True'],
652 652 ... input=stringio(b''))
653 653 ... runcommand(server, [b'debugprompt', b'--config',
654 654 ... b'ui.interactive=True'],
655 655 ... input=stringio(b'5678\n'))
656 656 ... runcommand(server, [b'debugreadstdin'])
657 657 ... runcommand(server, [b'debugwritestdout'])
658 658 *** runcommand debuggetpass --config ui.interactive=True
659 659 password: 1234
660 660 *** runcommand debuggetpass --config ui.interactive=True
661 661 password:
662 662 *** runcommand debuggetpass --config ui.interactive=True
663 663 password: abort: response expected
664 664 [255]
665 665 *** runcommand debugprompt --config ui.interactive=True
666 666 prompt: 5678
667 667 *** runcommand debugreadstdin
668 668 read: ''
669 669 *** runcommand debugwritestdout
670 670 low-level stdout fd and
671 671 stdout should be redirected to stderr
672 672
673 673
674 674 run commandserver in commandserver, which is silly but should work:
675 675
676 676 >>> from hgclient import bprint, check, readchannel, runcommand, stringio
677 677 >>> @check
678 678 ... def nested(server):
679 679 ... bprint(b'%c, %r' % readchannel(server))
680 680 ... class nestedserver(object):
681 681 ... stdin = stringio(b'getencoding\n')
682 682 ... stdout = stringio()
683 683 ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'],
684 684 ... output=nestedserver.stdout, input=nestedserver.stdin)
685 685 ... nestedserver.stdout.seek(0)
686 686 ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello
687 687 ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding
688 688 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
689 689 *** runcommand serve --cmdserver pipe
690 690 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
691 691 r, '*' (glob)
692 692
693 693
694 694 start without repository:
695 695
696 696 $ cd ..
697 697
698 698 >>> from hgclient import bprint, check, readchannel, runcommand
699 699 >>> @check
700 700 ... def hellomessage(server):
701 701 ... ch, data = readchannel(server)
702 702 ... bprint(b'%c, %r' % (ch, data))
703 703 ... # run an arbitrary command to make sure the next thing the server
704 704 ... # sends isn't part of the hello message
705 705 ... runcommand(server, [b'id'])
706 706 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
707 707 *** runcommand id
708 708 abort: there is no Mercurial repository here (.hg not found)
709 709 [255]
710 710
711 711 >>> from hgclient import check, readchannel, runcommand
712 712 >>> @check
713 713 ... def startwithoutrepo(server):
714 714 ... readchannel(server)
715 715 ... runcommand(server, [b'init', b'repo2'])
716 716 ... runcommand(server, [b'id', b'-R', b'repo2'])
717 717 *** runcommand init repo2
718 718 *** runcommand id -R repo2
719 719 000000000000 tip
720 720
721 721
722 722 don't fall back to cwd if invalid -R path is specified (issue4805):
723 723
724 724 $ cd repo
725 725 $ hg serve --cmdserver pipe -R ../nonexistent
726 726 abort: repository ../nonexistent not found!
727 727 [255]
728 728 $ cd ..
729 729
730 730
731 731 structured message channel:
732 732
733 733 $ cat <<'EOF' >> repo2/.hg/hgrc
734 734 > [ui]
735 735 > # server --config should precede repository option
736 736 > message-output = stdio
737 737 > EOF
738 738
739 739 >>> from hgclient import bprint, checkwith, readchannel, runcommand
740 740 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
741 741 ... b'--config', b'cmdserver.message-encodings=foo cbor'])
742 742 ... def verify(server):
743 743 ... _ch, data = readchannel(server)
744 744 ... bprint(data)
745 745 ... runcommand(server, [b'-R', b'repo2', b'verify'])
746 746 capabilities: getencoding runcommand
747 747 encoding: ascii
748 748 message-encoding: cbor
749 749 pid: * (glob)
750 750 pgid: * (glob) (no-windows !)
751 751 *** runcommand -R repo2 verify
752 752 message: '\xa2DdataTchecking changesets\nDtypeFstatus'
753 753 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
754 754 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
755 755 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
756 756 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
757 757 message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@'
758 758 message: '\xa2DdataOchecking files\nDtypeFstatus'
759 759 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
760 760 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
761 761
762 762 >>> from hgclient import checkwith, readchannel, runcommand, stringio
763 763 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
764 764 ... b'--config', b'cmdserver.message-encodings=cbor',
765 765 ... b'--config', b'extensions.dbgui=dbgui.py'])
766 766 ... def prompt(server):
767 767 ... readchannel(server)
768 768 ... interactive = [b'--config', b'ui.interactive=True']
769 769 ... runcommand(server, [b'debuggetpass'] + interactive,
770 770 ... input=stringio(b'1234\n'))
771 771 ... runcommand(server, [b'debugprompt'] + interactive,
772 772 ... input=stringio(b'5678\n'))
773 773 ... runcommand(server, [b'debugpromptchoice'] + interactive,
774 774 ... input=stringio(b'n\n'))
775 775 *** runcommand debuggetpass --config ui.interactive=True
776 776 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
777 777 1234
778 778 *** runcommand debugprompt --config ui.interactive=True
779 779 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
780 780 5678
781 781 *** runcommand debugpromptchoice --config ui.interactive=True
782 782 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
783 783 1
784 784
785 785 bad message encoding:
786 786
787 787 $ hg serve --cmdserver pipe --config ui.message-output=channel
788 788 abort: no supported message encodings:
789 789 [255]
790 790 $ hg serve --cmdserver pipe --config ui.message-output=channel \
791 791 > --config cmdserver.message-encodings='foo bar'
792 792 abort: no supported message encodings: foo bar
793 793 [255]
794 794
795 795 unix domain socket:
796 796
797 797 $ cd repo
798 798 $ hg update -q
799 799
800 800 #if unix-socket unix-permissions
801 801
802 802 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
803 803 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
804 804 >>> def hellomessage(conn):
805 805 ... ch, data = readchannel(conn)
806 806 ... bprint(b'%c, %r' % (ch, data))
807 807 ... runcommand(conn, [b'id'])
808 808 >>> check(hellomessage, server.connect)
809 809 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
810 810 *** runcommand id
811 811 eff892de26ec tip bm1/bm2/bm3
812 812 >>> def unknowncommand(conn):
813 813 ... readchannel(conn)
814 814 ... conn.stdin.write(b'unknowncommand\n')
815 815 >>> check(unknowncommand, server.connect) # error sent to server.log
816 816 >>> def serverinput(conn):
817 817 ... readchannel(conn)
818 818 ... patch = b"""
819 819 ... # HG changeset patch
820 820 ... # User test
821 821 ... # Date 0 0
822 822 ... 2
823 823 ...
824 824 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
825 825 ... --- a/a
826 826 ... +++ b/a
827 827 ... @@ -1,1 +1,2 @@
828 828 ... 1
829 829 ... +2
830 830 ... """
831 831 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
832 832 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
833 833 >>> check(serverinput, server.connect)
834 834 *** runcommand import -
835 835 applying patch from stdin
836 836 *** runcommand log -rtip -q
837 837 2:1ed24be7e7a0
838 838 >>> server.shutdown()
839 839
840 840 $ cat .hg/server.log
841 841 listening at .hg/server.sock
842 842 abort: unknown command unknowncommand
843 843 killed!
844 844 $ rm .hg/server.log
845 845
846 846 if server crashed before hello, traceback will be sent to 'e' channel as
847 847 last ditch:
848 848
849 849 $ cat <<'EOF' > ../earlycrasher.py
850 850 > from mercurial import commandserver, extensions
851 851 > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups):
852 852 > def createcmdserver(*args, **kwargs):
853 853 > raise Exception('crash')
854 854 > return orig(ui, repo, conn, createcmdserver, prereposetups)
855 855 > def extsetup(ui):
856 856 > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest)
857 857 > EOF
858 858 $ cat <<EOF >> .hg/hgrc
859 859 > [extensions]
860 860 > earlycrasher = ../earlycrasher.py
861 861 > EOF
862 862 >>> from hgclient import bprint, check, readchannel, unixserver
863 863 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
864 864 >>> def earlycrash(conn):
865 865 ... while True:
866 866 ... try:
867 867 ... ch, data = readchannel(conn)
868 868 ... for l in data.splitlines(True):
869 869 ... if not l.startswith(b' '):
870 870 ... bprint(b'%c, %r' % (ch, l))
871 871 ... except EOFError:
872 872 ... break
873 873 >>> check(earlycrash, server.connect)
874 874 e, 'Traceback (most recent call last):\n'
875 875 e, 'Exception: crash\n'
876 876 >>> server.shutdown()
877 877
878 878 $ cat .hg/server.log | grep -v '^ '
879 879 listening at .hg/server.sock
880 880 Traceback (most recent call last):
881 881 Exception: crash
882 882 killed!
883 883 #endif
884 884 #if no-unix-socket
885 885
886 886 $ hg serve --cmdserver unix -a .hg/server.sock
887 887 abort: unsupported platform
888 888 [255]
889 889
890 890 #endif
891 891
892 892 $ cd ..
893 893
894 894 Test that accessing to invalid changelog cache is avoided at
895 895 subsequent operations even if repo object is reused even after failure
896 896 of transaction (see 0a7610758c42 also)
897 897
898 898 "hg log" after failure of transaction is needed to detect invalid
899 899 cache in repoview: this can't detect by "hg verify" only.
900 900
901 901 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
902 902 4) are tested, because '00changelog.i' are differently changed in each
903 903 cases.
904 904
905 905 $ cat > $TESTTMP/failafterfinalize.py <<EOF
906 906 > # extension to abort transaction after finalization forcibly
907 907 > from mercurial import commands, error, extensions, lock as lockmod
908 908 > from mercurial import registrar
909 909 > cmdtable = {}
910 910 > command = registrar.command(cmdtable)
911 911 > configtable = {}
912 912 > configitem = registrar.configitem(configtable)
913 913 > configitem(b'failafterfinalize', b'fail',
914 914 > default=None,
915 915 > )
916 916 > def fail(tr):
917 917 > raise error.Abort(b'fail after finalization')
918 918 > def reposetup(ui, repo):
919 919 > class failrepo(repo.__class__):
920 > def commitctx(self, ctx, error=False):
920 > def commitctx(self, ctx, error=False, origctx=None):
921 921 > if self.ui.configbool(b'failafterfinalize', b'fail'):
922 922 > # 'sorted()' by ASCII code on category names causes
923 923 > # invoking 'fail' after finalization of changelog
924 924 > # using "'cl-%i' % id(self)" as category name
925 925 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
926 > return super(failrepo, self).commitctx(ctx, error)
926 > return super(failrepo, self).commitctx(ctx, error, origctx)
927 927 > repo.__class__ = failrepo
928 928 > EOF
929 929
930 930 $ hg init repo3
931 931 $ cd repo3
932 932
933 933 $ cat <<EOF >> $HGRCPATH
934 934 > [ui]
935 935 > logtemplate = {rev} {desc|firstline} ({files})\n
936 936 >
937 937 > [extensions]
938 938 > failafterfinalize = $TESTTMP/failafterfinalize.py
939 939 > EOF
940 940
941 941 - test failure with "empty changelog"
942 942
943 943 $ echo foo > foo
944 944 $ hg add foo
945 945
946 946 (failure before finalization)
947 947
948 948 >>> from hgclient import check, readchannel, runcommand
949 949 >>> @check
950 950 ... def abort(server):
951 951 ... readchannel(server)
952 952 ... runcommand(server, [b'commit',
953 953 ... b'--config', b'hooks.pretxncommit=false',
954 954 ... b'-mfoo'])
955 955 ... runcommand(server, [b'log'])
956 956 ... runcommand(server, [b'verify', b'-q'])
957 957 *** runcommand commit --config hooks.pretxncommit=false -mfoo
958 958 transaction abort!
959 959 rollback completed
960 960 abort: pretxncommit hook exited with status 1
961 961 [255]
962 962 *** runcommand log
963 963 *** runcommand verify -q
964 964
965 965 (failure after finalization)
966 966
967 967 >>> from hgclient import check, readchannel, runcommand
968 968 >>> @check
969 969 ... def abort(server):
970 970 ... readchannel(server)
971 971 ... runcommand(server, [b'commit',
972 972 ... b'--config', b'failafterfinalize.fail=true',
973 973 ... b'-mfoo'])
974 974 ... runcommand(server, [b'log'])
975 975 ... runcommand(server, [b'verify', b'-q'])
976 976 *** runcommand commit --config failafterfinalize.fail=true -mfoo
977 977 transaction abort!
978 978 rollback completed
979 979 abort: fail after finalization
980 980 [255]
981 981 *** runcommand log
982 982 *** runcommand verify -q
983 983
984 984 - test failure with "not-empty changelog"
985 985
986 986 $ echo bar > bar
987 987 $ hg add bar
988 988 $ hg commit -mbar bar
989 989
990 990 (failure before finalization)
991 991
992 992 >>> from hgclient import check, readchannel, runcommand
993 993 >>> @check
994 994 ... def abort(server):
995 995 ... readchannel(server)
996 996 ... runcommand(server, [b'commit',
997 997 ... b'--config', b'hooks.pretxncommit=false',
998 998 ... b'-mfoo', b'foo'])
999 999 ... runcommand(server, [b'log'])
1000 1000 ... runcommand(server, [b'verify', b'-q'])
1001 1001 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
1002 1002 transaction abort!
1003 1003 rollback completed
1004 1004 abort: pretxncommit hook exited with status 1
1005 1005 [255]
1006 1006 *** runcommand log
1007 1007 0 bar (bar)
1008 1008 *** runcommand verify -q
1009 1009
1010 1010 (failure after finalization)
1011 1011
1012 1012 >>> from hgclient import check, readchannel, runcommand
1013 1013 >>> @check
1014 1014 ... def abort(server):
1015 1015 ... readchannel(server)
1016 1016 ... runcommand(server, [b'commit',
1017 1017 ... b'--config', b'failafterfinalize.fail=true',
1018 1018 ... b'-mfoo', b'foo'])
1019 1019 ... runcommand(server, [b'log'])
1020 1020 ... runcommand(server, [b'verify', b'-q'])
1021 1021 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1022 1022 transaction abort!
1023 1023 rollback completed
1024 1024 abort: fail after finalization
1025 1025 [255]
1026 1026 *** runcommand log
1027 1027 0 bar (bar)
1028 1028 *** runcommand verify -q
1029 1029
1030 1030 $ cd ..
1031 1031
1032 1032 Test symlink traversal over cached audited paths:
1033 1033 -------------------------------------------------
1034 1034
1035 1035 #if symlink
1036 1036
1037 1037 set up symlink hell
1038 1038
1039 1039 $ mkdir merge-symlink-out
1040 1040 $ hg init merge-symlink
1041 1041 $ cd merge-symlink
1042 1042 $ touch base
1043 1043 $ hg commit -qAm base
1044 1044 $ ln -s ../merge-symlink-out a
1045 1045 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1046 1046 $ hg up -q 0
1047 1047 $ mkdir a
1048 1048 $ touch a/poisoned
1049 1049 $ hg commit -qAm 'file a/poisoned'
1050 1050 $ hg log -G -T '{rev}: {desc}\n'
1051 1051 @ 2: file a/poisoned
1052 1052 |
1053 1053 | o 1: symlink a -> ../merge-symlink-out
1054 1054 |/
1055 1055 o 0: base
1056 1056
1057 1057
1058 1058 try trivial merge after update: cache of audited paths should be discarded,
1059 1059 and the merge should fail (issue5628)
1060 1060
1061 1061 $ hg up -q null
1062 1062 >>> from hgclient import check, readchannel, runcommand
1063 1063 >>> @check
1064 1064 ... def merge(server):
1065 1065 ... readchannel(server)
1066 1066 ... # audit a/poisoned as a good path
1067 1067 ... runcommand(server, [b'up', b'-qC', b'2'])
1068 1068 ... runcommand(server, [b'up', b'-qC', b'1'])
1069 1069 ... # here a is a symlink, so a/poisoned is bad
1070 1070 ... runcommand(server, [b'merge', b'2'])
1071 1071 *** runcommand up -qC 2
1072 1072 *** runcommand up -qC 1
1073 1073 *** runcommand merge 2
1074 1074 abort: path 'a/poisoned' traverses symbolic link 'a'
1075 1075 [255]
1076 1076 $ ls ../merge-symlink-out
1077 1077
1078 1078 cache of repo.auditor should be discarded, so matcher would never traverse
1079 1079 symlinks:
1080 1080
1081 1081 $ hg up -qC 0
1082 1082 $ touch ../merge-symlink-out/poisoned
1083 1083 >>> from hgclient import check, readchannel, runcommand
1084 1084 >>> @check
1085 1085 ... def files(server):
1086 1086 ... readchannel(server)
1087 1087 ... runcommand(server, [b'up', b'-qC', b'2'])
1088 1088 ... # audit a/poisoned as a good path
1089 1089 ... runcommand(server, [b'files', b'a/poisoned'])
1090 1090 ... runcommand(server, [b'up', b'-qC', b'0'])
1091 1091 ... runcommand(server, [b'up', b'-qC', b'1'])
1092 1092 ... # here 'a' is a symlink, so a/poisoned should be warned
1093 1093 ... runcommand(server, [b'files', b'a/poisoned'])
1094 1094 *** runcommand up -qC 2
1095 1095 *** runcommand files a/poisoned
1096 1096 a/poisoned
1097 1097 *** runcommand up -qC 0
1098 1098 *** runcommand up -qC 1
1099 1099 *** runcommand files a/poisoned
1100 1100 abort: path 'a/poisoned' traverses symbolic link 'a'
1101 1101 [255]
1102 1102
1103 1103 $ cd ..
1104 1104
1105 1105 #endif
@@ -1,630 +1,635 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 Bazaar Source
152 152 #############
153 153
154 154 The following options can be used with "--config":
155 155
156 156 convert.bzr.saverev
157 157 whether to store the original Bazaar commit ID in the
158 158 metadata of the destination commit. The default is True.
159 159
160 160 CVS Source
161 161 ##########
162 162
163 163 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
164 164 indicate the starting point of what will be converted. Direct access to
165 165 the repository files is not needed, unless of course the repository is
166 166 ":local:". The conversion uses the top level directory in the sandbox to
167 167 find the CVS repository, and then uses CVS rlog commands to find files to
168 168 convert. This means that unless a filemap is given, all files under the
169 169 starting directory will be converted, and that any directory
170 170 reorganization in the CVS sandbox is ignored.
171 171
172 172 The following options can be used with "--config":
173 173
174 174 convert.cvsps.cache
175 175 Set to False to disable remote log caching, for testing and
176 176 debugging purposes. Default is True.
177 177 convert.cvsps.fuzz
178 178 Specify the maximum time (in seconds) that is allowed
179 179 between commits with identical user and log message in a
180 180 single changeset. When very large files were checked in as
181 181 part of a changeset then the default may not be long enough.
182 182 The default is 60.
183 183 convert.cvsps.logencoding
184 184 Specify encoding name to be used for transcoding CVS log
185 185 messages. Multiple encoding names can be specified as a list
186 186 (see 'hg help config.Syntax'), but only the first acceptable
187 187 encoding in the list is used per CVS log entries. This
188 188 transcoding is executed before cvslog hook below.
189 189 convert.cvsps.mergeto
190 190 Specify a regular expression to which commit log messages
191 191 are matched. If a match occurs, then the conversion process
192 192 will insert a dummy revision merging the branch on which
193 193 this log message occurs to the branch indicated in the
194 194 regex. Default is "{{mergetobranch ([-\w]+)}}"
195 195 convert.cvsps.mergefrom
196 196 Specify a regular expression to which commit log messages
197 197 are matched. If a match occurs, then the conversion process
198 198 will add the most recent revision on the branch indicated in
199 199 the regex as the second parent of the changeset. Default is
200 200 "{{mergefrombranch ([-\w]+)}}"
201 201 convert.localtimezone
202 202 use local time (as determined by the TZ environment
203 203 variable) for changeset date/times. The default is False
204 204 (use UTC).
205 205 hooks.cvslog Specify a Python function to be called at the end of
206 206 gathering the CVS log. The function is passed a list with
207 207 the log entries, and can modify the entries in-place, or add
208 208 or delete them.
209 209 hooks.cvschangesets
210 210 Specify a Python function to be called after the changesets
211 211 are calculated from the CVS log. The function is passed a
212 212 list with the changeset entries, and can modify the
213 213 changesets in-place, or add or delete them.
214 214
215 215 An additional "debugcvsps" Mercurial command allows the builtin changeset
216 216 merging code to be run without doing a conversion. Its parameters and
217 217 output are similar to that of cvsps 2.1. Please see the command help for
218 218 more details.
219 219
220 220 Subversion Source
221 221 #################
222 222
223 223 Subversion source detects classical trunk/branches/tags layouts. By
224 224 default, the supplied "svn://repo/path/" source URL is converted as a
225 225 single branch. If "svn://repo/path/trunk" exists it replaces the default
226 226 branch. If "svn://repo/path/branches" exists, its subdirectories are
227 227 listed as possible branches. If "svn://repo/path/tags" exists, it is
228 228 looked for tags referencing converted branches. Default "trunk",
229 229 "branches" and "tags" values can be overridden with following options. Set
230 230 them to paths relative to the source URL, or leave them blank to disable
231 231 auto detection.
232 232
233 233 The following options can be set with "--config":
234 234
235 235 convert.svn.branches
236 236 specify the directory containing branches. The default is
237 237 "branches".
238 238 convert.svn.tags
239 239 specify the directory containing tags. The default is
240 240 "tags".
241 241 convert.svn.trunk
242 242 specify the name of the trunk branch. The default is
243 243 "trunk".
244 244 convert.localtimezone
245 245 use local time (as determined by the TZ environment
246 246 variable) for changeset date/times. The default is False
247 247 (use UTC).
248 248
249 249 Source history can be retrieved starting at a specific revision, instead
250 250 of being integrally converted. Only single branch conversions are
251 251 supported.
252 252
253 253 convert.svn.startrev
254 254 specify start Subversion revision number. The default is 0.
255 255
256 256 Git Source
257 257 ##########
258 258
259 259 The Git importer converts commits from all reachable branches (refs in
260 260 refs/heads) and remotes (refs in refs/remotes) to Mercurial. Branches are
261 261 converted to bookmarks with the same name, with the leading 'refs/heads'
262 262 stripped. Git submodules are converted to Git subrepos in Mercurial.
263 263
264 264 The following options can be set with "--config":
265 265
266 266 convert.git.similarity
267 267 specify how similar files modified in a commit must be to be
268 268 imported as renames or copies, as a percentage between "0"
269 269 (disabled) and "100" (files must be identical). For example,
270 270 "90" means that a delete/add pair will be imported as a
271 271 rename if more than 90% of the file hasn't changed. The
272 272 default is "50".
273 273 convert.git.findcopiesharder
274 274 while detecting copies, look at all files in the working
275 275 copy instead of just changed ones. This is very expensive
276 276 for large projects, and is only effective when
277 277 "convert.git.similarity" is greater than 0. The default is
278 278 False.
279 279 convert.git.renamelimit
280 280 perform rename and copy detection up to this many changed
281 281 files in a commit. Increasing this will make rename and copy
282 282 detection more accurate but will significantly slow down
283 283 computation on large projects. The option is only relevant
284 284 if "convert.git.similarity" is greater than 0. The default
285 285 is "400".
286 286 convert.git.committeractions
287 287 list of actions to take when processing author and committer
288 288 values.
289 289
290 290 Git commits have separate author (who wrote the commit) and committer
291 291 (who applied the commit) fields. Not all destinations support separate
292 292 author and committer fields (including Mercurial). This config option
293 293 controls what to do with these author and committer fields during
294 294 conversion.
295 295
296 296 A value of "messagedifferent" will append a "committer: ..." line to
297 297 the commit message if the Git committer is different from the author.
298 298 The prefix of that line can be specified using the syntax
299 299 "messagedifferent=<prefix>". e.g. "messagedifferent=git-committer:".
300 300 When a prefix is specified, a space will always be inserted between
301 301 the prefix and the value.
302 302
303 303 "messagealways" behaves like "messagedifferent" except it will always
304 304 result in a "committer: ..." line being appended to the commit
305 305 message. This value is mutually exclusive with "messagedifferent".
306 306
307 307 "dropcommitter" will remove references to the committer. Only
308 308 references to the author will remain. Actions that add references to
309 309 the committer will have no effect when this is set.
310 310
311 311 "replaceauthor" will replace the value of the author field with the
312 312 committer. Other actions that add references to the committer will
313 313 still take effect when this is set.
314 314
315 315 The default is "messagedifferent".
316 316
317 317 convert.git.extrakeys
318 318 list of extra keys from commit metadata to copy to the
319 319 destination. Some Git repositories store extra metadata in
320 320 commits. By default, this non-default metadata will be lost
321 321 during conversion. Setting this config option can retain
322 322 that metadata. Some built-in keys such as "parent" and
323 323 "branch" are not allowed to be copied.
324 324 convert.git.remoteprefix
325 325 remote refs are converted as bookmarks with
326 326 "convert.git.remoteprefix" as a prefix followed by a /. The
327 327 default is 'remote'.
328 328 convert.git.saverev
329 329 whether to store the original Git commit ID in the metadata
330 330 of the destination commit. The default is True.
331 331 convert.git.skipsubmodules
332 332 does not convert root level .gitmodules files or files with
333 333 160000 mode indicating a submodule. Default is False.
334 334
335 335 Perforce Source
336 336 ###############
337 337
338 338 The Perforce (P4) importer can be given a p4 depot path or a client
339 339 specification as source. It will convert all files in the source to a flat
340 340 Mercurial repository, ignoring labels, branches and integrations. Note
341 341 that when a depot path is given you then usually should specify a target
342 342 directory, because otherwise the target may be named "...-hg".
343 343
344 344 The following options can be set with "--config":
345 345
346 346 convert.p4.encoding
347 347 specify the encoding to use when decoding standard output of
348 348 the Perforce command line tool. The default is default
349 349 system encoding.
350 350 convert.p4.startrev
351 351 specify initial Perforce revision (a Perforce changelist
352 352 number).
353 353
354 354 Mercurial Destination
355 355 #####################
356 356
357 357 The Mercurial destination will recognize Mercurial subrepositories in the
358 358 destination directory, and update the .hgsubstate file automatically if
359 359 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
360 360 Converting a repository with subrepositories requires converting a single
361 361 repository at a time, from the bottom up.
362 362
363 363 The following options are supported:
364 364
365 365 convert.hg.clonebranches
366 366 dispatch source branches in separate clones. The default is
367 367 False.
368 368 convert.hg.tagsbranch
369 369 branch name for tag revisions, defaults to "default".
370 370 convert.hg.usebranchnames
371 371 preserve branch names. The default is True.
372 372 convert.hg.sourcename
373 373 records the given string as a 'convert_source' extra value
374 374 on each commit made in the target repository. The default is
375 375 None.
376 convert.hg.preserve-hash
377 only works with mercurial sources. Make convert prevent
378 performance improvement to the list of modified files in
379 commits when such an improvement would cause the hash of a
380 commit to change. The default is False.
376 381
377 382 All Destinations
378 383 ################
379 384
380 385 All destination types accept the following options:
381 386
382 387 convert.skiptags
383 388 does not convert tags from the source repo to the target
384 389 repo. The default is False.
385 390
386 391 options ([+] can be repeated):
387 392
388 393 -s --source-type TYPE source repository type
389 394 -d --dest-type TYPE destination repository type
390 395 -r --rev REV [+] import up to source revision REV
391 396 -A --authormap FILE remap usernames using this file
392 397 --filemap FILE remap file names using contents of file
393 398 --full apply filemap changes by converting all files again
394 399 --splicemap FILE splice synthesized history into place
395 400 --branchmap FILE change branch names while converting
396 401 --branchsort try to sort changesets by branches
397 402 --datesort try to sort changesets by date
398 403 --sourcesort preserve source changesets order
399 404 --closesort try to reorder closed revisions
400 405
401 406 (some details hidden, use --verbose to show complete help)
402 407 $ hg init a
403 408 $ cd a
404 409 $ echo a > a
405 410 $ hg ci -d'0 0' -Ama
406 411 adding a
407 412 $ hg cp a b
408 413 $ hg ci -d'1 0' -mb
409 414 $ hg rm a
410 415 $ hg ci -d'2 0' -mc
411 416 $ hg mv b a
412 417 $ hg ci -d'3 0' -md
413 418 $ echo a >> a
414 419 $ hg ci -d'4 0' -me
415 420 $ cd ..
416 421 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
417 422 assuming destination a-hg
418 423 initializing destination a-hg repository
419 424 scanning source...
420 425 sorting...
421 426 converting...
422 427 4 a
423 428 3 b
424 429 2 c
425 430 1 d
426 431 0 e
427 432 $ hg --cwd a-hg pull ../a
428 433 pulling from ../a
429 434 searching for changes
430 435 no changes found
431 436 5 local changesets published
432 437
433 438 conversion to existing file should fail
434 439
435 440 $ touch bogusfile
436 441 $ hg convert a bogusfile
437 442 initializing destination bogusfile repository
438 443 abort: cannot create new bundle repository
439 444 [255]
440 445
441 446 #if unix-permissions no-root
442 447
443 448 conversion to dir without permissions should fail
444 449
445 450 $ mkdir bogusdir
446 451 $ chmod 000 bogusdir
447 452
448 453 $ hg convert a bogusdir
449 454 abort: Permission denied: *bogusdir* (glob)
450 455 [255]
451 456
452 457 user permissions should succeed
453 458
454 459 $ chmod 700 bogusdir
455 460 $ hg convert a bogusdir
456 461 initializing destination bogusdir repository
457 462 scanning source...
458 463 sorting...
459 464 converting...
460 465 4 a
461 466 3 b
462 467 2 c
463 468 1 d
464 469 0 e
465 470
466 471 #endif
467 472
468 473 test pre and post conversion actions
469 474
470 475 $ echo 'include b' > filemap
471 476 $ hg convert --debug --filemap filemap a partialb | \
472 477 > grep 'run hg'
473 478 run hg source pre-conversion action
474 479 run hg sink pre-conversion action
475 480 run hg sink post-conversion action
476 481 run hg source post-conversion action
477 482
478 483 converting empty dir should fail "nicely
479 484
480 485 $ mkdir emptydir
481 486
482 487 override $PATH to ensure p4 not visible; use $PYTHON in case we're
483 488 running from a devel copy, not a temp installation
484 489
485 490 $ PATH="$BINDIR" "$PYTHON" "$BINDIR"/hg convert emptydir
486 491 assuming destination emptydir-hg
487 492 initializing destination emptydir-hg repository
488 493 emptydir does not look like a CVS checkout
489 494 $TESTTMP/emptydir does not look like a Git repository
490 495 emptydir does not look like a Subversion repository
491 496 emptydir is not a local Mercurial repository
492 497 emptydir does not look like a darcs repository
493 498 emptydir does not look like a monotone repository
494 499 emptydir does not look like a GNU Arch repository
495 500 emptydir does not look like a Bazaar repository
496 501 cannot find required "p4" tool
497 502 abort: emptydir: missing or unsupported repository
498 503 [255]
499 504
500 505 convert with imaginary source type
501 506
502 507 $ hg convert --source-type foo a a-foo
503 508 initializing destination a-foo repository
504 509 abort: foo: invalid source repository type
505 510 [255]
506 511
507 512 convert with imaginary sink type
508 513
509 514 $ hg convert --dest-type foo a a-foo
510 515 abort: foo: invalid destination repository type
511 516 [255]
512 517
513 518 testing: convert must not produce duplicate entries in fncache
514 519
515 520 $ hg convert a b
516 521 initializing destination b repository
517 522 scanning source...
518 523 sorting...
519 524 converting...
520 525 4 a
521 526 3 b
522 527 2 c
523 528 1 d
524 529 0 e
525 530
526 531 contents of fncache file:
527 532
528 533 #if repofncache
529 534 $ cat b/.hg/store/fncache | sort
530 535 data/a.i (reporevlogstore !)
531 536 data/b.i (reporevlogstore !)
532 537 #endif
533 538
534 539 test bogus URL
535 540
536 541 #if no-msys
537 542 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
538 543 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
539 544 [255]
540 545 #endif
541 546
542 547 test revset converted() lookup
543 548
544 549 $ hg --config convert.hg.saverev=True convert a c
545 550 initializing destination c repository
546 551 scanning source...
547 552 sorting...
548 553 converting...
549 554 4 a
550 555 3 b
551 556 2 c
552 557 1 d
553 558 0 e
554 559 $ echo f > c/f
555 560 $ hg -R c ci -d'0 0' -Amf
556 561 adding f
557 562 created new head
558 563 $ hg -R c log -r "converted(09d945a62ce6)"
559 564 changeset: 1:98c3dd46a874
560 565 user: test
561 566 date: Thu Jan 01 00:00:01 1970 +0000
562 567 summary: b
563 568
564 569 $ hg -R c log -r "converted()"
565 570 changeset: 0:31ed57b2037c
566 571 user: test
567 572 date: Thu Jan 01 00:00:00 1970 +0000
568 573 summary: a
569 574
570 575 changeset: 1:98c3dd46a874
571 576 user: test
572 577 date: Thu Jan 01 00:00:01 1970 +0000
573 578 summary: b
574 579
575 580 changeset: 2:3b9ca06ef716
576 581 user: test
577 582 date: Thu Jan 01 00:00:02 1970 +0000
578 583 summary: c
579 584
580 585 changeset: 3:4e0debd37cf2
581 586 user: test
582 587 date: Thu Jan 01 00:00:03 1970 +0000
583 588 summary: d
584 589
585 590 changeset: 4:9de3bc9349c5
586 591 user: test
587 592 date: Thu Jan 01 00:00:04 1970 +0000
588 593 summary: e
589 594
590 595
591 596 test specifying a sourcename
592 597 $ echo g > a/g
593 598 $ hg -R a ci -d'0 0' -Amg
594 599 adding g
595 600 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
596 601 scanning source...
597 602 sorting...
598 603 converting...
599 604 0 g
600 605 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
601 606 branch=default
602 607 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
603 608 convert_source=mysource
604 609
605 610 $ cat > branchmap.txt << EOF
606 611 > old branch new_branch
607 612 > EOF
608 613
609 614 $ hg -R a branch -q 'old branch'
610 615 $ echo gg > a/g
611 616 $ hg -R a ci -m 'branch name with spaces'
612 617 $ hg convert --branchmap branchmap.txt a d
613 618 initializing destination d repository
614 619 scanning source...
615 620 sorting...
616 621 converting...
617 622 6 a
618 623 5 b
619 624 4 c
620 625 3 d
621 626 2 e
622 627 1 g
623 628 0 branch name with spaces
624 629
625 630 $ hg -R a branches
626 631 old branch 6:a24a66ade009
627 632 default 5:a3bc6100aa8e (inactive)
628 633 $ hg -R d branches
629 634 new_branch 6:64ed208b732b
630 635 default 5:a3bc6100aa8e (inactive)
General Comments 0
You need to be logged in to leave comments. Login now