##// END OF EJS Templates
crecord: make enter move cursor down to the next item of the same type...
av6 -
r40289:682f73fa default
parent child Browse files
Show More
@@ -1,1427 +1,1424 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 coreconfigitem('alias', '.*',
117 117 default=dynamicdefault,
118 118 generic=True,
119 119 )
120 120 coreconfigitem('annotate', 'nodates',
121 121 default=False,
122 122 )
123 123 coreconfigitem('annotate', 'showfunc',
124 124 default=False,
125 125 )
126 126 coreconfigitem('annotate', 'unified',
127 127 default=None,
128 128 )
129 129 coreconfigitem('annotate', 'git',
130 130 default=False,
131 131 )
132 132 coreconfigitem('annotate', 'ignorews',
133 133 default=False,
134 134 )
135 135 coreconfigitem('annotate', 'ignorewsamount',
136 136 default=False,
137 137 )
138 138 coreconfigitem('annotate', 'ignoreblanklines',
139 139 default=False,
140 140 )
141 141 coreconfigitem('annotate', 'ignorewseol',
142 142 default=False,
143 143 )
144 144 coreconfigitem('annotate', 'nobinary',
145 145 default=False,
146 146 )
147 147 coreconfigitem('annotate', 'noprefix',
148 148 default=False,
149 149 )
150 150 coreconfigitem('annotate', 'word-diff',
151 151 default=False,
152 152 )
153 153 coreconfigitem('auth', 'cookiefile',
154 154 default=None,
155 155 )
156 156 # bookmarks.pushing: internal hack for discovery
157 157 coreconfigitem('bookmarks', 'pushing',
158 158 default=list,
159 159 )
160 160 # bundle.mainreporoot: internal hack for bundlerepo
161 161 coreconfigitem('bundle', 'mainreporoot',
162 162 default='',
163 163 )
164 164 coreconfigitem('censor', 'policy',
165 165 default='abort',
166 166 )
167 167 coreconfigitem('chgserver', 'idletimeout',
168 168 default=3600,
169 169 )
170 170 coreconfigitem('chgserver', 'skiphash',
171 171 default=False,
172 172 )
173 173 coreconfigitem('cmdserver', 'log',
174 174 default=None,
175 175 )
176 176 coreconfigitem('color', '.*',
177 177 default=None,
178 178 generic=True,
179 179 )
180 180 coreconfigitem('color', 'mode',
181 181 default='auto',
182 182 )
183 183 coreconfigitem('color', 'pagermode',
184 184 default=dynamicdefault,
185 185 )
186 186 coreconfigitem('commands', 'grep.all-files',
187 187 default=False,
188 188 )
189 189 coreconfigitem('commands', 'resolve.confirm',
190 190 default=False,
191 191 )
192 192 coreconfigitem('commands', 'resolve.explicit-re-merge',
193 193 default=False,
194 194 )
195 195 coreconfigitem('commands', 'resolve.mark-check',
196 196 default='none',
197 197 )
198 198 coreconfigitem('commands', 'show.aliasprefix',
199 199 default=list,
200 200 )
201 201 coreconfigitem('commands', 'status.relative',
202 202 default=False,
203 203 )
204 204 coreconfigitem('commands', 'status.skipstates',
205 205 default=[],
206 206 )
207 207 coreconfigitem('commands', 'status.terse',
208 208 default='',
209 209 )
210 210 coreconfigitem('commands', 'status.verbose',
211 211 default=False,
212 212 )
213 213 coreconfigitem('commands', 'update.check',
214 214 default=None,
215 215 )
216 216 coreconfigitem('commands', 'update.requiredest',
217 217 default=False,
218 218 )
219 219 coreconfigitem('committemplate', '.*',
220 220 default=None,
221 221 generic=True,
222 222 )
223 223 coreconfigitem('convert', 'bzr.saverev',
224 224 default=True,
225 225 )
226 226 coreconfigitem('convert', 'cvsps.cache',
227 227 default=True,
228 228 )
229 229 coreconfigitem('convert', 'cvsps.fuzz',
230 230 default=60,
231 231 )
232 232 coreconfigitem('convert', 'cvsps.logencoding',
233 233 default=None,
234 234 )
235 235 coreconfigitem('convert', 'cvsps.mergefrom',
236 236 default=None,
237 237 )
238 238 coreconfigitem('convert', 'cvsps.mergeto',
239 239 default=None,
240 240 )
241 241 coreconfigitem('convert', 'git.committeractions',
242 242 default=lambda: ['messagedifferent'],
243 243 )
244 244 coreconfigitem('convert', 'git.extrakeys',
245 245 default=list,
246 246 )
247 247 coreconfigitem('convert', 'git.findcopiesharder',
248 248 default=False,
249 249 )
250 250 coreconfigitem('convert', 'git.remoteprefix',
251 251 default='remote',
252 252 )
253 253 coreconfigitem('convert', 'git.renamelimit',
254 254 default=400,
255 255 )
256 256 coreconfigitem('convert', 'git.saverev',
257 257 default=True,
258 258 )
259 259 coreconfigitem('convert', 'git.similarity',
260 260 default=50,
261 261 )
262 262 coreconfigitem('convert', 'git.skipsubmodules',
263 263 default=False,
264 264 )
265 265 coreconfigitem('convert', 'hg.clonebranches',
266 266 default=False,
267 267 )
268 268 coreconfigitem('convert', 'hg.ignoreerrors',
269 269 default=False,
270 270 )
271 271 coreconfigitem('convert', 'hg.revs',
272 272 default=None,
273 273 )
274 274 coreconfigitem('convert', 'hg.saverev',
275 275 default=False,
276 276 )
277 277 coreconfigitem('convert', 'hg.sourcename',
278 278 default=None,
279 279 )
280 280 coreconfigitem('convert', 'hg.startrev',
281 281 default=None,
282 282 )
283 283 coreconfigitem('convert', 'hg.tagsbranch',
284 284 default='default',
285 285 )
286 286 coreconfigitem('convert', 'hg.usebranchnames',
287 287 default=True,
288 288 )
289 289 coreconfigitem('convert', 'ignoreancestorcheck',
290 290 default=False,
291 291 )
292 292 coreconfigitem('convert', 'localtimezone',
293 293 default=False,
294 294 )
295 295 coreconfigitem('convert', 'p4.encoding',
296 296 default=dynamicdefault,
297 297 )
298 298 coreconfigitem('convert', 'p4.startrev',
299 299 default=0,
300 300 )
301 301 coreconfigitem('convert', 'skiptags',
302 302 default=False,
303 303 )
304 304 coreconfigitem('convert', 'svn.debugsvnlog',
305 305 default=True,
306 306 )
307 307 coreconfigitem('convert', 'svn.trunk',
308 308 default=None,
309 309 )
310 310 coreconfigitem('convert', 'svn.tags',
311 311 default=None,
312 312 )
313 313 coreconfigitem('convert', 'svn.branches',
314 314 default=None,
315 315 )
316 316 coreconfigitem('convert', 'svn.startrev',
317 317 default=0,
318 318 )
319 319 coreconfigitem('debug', 'dirstate.delaywrite',
320 320 default=0,
321 321 )
322 322 coreconfigitem('defaults', '.*',
323 323 default=None,
324 324 generic=True,
325 325 )
326 326 coreconfigitem('devel', 'all-warnings',
327 327 default=False,
328 328 )
329 329 coreconfigitem('devel', 'bundle2.debug',
330 330 default=False,
331 331 )
332 332 coreconfigitem('devel', 'cache-vfs',
333 333 default=None,
334 334 )
335 335 coreconfigitem('devel', 'check-locks',
336 336 default=False,
337 337 )
338 338 coreconfigitem('devel', 'check-relroot',
339 339 default=False,
340 340 )
341 341 coreconfigitem('devel', 'default-date',
342 342 default=None,
343 343 )
344 344 coreconfigitem('devel', 'deprec-warn',
345 345 default=False,
346 346 )
347 347 coreconfigitem('devel', 'disableloaddefaultcerts',
348 348 default=False,
349 349 )
350 350 coreconfigitem('devel', 'warn-empty-changegroup',
351 351 default=False,
352 352 )
353 353 coreconfigitem('devel', 'legacy.exchange',
354 354 default=list,
355 355 )
356 356 coreconfigitem('devel', 'servercafile',
357 357 default='',
358 358 )
359 359 coreconfigitem('devel', 'serverexactprotocol',
360 360 default='',
361 361 )
362 362 coreconfigitem('devel', 'serverrequirecert',
363 363 default=False,
364 364 )
365 365 coreconfigitem('devel', 'strip-obsmarkers',
366 366 default=True,
367 367 )
368 368 coreconfigitem('devel', 'warn-config',
369 369 default=None,
370 370 )
371 371 coreconfigitem('devel', 'warn-config-default',
372 372 default=None,
373 373 )
374 374 coreconfigitem('devel', 'user.obsmarker',
375 375 default=None,
376 376 )
377 377 coreconfigitem('devel', 'warn-config-unknown',
378 378 default=None,
379 379 )
380 380 coreconfigitem('devel', 'debug.copies',
381 381 default=False,
382 382 )
383 383 coreconfigitem('devel', 'debug.extensions',
384 384 default=False,
385 385 )
386 386 coreconfigitem('devel', 'debug.peer-request',
387 387 default=False,
388 388 )
389 389 coreconfigitem('diff', 'nodates',
390 390 default=False,
391 391 )
392 392 coreconfigitem('diff', 'showfunc',
393 393 default=False,
394 394 )
395 395 coreconfigitem('diff', 'unified',
396 396 default=None,
397 397 )
398 398 coreconfigitem('diff', 'git',
399 399 default=False,
400 400 )
401 401 coreconfigitem('diff', 'ignorews',
402 402 default=False,
403 403 )
404 404 coreconfigitem('diff', 'ignorewsamount',
405 405 default=False,
406 406 )
407 407 coreconfigitem('diff', 'ignoreblanklines',
408 408 default=False,
409 409 )
410 410 coreconfigitem('diff', 'ignorewseol',
411 411 default=False,
412 412 )
413 413 coreconfigitem('diff', 'nobinary',
414 414 default=False,
415 415 )
416 416 coreconfigitem('diff', 'noprefix',
417 417 default=False,
418 418 )
419 419 coreconfigitem('diff', 'word-diff',
420 420 default=False,
421 421 )
422 422 coreconfigitem('email', 'bcc',
423 423 default=None,
424 424 )
425 425 coreconfigitem('email', 'cc',
426 426 default=None,
427 427 )
428 428 coreconfigitem('email', 'charsets',
429 429 default=list,
430 430 )
431 431 coreconfigitem('email', 'from',
432 432 default=None,
433 433 )
434 434 coreconfigitem('email', 'method',
435 435 default='smtp',
436 436 )
437 437 coreconfigitem('email', 'reply-to',
438 438 default=None,
439 439 )
440 440 coreconfigitem('email', 'to',
441 441 default=None,
442 442 )
443 443 coreconfigitem('experimental', 'archivemetatemplate',
444 444 default=dynamicdefault,
445 445 )
446 446 coreconfigitem('experimental', 'bundle-phases',
447 447 default=False,
448 448 )
449 449 coreconfigitem('experimental', 'bundle2-advertise',
450 450 default=True,
451 451 )
452 452 coreconfigitem('experimental', 'bundle2-output-capture',
453 453 default=False,
454 454 )
455 455 coreconfigitem('experimental', 'bundle2.pushback',
456 456 default=False,
457 457 )
458 458 coreconfigitem('experimental', 'bundle2lazylocking',
459 459 default=False,
460 460 )
461 461 coreconfigitem('experimental', 'bundlecomplevel',
462 462 default=None,
463 463 )
464 464 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
465 465 default=None,
466 466 )
467 467 coreconfigitem('experimental', 'bundlecomplevel.gzip',
468 468 default=None,
469 469 )
470 470 coreconfigitem('experimental', 'bundlecomplevel.none',
471 471 default=None,
472 472 )
473 473 coreconfigitem('experimental', 'bundlecomplevel.zstd',
474 474 default=None,
475 475 )
476 476 coreconfigitem('experimental', 'changegroup3',
477 477 default=False,
478 478 )
479 479 coreconfigitem('experimental', 'clientcompressionengines',
480 480 default=list,
481 481 )
482 482 coreconfigitem('experimental', 'copytrace',
483 483 default='on',
484 484 )
485 485 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
486 486 default=100,
487 487 )
488 488 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
489 489 default=100,
490 490 )
491 491 coreconfigitem('experimental', 'crecordtest',
492 492 default=None,
493 493 )
494 494 coreconfigitem('experimental', 'directaccess',
495 495 default=False,
496 496 )
497 497 coreconfigitem('experimental', 'directaccess.revnums',
498 498 default=False,
499 499 )
500 500 coreconfigitem('experimental', 'editortmpinhg',
501 501 default=False,
502 502 )
503 503 coreconfigitem('experimental', 'evolution',
504 504 default=list,
505 505 )
506 506 coreconfigitem('experimental', 'evolution.allowdivergence',
507 507 default=False,
508 508 alias=[('experimental', 'allowdivergence')]
509 509 )
510 510 coreconfigitem('experimental', 'evolution.allowunstable',
511 511 default=None,
512 512 )
513 513 coreconfigitem('experimental', 'evolution.createmarkers',
514 514 default=None,
515 515 )
516 516 coreconfigitem('experimental', 'evolution.effect-flags',
517 517 default=True,
518 518 alias=[('experimental', 'effect-flags')]
519 519 )
520 520 coreconfigitem('experimental', 'evolution.exchange',
521 521 default=None,
522 522 )
523 523 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
524 524 default=False,
525 525 )
526 526 coreconfigitem('experimental', 'evolution.report-instabilities',
527 527 default=True,
528 528 )
529 529 coreconfigitem('experimental', 'evolution.track-operation',
530 530 default=True,
531 531 )
532 532 coreconfigitem('experimental', 'maxdeltachainspan',
533 533 default=-1,
534 534 )
535 535 coreconfigitem('experimental', 'mergetempdirprefix',
536 536 default=None,
537 537 )
538 538 coreconfigitem('experimental', 'mmapindexthreshold',
539 539 default=None,
540 540 )
541 541 coreconfigitem('experimental', 'narrow',
542 542 default=False,
543 543 )
544 544 coreconfigitem('experimental', 'nonnormalparanoidcheck',
545 545 default=False,
546 546 )
547 547 coreconfigitem('experimental', 'exportableenviron',
548 548 default=list,
549 549 )
550 550 coreconfigitem('experimental', 'extendedheader.index',
551 551 default=None,
552 552 )
553 553 coreconfigitem('experimental', 'extendedheader.similarity',
554 554 default=False,
555 555 )
556 556 coreconfigitem('experimental', 'format.compression',
557 557 default='zlib',
558 558 )
559 559 coreconfigitem('experimental', 'graphshorten',
560 560 default=False,
561 561 )
562 562 coreconfigitem('experimental', 'graphstyle.parent',
563 563 default=dynamicdefault,
564 564 )
565 565 coreconfigitem('experimental', 'graphstyle.missing',
566 566 default=dynamicdefault,
567 567 )
568 568 coreconfigitem('experimental', 'graphstyle.grandparent',
569 569 default=dynamicdefault,
570 570 )
571 571 coreconfigitem('experimental', 'hook-track-tags',
572 572 default=False,
573 573 )
574 574 coreconfigitem('experimental', 'httppeer.advertise-v2',
575 575 default=False,
576 576 )
577 577 coreconfigitem('experimental', 'httppeer.v2-encoder-order',
578 578 default=None,
579 579 )
580 580 coreconfigitem('experimental', 'httppostargs',
581 581 default=False,
582 582 )
583 583 coreconfigitem('experimental', 'mergedriver',
584 584 default=None,
585 585 )
586 586 coreconfigitem('experimental', 'nointerrupt', default=False)
587 587 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
588 588
589 589 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
590 590 default=False,
591 591 )
592 592 coreconfigitem('experimental', 'remotenames',
593 593 default=False,
594 594 )
595 595 coreconfigitem('experimental', 'removeemptydirs',
596 596 default=True,
597 597 )
598 598 coreconfigitem('experimental', 'revisions.prefixhexnode',
599 599 default=False,
600 600 )
601 601 coreconfigitem('experimental', 'revlogv2',
602 602 default=None,
603 603 )
604 604 coreconfigitem('experimental', 'revisions.disambiguatewithin',
605 605 default=None,
606 606 )
607 607 coreconfigitem('experimental', 'server.filesdata.recommended-batch-size',
608 608 default=50000,
609 609 )
610 610 coreconfigitem('experimental', 'server.manifestdata.recommended-batch-size',
611 611 default=100000,
612 612 )
613 613 coreconfigitem('experimental', 'single-head-per-branch',
614 614 default=False,
615 615 )
616 616 coreconfigitem('experimental', 'sshserver.support-v2',
617 617 default=False,
618 618 )
619 coreconfigitem('experimental', 'spacemovesdown',
620 default=False,
621 )
622 619 coreconfigitem('experimental', 'sparse-read',
623 620 default=False,
624 621 )
625 622 coreconfigitem('experimental', 'sparse-read.density-threshold',
626 623 default=0.50,
627 624 )
628 625 coreconfigitem('experimental', 'sparse-read.min-gap-size',
629 626 default='65K',
630 627 )
631 628 coreconfigitem('experimental', 'treemanifest',
632 629 default=False,
633 630 )
634 631 coreconfigitem('experimental', 'update.atomic-file',
635 632 default=False,
636 633 )
637 634 coreconfigitem('experimental', 'sshpeer.advertise-v2',
638 635 default=False,
639 636 )
640 637 coreconfigitem('experimental', 'web.apiserver',
641 638 default=False,
642 639 )
643 640 coreconfigitem('experimental', 'web.api.http-v2',
644 641 default=False,
645 642 )
646 643 coreconfigitem('experimental', 'web.api.debugreflect',
647 644 default=False,
648 645 )
649 646 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
650 647 default=False,
651 648 )
652 649 coreconfigitem('experimental', 'xdiff',
653 650 default=False,
654 651 )
655 652 coreconfigitem('extensions', '.*',
656 653 default=None,
657 654 generic=True,
658 655 )
659 656 coreconfigitem('extdata', '.*',
660 657 default=None,
661 658 generic=True,
662 659 )
663 660 coreconfigitem('format', 'chunkcachesize',
664 661 default=None,
665 662 )
666 663 coreconfigitem('format', 'dotencode',
667 664 default=True,
668 665 )
669 666 coreconfigitem('format', 'generaldelta',
670 667 default=False,
671 668 )
672 669 coreconfigitem('format', 'manifestcachesize',
673 670 default=None,
674 671 )
675 672 coreconfigitem('format', 'maxchainlen',
676 673 default=dynamicdefault,
677 674 )
678 675 coreconfigitem('format', 'obsstore-version',
679 676 default=None,
680 677 )
681 678 coreconfigitem('format', 'sparse-revlog',
682 679 default=False,
683 680 )
684 681 coreconfigitem('format', 'usefncache',
685 682 default=True,
686 683 )
687 684 coreconfigitem('format', 'usegeneraldelta',
688 685 default=True,
689 686 )
690 687 coreconfigitem('format', 'usestore',
691 688 default=True,
692 689 )
693 690 coreconfigitem('format', 'internal-phase',
694 691 default=False,
695 692 )
696 693 coreconfigitem('fsmonitor', 'warn_when_unused',
697 694 default=True,
698 695 )
699 696 coreconfigitem('fsmonitor', 'warn_update_file_count',
700 697 default=50000,
701 698 )
702 699 coreconfigitem('hooks', '.*',
703 700 default=dynamicdefault,
704 701 generic=True,
705 702 )
706 703 coreconfigitem('hgweb-paths', '.*',
707 704 default=list,
708 705 generic=True,
709 706 )
710 707 coreconfigitem('hostfingerprints', '.*',
711 708 default=list,
712 709 generic=True,
713 710 )
714 711 coreconfigitem('hostsecurity', 'ciphers',
715 712 default=None,
716 713 )
717 714 coreconfigitem('hostsecurity', 'disabletls10warning',
718 715 default=False,
719 716 )
720 717 coreconfigitem('hostsecurity', 'minimumprotocol',
721 718 default=dynamicdefault,
722 719 )
723 720 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
724 721 default=dynamicdefault,
725 722 generic=True,
726 723 )
727 724 coreconfigitem('hostsecurity', '.*:ciphers$',
728 725 default=dynamicdefault,
729 726 generic=True,
730 727 )
731 728 coreconfigitem('hostsecurity', '.*:fingerprints$',
732 729 default=list,
733 730 generic=True,
734 731 )
735 732 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
736 733 default=None,
737 734 generic=True,
738 735 )
739 736
740 737 coreconfigitem('http_proxy', 'always',
741 738 default=False,
742 739 )
743 740 coreconfigitem('http_proxy', 'host',
744 741 default=None,
745 742 )
746 743 coreconfigitem('http_proxy', 'no',
747 744 default=list,
748 745 )
749 746 coreconfigitem('http_proxy', 'passwd',
750 747 default=None,
751 748 )
752 749 coreconfigitem('http_proxy', 'user',
753 750 default=None,
754 751 )
755 752
756 753 coreconfigitem('http', 'timeout',
757 754 default=None,
758 755 )
759 756
760 757 coreconfigitem('logtoprocess', 'commandexception',
761 758 default=None,
762 759 )
763 760 coreconfigitem('logtoprocess', 'commandfinish',
764 761 default=None,
765 762 )
766 763 coreconfigitem('logtoprocess', 'command',
767 764 default=None,
768 765 )
769 766 coreconfigitem('logtoprocess', 'develwarn',
770 767 default=None,
771 768 )
772 769 coreconfigitem('logtoprocess', 'uiblocked',
773 770 default=None,
774 771 )
775 772 coreconfigitem('merge', 'checkunknown',
776 773 default='abort',
777 774 )
778 775 coreconfigitem('merge', 'checkignored',
779 776 default='abort',
780 777 )
781 778 coreconfigitem('experimental', 'merge.checkpathconflicts',
782 779 default=False,
783 780 )
784 781 coreconfigitem('merge', 'followcopies',
785 782 default=True,
786 783 )
787 784 coreconfigitem('merge', 'on-failure',
788 785 default='continue',
789 786 )
790 787 coreconfigitem('merge', 'preferancestor',
791 788 default=lambda: ['*'],
792 789 )
793 790 coreconfigitem('merge', 'strict-capability-check',
794 791 default=False,
795 792 )
796 793 coreconfigitem('merge-tools', '.*',
797 794 default=None,
798 795 generic=True,
799 796 )
800 797 coreconfigitem('merge-tools', br'.*\.args$',
801 798 default="$local $base $other",
802 799 generic=True,
803 800 priority=-1,
804 801 )
805 802 coreconfigitem('merge-tools', br'.*\.binary$',
806 803 default=False,
807 804 generic=True,
808 805 priority=-1,
809 806 )
810 807 coreconfigitem('merge-tools', br'.*\.check$',
811 808 default=list,
812 809 generic=True,
813 810 priority=-1,
814 811 )
815 812 coreconfigitem('merge-tools', br'.*\.checkchanged$',
816 813 default=False,
817 814 generic=True,
818 815 priority=-1,
819 816 )
820 817 coreconfigitem('merge-tools', br'.*\.executable$',
821 818 default=dynamicdefault,
822 819 generic=True,
823 820 priority=-1,
824 821 )
825 822 coreconfigitem('merge-tools', br'.*\.fixeol$',
826 823 default=False,
827 824 generic=True,
828 825 priority=-1,
829 826 )
830 827 coreconfigitem('merge-tools', br'.*\.gui$',
831 828 default=False,
832 829 generic=True,
833 830 priority=-1,
834 831 )
835 832 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
836 833 default='basic',
837 834 generic=True,
838 835 priority=-1,
839 836 )
840 837 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
841 838 default=dynamicdefault, # take from ui.mergemarkertemplate
842 839 generic=True,
843 840 priority=-1,
844 841 )
845 842 coreconfigitem('merge-tools', br'.*\.priority$',
846 843 default=0,
847 844 generic=True,
848 845 priority=-1,
849 846 )
850 847 coreconfigitem('merge-tools', br'.*\.premerge$',
851 848 default=dynamicdefault,
852 849 generic=True,
853 850 priority=-1,
854 851 )
855 852 coreconfigitem('merge-tools', br'.*\.symlink$',
856 853 default=False,
857 854 generic=True,
858 855 priority=-1,
859 856 )
860 857 coreconfigitem('pager', 'attend-.*',
861 858 default=dynamicdefault,
862 859 generic=True,
863 860 )
864 861 coreconfigitem('pager', 'ignore',
865 862 default=list,
866 863 )
867 864 coreconfigitem('pager', 'pager',
868 865 default=dynamicdefault,
869 866 )
870 867 coreconfigitem('patch', 'eol',
871 868 default='strict',
872 869 )
873 870 coreconfigitem('patch', 'fuzz',
874 871 default=2,
875 872 )
876 873 coreconfigitem('paths', 'default',
877 874 default=None,
878 875 )
879 876 coreconfigitem('paths', 'default-push',
880 877 default=None,
881 878 )
882 879 coreconfigitem('paths', '.*',
883 880 default=None,
884 881 generic=True,
885 882 )
886 883 coreconfigitem('phases', 'checksubrepos',
887 884 default='follow',
888 885 )
889 886 coreconfigitem('phases', 'new-commit',
890 887 default='draft',
891 888 )
892 889 coreconfigitem('phases', 'publish',
893 890 default=True,
894 891 )
895 892 coreconfigitem('profiling', 'enabled',
896 893 default=False,
897 894 )
898 895 coreconfigitem('profiling', 'format',
899 896 default='text',
900 897 )
901 898 coreconfigitem('profiling', 'freq',
902 899 default=1000,
903 900 )
904 901 coreconfigitem('profiling', 'limit',
905 902 default=30,
906 903 )
907 904 coreconfigitem('profiling', 'nested',
908 905 default=0,
909 906 )
910 907 coreconfigitem('profiling', 'output',
911 908 default=None,
912 909 )
913 910 coreconfigitem('profiling', 'showmax',
914 911 default=0.999,
915 912 )
916 913 coreconfigitem('profiling', 'showmin',
917 914 default=dynamicdefault,
918 915 )
919 916 coreconfigitem('profiling', 'sort',
920 917 default='inlinetime',
921 918 )
922 919 coreconfigitem('profiling', 'statformat',
923 920 default='hotpath',
924 921 )
925 922 coreconfigitem('profiling', 'time-track',
926 923 default='cpu',
927 924 )
928 925 coreconfigitem('profiling', 'type',
929 926 default='stat',
930 927 )
931 928 coreconfigitem('progress', 'assume-tty',
932 929 default=False,
933 930 )
934 931 coreconfigitem('progress', 'changedelay',
935 932 default=1,
936 933 )
937 934 coreconfigitem('progress', 'clear-complete',
938 935 default=True,
939 936 )
940 937 coreconfigitem('progress', 'debug',
941 938 default=False,
942 939 )
943 940 coreconfigitem('progress', 'delay',
944 941 default=3,
945 942 )
946 943 coreconfigitem('progress', 'disable',
947 944 default=False,
948 945 )
949 946 coreconfigitem('progress', 'estimateinterval',
950 947 default=60.0,
951 948 )
952 949 coreconfigitem('progress', 'format',
953 950 default=lambda: ['topic', 'bar', 'number', 'estimate'],
954 951 )
955 952 coreconfigitem('progress', 'refresh',
956 953 default=0.1,
957 954 )
958 955 coreconfigitem('progress', 'width',
959 956 default=dynamicdefault,
960 957 )
961 958 coreconfigitem('push', 'pushvars.server',
962 959 default=False,
963 960 )
964 961 coreconfigitem('storage', 'new-repo-backend',
965 962 default='revlogv1',
966 963 )
967 964 coreconfigitem('storage', 'revlog.optimize-delta-parent-choice',
968 965 default=True,
969 966 alias=[('format', 'aggressivemergedeltas')],
970 967 )
971 968 coreconfigitem('server', 'bookmarks-pushkey-compat',
972 969 default=True,
973 970 )
974 971 coreconfigitem('server', 'bundle1',
975 972 default=True,
976 973 )
977 974 coreconfigitem('server', 'bundle1gd',
978 975 default=None,
979 976 )
980 977 coreconfigitem('server', 'bundle1.pull',
981 978 default=None,
982 979 )
983 980 coreconfigitem('server', 'bundle1gd.pull',
984 981 default=None,
985 982 )
986 983 coreconfigitem('server', 'bundle1.push',
987 984 default=None,
988 985 )
989 986 coreconfigitem('server', 'bundle1gd.push',
990 987 default=None,
991 988 )
992 989 coreconfigitem('server', 'bundle2.stream',
993 990 default=True,
994 991 alias=[('experimental', 'bundle2.stream')]
995 992 )
996 993 coreconfigitem('server', 'compressionengines',
997 994 default=list,
998 995 )
999 996 coreconfigitem('server', 'concurrent-push-mode',
1000 997 default='strict',
1001 998 )
1002 999 coreconfigitem('server', 'disablefullbundle',
1003 1000 default=False,
1004 1001 )
1005 1002 coreconfigitem('server', 'maxhttpheaderlen',
1006 1003 default=1024,
1007 1004 )
1008 1005 coreconfigitem('server', 'pullbundle',
1009 1006 default=False,
1010 1007 )
1011 1008 coreconfigitem('server', 'preferuncompressed',
1012 1009 default=False,
1013 1010 )
1014 1011 coreconfigitem('server', 'streamunbundle',
1015 1012 default=False,
1016 1013 )
1017 1014 coreconfigitem('server', 'uncompressed',
1018 1015 default=True,
1019 1016 )
1020 1017 coreconfigitem('server', 'uncompressedallowsecret',
1021 1018 default=False,
1022 1019 )
1023 1020 coreconfigitem('server', 'validate',
1024 1021 default=False,
1025 1022 )
1026 1023 coreconfigitem('server', 'zliblevel',
1027 1024 default=-1,
1028 1025 )
1029 1026 coreconfigitem('server', 'zstdlevel',
1030 1027 default=3,
1031 1028 )
1032 1029 coreconfigitem('share', 'pool',
1033 1030 default=None,
1034 1031 )
1035 1032 coreconfigitem('share', 'poolnaming',
1036 1033 default='identity',
1037 1034 )
1038 1035 coreconfigitem('smtp', 'host',
1039 1036 default=None,
1040 1037 )
1041 1038 coreconfigitem('smtp', 'local_hostname',
1042 1039 default=None,
1043 1040 )
1044 1041 coreconfigitem('smtp', 'password',
1045 1042 default=None,
1046 1043 )
1047 1044 coreconfigitem('smtp', 'port',
1048 1045 default=dynamicdefault,
1049 1046 )
1050 1047 coreconfigitem('smtp', 'tls',
1051 1048 default='none',
1052 1049 )
1053 1050 coreconfigitem('smtp', 'username',
1054 1051 default=None,
1055 1052 )
1056 1053 coreconfigitem('sparse', 'missingwarning',
1057 1054 default=True,
1058 1055 )
1059 1056 coreconfigitem('subrepos', 'allowed',
1060 1057 default=dynamicdefault, # to make backporting simpler
1061 1058 )
1062 1059 coreconfigitem('subrepos', 'hg:allowed',
1063 1060 default=dynamicdefault,
1064 1061 )
1065 1062 coreconfigitem('subrepos', 'git:allowed',
1066 1063 default=dynamicdefault,
1067 1064 )
1068 1065 coreconfigitem('subrepos', 'svn:allowed',
1069 1066 default=dynamicdefault,
1070 1067 )
1071 1068 coreconfigitem('templates', '.*',
1072 1069 default=None,
1073 1070 generic=True,
1074 1071 )
1075 1072 coreconfigitem('trusted', 'groups',
1076 1073 default=list,
1077 1074 )
1078 1075 coreconfigitem('trusted', 'users',
1079 1076 default=list,
1080 1077 )
1081 1078 coreconfigitem('ui', '_usedassubrepo',
1082 1079 default=False,
1083 1080 )
1084 1081 coreconfigitem('ui', 'allowemptycommit',
1085 1082 default=False,
1086 1083 )
1087 1084 coreconfigitem('ui', 'archivemeta',
1088 1085 default=True,
1089 1086 )
1090 1087 coreconfigitem('ui', 'askusername',
1091 1088 default=False,
1092 1089 )
1093 1090 coreconfigitem('ui', 'clonebundlefallback',
1094 1091 default=False,
1095 1092 )
1096 1093 coreconfigitem('ui', 'clonebundleprefers',
1097 1094 default=list,
1098 1095 )
1099 1096 coreconfigitem('ui', 'clonebundles',
1100 1097 default=True,
1101 1098 )
1102 1099 coreconfigitem('ui', 'color',
1103 1100 default='auto',
1104 1101 )
1105 1102 coreconfigitem('ui', 'commitsubrepos',
1106 1103 default=False,
1107 1104 )
1108 1105 coreconfigitem('ui', 'debug',
1109 1106 default=False,
1110 1107 )
1111 1108 coreconfigitem('ui', 'debugger',
1112 1109 default=None,
1113 1110 )
1114 1111 coreconfigitem('ui', 'editor',
1115 1112 default=dynamicdefault,
1116 1113 )
1117 1114 coreconfigitem('ui', 'fallbackencoding',
1118 1115 default=None,
1119 1116 )
1120 1117 coreconfigitem('ui', 'forcecwd',
1121 1118 default=None,
1122 1119 )
1123 1120 coreconfigitem('ui', 'forcemerge',
1124 1121 default=None,
1125 1122 )
1126 1123 coreconfigitem('ui', 'formatdebug',
1127 1124 default=False,
1128 1125 )
1129 1126 coreconfigitem('ui', 'formatjson',
1130 1127 default=False,
1131 1128 )
1132 1129 coreconfigitem('ui', 'formatted',
1133 1130 default=None,
1134 1131 )
1135 1132 coreconfigitem('ui', 'graphnodetemplate',
1136 1133 default=None,
1137 1134 )
1138 1135 coreconfigitem('ui', 'history-editing-backup',
1139 1136 default=True,
1140 1137 )
1141 1138 coreconfigitem('ui', 'interactive',
1142 1139 default=None,
1143 1140 )
1144 1141 coreconfigitem('ui', 'interface',
1145 1142 default=None,
1146 1143 )
1147 1144 coreconfigitem('ui', 'interface.chunkselector',
1148 1145 default=None,
1149 1146 )
1150 1147 coreconfigitem('ui', 'large-file-limit',
1151 1148 default=10000000,
1152 1149 )
1153 1150 coreconfigitem('ui', 'logblockedtimes',
1154 1151 default=False,
1155 1152 )
1156 1153 coreconfigitem('ui', 'logtemplate',
1157 1154 default=None,
1158 1155 )
1159 1156 coreconfigitem('ui', 'merge',
1160 1157 default=None,
1161 1158 )
1162 1159 coreconfigitem('ui', 'mergemarkers',
1163 1160 default='basic',
1164 1161 )
1165 1162 coreconfigitem('ui', 'mergemarkertemplate',
1166 1163 default=('{node|short} '
1167 1164 '{ifeq(tags, "tip", "", '
1168 1165 'ifeq(tags, "", "", "{tags} "))}'
1169 1166 '{if(bookmarks, "{bookmarks} ")}'
1170 1167 '{ifeq(branch, "default", "", "{branch} ")}'
1171 1168 '- {author|user}: {desc|firstline}')
1172 1169 )
1173 1170 coreconfigitem('ui', 'nontty',
1174 1171 default=False,
1175 1172 )
1176 1173 coreconfigitem('ui', 'origbackuppath',
1177 1174 default=None,
1178 1175 )
1179 1176 coreconfigitem('ui', 'paginate',
1180 1177 default=True,
1181 1178 )
1182 1179 coreconfigitem('ui', 'patch',
1183 1180 default=None,
1184 1181 )
1185 1182 coreconfigitem('ui', 'portablefilenames',
1186 1183 default='warn',
1187 1184 )
1188 1185 coreconfigitem('ui', 'promptecho',
1189 1186 default=False,
1190 1187 )
1191 1188 coreconfigitem('ui', 'quiet',
1192 1189 default=False,
1193 1190 )
1194 1191 coreconfigitem('ui', 'quietbookmarkmove',
1195 1192 default=False,
1196 1193 )
1197 1194 coreconfigitem('ui', 'remotecmd',
1198 1195 default='hg',
1199 1196 )
1200 1197 coreconfigitem('ui', 'report_untrusted',
1201 1198 default=True,
1202 1199 )
1203 1200 coreconfigitem('ui', 'rollback',
1204 1201 default=True,
1205 1202 )
1206 1203 coreconfigitem('ui', 'signal-safe-lock',
1207 1204 default=True,
1208 1205 )
1209 1206 coreconfigitem('ui', 'slash',
1210 1207 default=False,
1211 1208 )
1212 1209 coreconfigitem('ui', 'ssh',
1213 1210 default='ssh',
1214 1211 )
1215 1212 coreconfigitem('ui', 'ssherrorhint',
1216 1213 default=None,
1217 1214 )
1218 1215 coreconfigitem('ui', 'statuscopies',
1219 1216 default=False,
1220 1217 )
1221 1218 coreconfigitem('ui', 'strict',
1222 1219 default=False,
1223 1220 )
1224 1221 coreconfigitem('ui', 'style',
1225 1222 default='',
1226 1223 )
1227 1224 coreconfigitem('ui', 'supportcontact',
1228 1225 default=None,
1229 1226 )
1230 1227 coreconfigitem('ui', 'textwidth',
1231 1228 default=78,
1232 1229 )
1233 1230 coreconfigitem('ui', 'timeout',
1234 1231 default='600',
1235 1232 )
1236 1233 coreconfigitem('ui', 'timeout.warn',
1237 1234 default=0,
1238 1235 )
1239 1236 coreconfigitem('ui', 'traceback',
1240 1237 default=False,
1241 1238 )
1242 1239 coreconfigitem('ui', 'tweakdefaults',
1243 1240 default=False,
1244 1241 )
1245 1242 coreconfigitem('ui', 'username',
1246 1243 alias=[('ui', 'user')]
1247 1244 )
1248 1245 coreconfigitem('ui', 'verbose',
1249 1246 default=False,
1250 1247 )
1251 1248 coreconfigitem('verify', 'skipflags',
1252 1249 default=None,
1253 1250 )
1254 1251 coreconfigitem('web', 'allowbz2',
1255 1252 default=False,
1256 1253 )
1257 1254 coreconfigitem('web', 'allowgz',
1258 1255 default=False,
1259 1256 )
1260 1257 coreconfigitem('web', 'allow-pull',
1261 1258 alias=[('web', 'allowpull')],
1262 1259 default=True,
1263 1260 )
1264 1261 coreconfigitem('web', 'allow-push',
1265 1262 alias=[('web', 'allow_push')],
1266 1263 default=list,
1267 1264 )
1268 1265 coreconfigitem('web', 'allowzip',
1269 1266 default=False,
1270 1267 )
1271 1268 coreconfigitem('web', 'archivesubrepos',
1272 1269 default=False,
1273 1270 )
1274 1271 coreconfigitem('web', 'cache',
1275 1272 default=True,
1276 1273 )
1277 1274 coreconfigitem('web', 'contact',
1278 1275 default=None,
1279 1276 )
1280 1277 coreconfigitem('web', 'deny_push',
1281 1278 default=list,
1282 1279 )
1283 1280 coreconfigitem('web', 'guessmime',
1284 1281 default=False,
1285 1282 )
1286 1283 coreconfigitem('web', 'hidden',
1287 1284 default=False,
1288 1285 )
1289 1286 coreconfigitem('web', 'labels',
1290 1287 default=list,
1291 1288 )
1292 1289 coreconfigitem('web', 'logoimg',
1293 1290 default='hglogo.png',
1294 1291 )
1295 1292 coreconfigitem('web', 'logourl',
1296 1293 default='https://mercurial-scm.org/',
1297 1294 )
1298 1295 coreconfigitem('web', 'accesslog',
1299 1296 default='-',
1300 1297 )
1301 1298 coreconfigitem('web', 'address',
1302 1299 default='',
1303 1300 )
1304 1301 coreconfigitem('web', 'allow-archive',
1305 1302 alias=[('web', 'allow_archive')],
1306 1303 default=list,
1307 1304 )
1308 1305 coreconfigitem('web', 'allow_read',
1309 1306 default=list,
1310 1307 )
1311 1308 coreconfigitem('web', 'baseurl',
1312 1309 default=None,
1313 1310 )
1314 1311 coreconfigitem('web', 'cacerts',
1315 1312 default=None,
1316 1313 )
1317 1314 coreconfigitem('web', 'certificate',
1318 1315 default=None,
1319 1316 )
1320 1317 coreconfigitem('web', 'collapse',
1321 1318 default=False,
1322 1319 )
1323 1320 coreconfigitem('web', 'csp',
1324 1321 default=None,
1325 1322 )
1326 1323 coreconfigitem('web', 'deny_read',
1327 1324 default=list,
1328 1325 )
1329 1326 coreconfigitem('web', 'descend',
1330 1327 default=True,
1331 1328 )
1332 1329 coreconfigitem('web', 'description',
1333 1330 default="",
1334 1331 )
1335 1332 coreconfigitem('web', 'encoding',
1336 1333 default=lambda: encoding.encoding,
1337 1334 )
1338 1335 coreconfigitem('web', 'errorlog',
1339 1336 default='-',
1340 1337 )
1341 1338 coreconfigitem('web', 'ipv6',
1342 1339 default=False,
1343 1340 )
1344 1341 coreconfigitem('web', 'maxchanges',
1345 1342 default=10,
1346 1343 )
1347 1344 coreconfigitem('web', 'maxfiles',
1348 1345 default=10,
1349 1346 )
1350 1347 coreconfigitem('web', 'maxshortchanges',
1351 1348 default=60,
1352 1349 )
1353 1350 coreconfigitem('web', 'motd',
1354 1351 default='',
1355 1352 )
1356 1353 coreconfigitem('web', 'name',
1357 1354 default=dynamicdefault,
1358 1355 )
1359 1356 coreconfigitem('web', 'port',
1360 1357 default=8000,
1361 1358 )
1362 1359 coreconfigitem('web', 'prefix',
1363 1360 default='',
1364 1361 )
1365 1362 coreconfigitem('web', 'push_ssl',
1366 1363 default=True,
1367 1364 )
1368 1365 coreconfigitem('web', 'refreshinterval',
1369 1366 default=20,
1370 1367 )
1371 1368 coreconfigitem('web', 'server-header',
1372 1369 default=None,
1373 1370 )
1374 1371 coreconfigitem('web', 'static',
1375 1372 default=None,
1376 1373 )
1377 1374 coreconfigitem('web', 'staticurl',
1378 1375 default=None,
1379 1376 )
1380 1377 coreconfigitem('web', 'stripes',
1381 1378 default=1,
1382 1379 )
1383 1380 coreconfigitem('web', 'style',
1384 1381 default='paper',
1385 1382 )
1386 1383 coreconfigitem('web', 'templates',
1387 1384 default=None,
1388 1385 )
1389 1386 coreconfigitem('web', 'view',
1390 1387 default='served',
1391 1388 )
1392 1389 coreconfigitem('worker', 'backgroundclose',
1393 1390 default=dynamicdefault,
1394 1391 )
1395 1392 # Windows defaults to a limit of 512 open files. A buffer of 128
1396 1393 # should give us enough headway.
1397 1394 coreconfigitem('worker', 'backgroundclosemaxqueue',
1398 1395 default=384,
1399 1396 )
1400 1397 coreconfigitem('worker', 'backgroundcloseminfilecount',
1401 1398 default=2048,
1402 1399 )
1403 1400 coreconfigitem('worker', 'backgroundclosethreadcount',
1404 1401 default=4,
1405 1402 )
1406 1403 coreconfigitem('worker', 'enabled',
1407 1404 default=True,
1408 1405 )
1409 1406 coreconfigitem('worker', 'numcpus',
1410 1407 default=None,
1411 1408 )
1412 1409
1413 1410 # Rebase related configuration moved to core because other extension are doing
1414 1411 # strange things. For example, shelve import the extensions to reuse some bit
1415 1412 # without formally loading it.
1416 1413 coreconfigitem('commands', 'rebase.requiredest',
1417 1414 default=False,
1418 1415 )
1419 1416 coreconfigitem('experimental', 'rebaseskipobsolete',
1420 1417 default=True,
1421 1418 )
1422 1419 coreconfigitem('rebase', 'singletransaction',
1423 1420 default=False,
1424 1421 )
1425 1422 coreconfigitem('rebase', 'experimental.inmemory',
1426 1423 default=False,
1427 1424 )
@@ -1,1784 +1,1804 b''
1 1 # stuff related specifically to patch manipulation / parsing
2 2 #
3 3 # Copyright 2008 Mark Edgington <edgimar@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 # This code is based on the Mark Edgington's crecord extension.
9 9 # (Itself based on Bryan O'Sullivan's record extension.)
10 10
11 11 from __future__ import absolute_import
12 12
13 13 import locale
14 14 import os
15 15 import re
16 16 import signal
17 17
18 18 from .i18n import _
19 19 from . import (
20 20 encoding,
21 21 error,
22 22 patch as patchmod,
23 23 scmutil,
24 24 util,
25 25 )
26 26 from .utils import (
27 27 stringutil,
28 28 )
29 29 stringio = util.stringio
30 30
31 31 # This is required for ncurses to display non-ASCII characters in default user
32 32 # locale encoding correctly. --immerrr
33 33 locale.setlocale(locale.LC_ALL, u'')
34 34
35 35 # patch comments based on the git one
36 36 diffhelptext = _("""# To remove '-' lines, make them ' ' lines (context).
37 37 # To remove '+' lines, delete them.
38 38 # Lines starting with # will be removed from the patch.
39 39 """)
40 40
41 41 hunkhelptext = _("""#
42 42 # If the patch applies cleanly, the edited hunk will immediately be
43 43 # added to the record list. If it does not apply cleanly, a rejects file
44 44 # will be generated. You can use that when you try again. If all lines
45 45 # of the hunk are removed, then the edit is aborted and the hunk is left
46 46 # unchanged.
47 47 """)
48 48
49 49 patchhelptext = _("""#
50 50 # If the patch applies cleanly, the edited patch will immediately
51 51 # be finalised. If it does not apply cleanly, rejects files will be
52 52 # generated. You can use those when you try again.
53 53 """)
54 54
55 55 try:
56 56 import curses
57 57 curses.error
58 58 except ImportError:
59 59 # I have no idea if wcurses works with crecord...
60 60 try:
61 61 import wcurses as curses
62 62 curses.error
63 63 except ImportError:
64 64 # wcurses is not shipped on Windows by default, or python is not
65 65 # compiled with curses
66 66 curses = False
67 67
68 68 class fallbackerror(error.Abort):
69 69 """Error that indicates the client should try to fallback to text mode."""
70 70 # Inherits from error.Abort so that existing behavior is preserved if the
71 71 # calling code does not know how to fallback.
72 72
73 73 def checkcurses(ui):
74 74 """Return True if the user wants to use curses
75 75
76 76 This method returns True if curses is found (and that python is built with
77 77 it) and that the user has the correct flag for the ui.
78 78 """
79 79 return curses and ui.interface("chunkselector") == "curses"
80 80
81 81 class patchnode(object):
82 82 """abstract class for patch graph nodes
83 83 (i.e. patchroot, header, hunk, hunkline)
84 84 """
85 85
86 86 def firstchild(self):
87 87 raise NotImplementedError("method must be implemented by subclass")
88 88
89 89 def lastchild(self):
90 90 raise NotImplementedError("method must be implemented by subclass")
91 91
92 92 def allchildren(self):
93 93 "Return a list of all of the direct children of this node"
94 94 raise NotImplementedError("method must be implemented by subclass")
95 95
96 96 def nextsibling(self):
97 97 """
98 98 Return the closest next item of the same type where there are no items
99 99 of different types between the current item and this closest item.
100 100 If no such item exists, return None.
101 101 """
102 102 raise NotImplementedError("method must be implemented by subclass")
103 103
104 104 def prevsibling(self):
105 105 """
106 106 Return the closest previous item of the same type where there are no
107 107 items of different types between the current item and this closest item.
108 108 If no such item exists, return None.
109 109 """
110 110 raise NotImplementedError("method must be implemented by subclass")
111 111
112 112 def parentitem(self):
113 113 raise NotImplementedError("method must be implemented by subclass")
114 114
115 115 def nextitem(self, skipfolded=True):
116 116 """
117 117 Try to return the next item closest to this item, regardless of item's
118 118 type (header, hunk, or hunkline).
119 119
120 120 If skipfolded == True, and the current item is folded, then the child
121 121 items that are hidden due to folding will be skipped when determining
122 122 the next item.
123 123
124 124 If it is not possible to get the next item, return None.
125 125 """
126 126 try:
127 127 itemfolded = self.folded
128 128 except AttributeError:
129 129 itemfolded = False
130 130 if skipfolded and itemfolded:
131 131 nextitem = self.nextsibling()
132 132 if nextitem is None:
133 133 try:
134 134 nextitem = self.parentitem().nextsibling()
135 135 except AttributeError:
136 136 nextitem = None
137 137 return nextitem
138 138 else:
139 139 # try child
140 140 item = self.firstchild()
141 141 if item is not None:
142 142 return item
143 143
144 144 # else try next sibling
145 145 item = self.nextsibling()
146 146 if item is not None:
147 147 return item
148 148
149 149 try:
150 150 # else try parent's next sibling
151 151 item = self.parentitem().nextsibling()
152 152 if item is not None:
153 153 return item
154 154
155 155 # else return grandparent's next sibling (or None)
156 156 return self.parentitem().parentitem().nextsibling()
157 157
158 158 except AttributeError: # parent and/or grandparent was None
159 159 return None
160 160
161 161 def previtem(self):
162 162 """
163 163 Try to return the previous item closest to this item, regardless of
164 164 item's type (header, hunk, or hunkline).
165 165
166 166 If it is not possible to get the previous item, return None.
167 167 """
168 168 # try previous sibling's last child's last child,
169 169 # else try previous sibling's last child, else try previous sibling
170 170 prevsibling = self.prevsibling()
171 171 if prevsibling is not None:
172 172 prevsiblinglastchild = prevsibling.lastchild()
173 173 if ((prevsiblinglastchild is not None) and
174 174 not prevsibling.folded):
175 175 prevsiblinglclc = prevsiblinglastchild.lastchild()
176 176 if ((prevsiblinglclc is not None) and
177 177 not prevsiblinglastchild.folded):
178 178 return prevsiblinglclc
179 179 else:
180 180 return prevsiblinglastchild
181 181 else:
182 182 return prevsibling
183 183
184 184 # try parent (or None)
185 185 return self.parentitem()
186 186
187 187 class patch(patchnode, list): # todo: rename patchroot
188 188 """
189 189 list of header objects representing the patch.
190 190 """
191 191 def __init__(self, headerlist):
192 192 self.extend(headerlist)
193 193 # add parent patch object reference to each header
194 194 for header in self:
195 195 header.patch = self
196 196
197 197 class uiheader(patchnode):
198 198 """patch header
199 199
200 200 xxx shouldn't we move this to mercurial/patch.py ?
201 201 """
202 202
203 203 def __init__(self, header):
204 204 self.nonuiheader = header
205 205 # flag to indicate whether to apply this chunk
206 206 self.applied = True
207 207 # flag which only affects the status display indicating if a node's
208 208 # children are partially applied (i.e. some applied, some not).
209 209 self.partial = False
210 210
211 211 # flag to indicate whether to display as folded/unfolded to user
212 212 self.folded = True
213 213
214 214 # list of all headers in patch
215 215 self.patch = None
216 216
217 217 # flag is False if this header was ever unfolded from initial state
218 218 self.neverunfolded = True
219 219 self.hunks = [uihunk(h, self) for h in self.hunks]
220 220
221 221 def prettystr(self):
222 222 x = stringio()
223 223 self.pretty(x)
224 224 return x.getvalue()
225 225
226 226 def nextsibling(self):
227 227 numheadersinpatch = len(self.patch)
228 228 indexofthisheader = self.patch.index(self)
229 229
230 230 if indexofthisheader < numheadersinpatch - 1:
231 231 nextheader = self.patch[indexofthisheader + 1]
232 232 return nextheader
233 233 else:
234 234 return None
235 235
236 236 def prevsibling(self):
237 237 indexofthisheader = self.patch.index(self)
238 238 if indexofthisheader > 0:
239 239 previousheader = self.patch[indexofthisheader - 1]
240 240 return previousheader
241 241 else:
242 242 return None
243 243
244 244 def parentitem(self):
245 245 """
246 246 there is no 'real' parent item of a header that can be selected,
247 247 so return None.
248 248 """
249 249 return None
250 250
251 251 def firstchild(self):
252 252 "return the first child of this item, if one exists. otherwise None."
253 253 if len(self.hunks) > 0:
254 254 return self.hunks[0]
255 255 else:
256 256 return None
257 257
258 258 def lastchild(self):
259 259 "return the last child of this item, if one exists. otherwise None."
260 260 if len(self.hunks) > 0:
261 261 return self.hunks[-1]
262 262 else:
263 263 return None
264 264
265 265 def allchildren(self):
266 266 "return a list of all of the direct children of this node"
267 267 return self.hunks
268 268
269 269 def __getattr__(self, name):
270 270 return getattr(self.nonuiheader, name)
271 271
272 272 class uihunkline(patchnode):
273 273 "represents a changed line in a hunk"
274 274 def __init__(self, linetext, hunk):
275 275 self.linetext = linetext
276 276 self.applied = True
277 277 # the parent hunk to which this line belongs
278 278 self.hunk = hunk
279 279 # folding lines currently is not used/needed, but this flag is needed
280 280 # in the previtem method.
281 281 self.folded = False
282 282
283 283 def prettystr(self):
284 284 return self.linetext
285 285
286 286 def nextsibling(self):
287 287 numlinesinhunk = len(self.hunk.changedlines)
288 288 indexofthisline = self.hunk.changedlines.index(self)
289 289
290 290 if (indexofthisline < numlinesinhunk - 1):
291 291 nextline = self.hunk.changedlines[indexofthisline + 1]
292 292 return nextline
293 293 else:
294 294 return None
295 295
296 296 def prevsibling(self):
297 297 indexofthisline = self.hunk.changedlines.index(self)
298 298 if indexofthisline > 0:
299 299 previousline = self.hunk.changedlines[indexofthisline - 1]
300 300 return previousline
301 301 else:
302 302 return None
303 303
304 304 def parentitem(self):
305 305 "return the parent to the current item"
306 306 return self.hunk
307 307
308 308 def firstchild(self):
309 309 "return the first child of this item, if one exists. otherwise None."
310 310 # hunk-lines don't have children
311 311 return None
312 312
313 313 def lastchild(self):
314 314 "return the last child of this item, if one exists. otherwise None."
315 315 # hunk-lines don't have children
316 316 return None
317 317
318 318 class uihunk(patchnode):
319 319 """ui patch hunk, wraps a hunk and keep track of ui behavior """
320 320 maxcontext = 3
321 321
322 322 def __init__(self, hunk, header):
323 323 self._hunk = hunk
324 324 self.changedlines = [uihunkline(line, self) for line in hunk.hunk]
325 325 self.header = header
326 326 # used at end for detecting how many removed lines were un-applied
327 327 self.originalremoved = self.removed
328 328
329 329 # flag to indicate whether to display as folded/unfolded to user
330 330 self.folded = True
331 331 # flag to indicate whether to apply this chunk
332 332 self.applied = True
333 333 # flag which only affects the status display indicating if a node's
334 334 # children are partially applied (i.e. some applied, some not).
335 335 self.partial = False
336 336
337 337 def nextsibling(self):
338 338 numhunksinheader = len(self.header.hunks)
339 339 indexofthishunk = self.header.hunks.index(self)
340 340
341 341 if (indexofthishunk < numhunksinheader - 1):
342 342 nexthunk = self.header.hunks[indexofthishunk + 1]
343 343 return nexthunk
344 344 else:
345 345 return None
346 346
347 347 def prevsibling(self):
348 348 indexofthishunk = self.header.hunks.index(self)
349 349 if indexofthishunk > 0:
350 350 previoushunk = self.header.hunks[indexofthishunk - 1]
351 351 return previoushunk
352 352 else:
353 353 return None
354 354
355 355 def parentitem(self):
356 356 "return the parent to the current item"
357 357 return self.header
358 358
359 359 def firstchild(self):
360 360 "return the first child of this item, if one exists. otherwise None."
361 361 if len(self.changedlines) > 0:
362 362 return self.changedlines[0]
363 363 else:
364 364 return None
365 365
366 366 def lastchild(self):
367 367 "return the last child of this item, if one exists. otherwise None."
368 368 if len(self.changedlines) > 0:
369 369 return self.changedlines[-1]
370 370 else:
371 371 return None
372 372
373 373 def allchildren(self):
374 374 "return a list of all of the direct children of this node"
375 375 return self.changedlines
376 376
377 377 def countchanges(self):
378 378 """changedlines -> (n+,n-)"""
379 379 add = len([l for l in self.changedlines if l.applied
380 380 and l.prettystr()[0] == '+'])
381 381 rem = len([l for l in self.changedlines if l.applied
382 382 and l.prettystr()[0] == '-'])
383 383 return add, rem
384 384
385 385 def getfromtoline(self):
386 386 # calculate the number of removed lines converted to context lines
387 387 removedconvertedtocontext = self.originalremoved - self.removed
388 388
389 389 contextlen = (len(self.before) + len(self.after) +
390 390 removedconvertedtocontext)
391 391 if self.after and self.after[-1] == '\\ No newline at end of file\n':
392 392 contextlen -= 1
393 393 fromlen = contextlen + self.removed
394 394 tolen = contextlen + self.added
395 395
396 396 # diffutils manual, section "2.2.2.2 detailed description of unified
397 397 # format": "an empty hunk is considered to end at the line that
398 398 # precedes the hunk."
399 399 #
400 400 # so, if either of hunks is empty, decrease its line start. --immerrr
401 401 # but only do this if fromline > 0, to avoid having, e.g fromline=-1.
402 402 fromline, toline = self.fromline, self.toline
403 403 if fromline != 0:
404 404 if fromlen == 0:
405 405 fromline -= 1
406 406 if tolen == 0 and toline > 0:
407 407 toline -= 1
408 408
409 409 fromtoline = '@@ -%d,%d +%d,%d @@%s\n' % (
410 410 fromline, fromlen, toline, tolen,
411 411 self.proc and (' ' + self.proc))
412 412 return fromtoline
413 413
414 414 def write(self, fp):
415 415 # updated self.added/removed, which are used by getfromtoline()
416 416 self.added, self.removed = self.countchanges()
417 417 fp.write(self.getfromtoline())
418 418
419 419 hunklinelist = []
420 420 # add the following to the list: (1) all applied lines, and
421 421 # (2) all unapplied removal lines (convert these to context lines)
422 422 for changedline in self.changedlines:
423 423 changedlinestr = changedline.prettystr()
424 424 if changedline.applied:
425 425 hunklinelist.append(changedlinestr)
426 426 elif changedlinestr[0] == "-":
427 427 hunklinelist.append(" " + changedlinestr[1:])
428 428
429 429 fp.write(''.join(self.before + hunklinelist + self.after))
430 430
431 431 pretty = write
432 432
433 433 def prettystr(self):
434 434 x = stringio()
435 435 self.pretty(x)
436 436 return x.getvalue()
437 437
438 438 def reversehunk(self):
439 439 """return a recordhunk which is the reverse of the hunk
440 440
441 441 Assuming the displayed patch is diff(A, B) result. The returned hunk is
442 442 intended to be applied to B, instead of A.
443 443
444 444 For example, when A is "0\n1\n2\n6\n" and B is "0\n3\n4\n5\n6\n", and
445 445 the user made the following selection:
446 446
447 447 0
448 448 [x] -1 [x]: selected
449 449 [ ] -2 [ ]: not selected
450 450 [x] +3
451 451 [ ] +4
452 452 [x] +5
453 453 6
454 454
455 455 This function returns a hunk like:
456 456
457 457 0
458 458 -3
459 459 -4
460 460 -5
461 461 +1
462 462 +4
463 463 6
464 464
465 465 Note "4" was first deleted then added. That's because "4" exists in B
466 466 side and "-4" must exist between "-3" and "-5" to make the patch
467 467 applicable to B.
468 468 """
469 469 dels = []
470 470 adds = []
471 471 for line in self.changedlines:
472 472 text = line.linetext
473 473 if line.applied:
474 474 if text[0] == '+':
475 475 dels.append(text[1:])
476 476 elif text[0] == '-':
477 477 adds.append(text[1:])
478 478 elif text[0] == '+':
479 479 dels.append(text[1:])
480 480 adds.append(text[1:])
481 481 hunk = ['-%s' % l for l in dels] + ['+%s' % l for l in adds]
482 482 h = self._hunk
483 483 return patchmod.recordhunk(h.header, h.toline, h.fromline, h.proc,
484 484 h.before, hunk, h.after)
485 485
486 486 def __getattr__(self, name):
487 487 return getattr(self._hunk, name)
488 488
489 489 def __repr__(self):
490 490 return '<hunk %r@%d>' % (self.filename(), self.fromline)
491 491
492 492 def filterpatch(ui, chunks, chunkselector, operation=None):
493 493 """interactively filter patch chunks into applied-only chunks"""
494 494 chunks = list(chunks)
495 495 # convert chunks list into structure suitable for displaying/modifying
496 496 # with curses. create a list of headers only.
497 497 headers = [c for c in chunks if isinstance(c, patchmod.header)]
498 498
499 499 # if there are no changed files
500 500 if len(headers) == 0:
501 501 return [], {}
502 502 uiheaders = [uiheader(h) for h in headers]
503 503 # let user choose headers/hunks/lines, and mark their applied flags
504 504 # accordingly
505 505 ret = chunkselector(ui, uiheaders, operation=operation)
506 506 appliedhunklist = []
507 507 for hdr in uiheaders:
508 508 if (hdr.applied and
509 509 (hdr.special() or len([h for h in hdr.hunks if h.applied]) > 0)):
510 510 appliedhunklist.append(hdr)
511 511 fixoffset = 0
512 512 for hnk in hdr.hunks:
513 513 if hnk.applied:
514 514 appliedhunklist.append(hnk)
515 515 # adjust the 'to'-line offset of the hunk to be correct
516 516 # after de-activating some of the other hunks for this file
517 517 if fixoffset:
518 518 #hnk = copy.copy(hnk) # necessary??
519 519 hnk.toline += fixoffset
520 520 else:
521 521 fixoffset += hnk.removed - hnk.added
522 522
523 523 return (appliedhunklist, ret)
524 524
525 525 def chunkselector(ui, headerlist, operation=None):
526 526 """
527 527 curses interface to get selection of chunks, and mark the applied flags
528 528 of the chosen chunks.
529 529 """
530 530 ui.write(_('starting interactive selection\n'))
531 531 chunkselector = curseschunkselector(headerlist, ui, operation)
532 532 origsigtstp = sentinel = object()
533 533 if util.safehasattr(signal, 'SIGTSTP'):
534 534 origsigtstp = signal.getsignal(signal.SIGTSTP)
535 535 try:
536 536 curses.wrapper(chunkselector.main)
537 537 if chunkselector.initexc is not None:
538 538 raise chunkselector.initexc
539 539 # ncurses does not restore signal handler for SIGTSTP
540 540 finally:
541 541 if origsigtstp is not sentinel:
542 542 signal.signal(signal.SIGTSTP, origsigtstp)
543 543 return chunkselector.opts
544 544
545 545 def testdecorator(testfn, f):
546 546 def u(*args, **kwargs):
547 547 return f(testfn, *args, **kwargs)
548 548 return u
549 549
550 550 def testchunkselector(testfn, ui, headerlist, operation=None):
551 551 """
552 552 test interface to get selection of chunks, and mark the applied flags
553 553 of the chosen chunks.
554 554 """
555 555 chunkselector = curseschunkselector(headerlist, ui, operation)
556 556 if testfn and os.path.exists(testfn):
557 557 testf = open(testfn, 'rb')
558 558 testcommands = [x.rstrip('\n') for x in testf.readlines()]
559 559 testf.close()
560 560 while True:
561 561 if chunkselector.handlekeypressed(testcommands.pop(0), test=True):
562 562 break
563 563 return chunkselector.opts
564 564
565 565 _headermessages = { # {operation: text}
566 566 'apply': _('Select hunks to apply'),
567 567 'discard': _('Select hunks to discard'),
568 568 None: _('Select hunks to record'),
569 569 }
570 570
571 571 class curseschunkselector(object):
572 572 def __init__(self, headerlist, ui, operation=None):
573 573 # put the headers into a patch object
574 574 self.headerlist = patch(headerlist)
575 575
576 576 self.ui = ui
577 577 self.opts = {}
578 578
579 579 self.errorstr = None
580 580 # list of all chunks
581 581 self.chunklist = []
582 582 for h in headerlist:
583 583 self.chunklist.append(h)
584 584 self.chunklist.extend(h.hunks)
585 585
586 586 # dictionary mapping (fgcolor, bgcolor) pairs to the
587 587 # corresponding curses color-pair value.
588 588 self.colorpairs = {}
589 589 # maps custom nicknames of color-pairs to curses color-pair values
590 590 self.colorpairnames = {}
591 591
592 592 # Honor color setting of ui section. Keep colored setup as
593 593 # long as not explicitly set to a falsy value - especially,
594 594 # when not set at all. This is to stay most compatible with
595 595 # previous (color only) behaviour.
596 596 uicolor = stringutil.parsebool(self.ui.config('ui', 'color'))
597 597 self.usecolor = uicolor is not False
598 598
599 599 # the currently selected header, hunk, or hunk-line
600 600 self.currentselecteditem = self.headerlist[0]
601 601
602 602 # updated when printing out patch-display -- the 'lines' here are the
603 603 # line positions *in the pad*, not on the screen.
604 604 self.selecteditemstartline = 0
605 605 self.selecteditemendline = None
606 606
607 607 # define indentation levels
608 608 self.headerindentnumchars = 0
609 609 self.hunkindentnumchars = 3
610 610 self.hunklineindentnumchars = 6
611 611
612 612 # the first line of the pad to print to the screen
613 613 self.firstlineofpadtoprint = 0
614 614
615 615 # keeps track of the number of lines in the pad
616 616 self.numpadlines = None
617 617
618 618 self.numstatuslines = 1
619 619
620 620 # keep a running count of the number of lines printed to the pad
621 621 # (used for determining when the selected item begins/ends)
622 622 self.linesprintedtopadsofar = 0
623 623
624 624 # the first line of the pad which is visible on the screen
625 625 self.firstlineofpadtoprint = 0
626 626
627 627 # stores optional text for a commit comment provided by the user
628 628 self.commenttext = ""
629 629
630 630 # if the last 'toggle all' command caused all changes to be applied
631 631 self.waslasttoggleallapplied = True
632 632
633 633 # affects some ui text
634 634 if operation not in _headermessages:
635 635 raise error.ProgrammingError('unexpected operation: %s' % operation)
636 636 self.operation = operation
637 637
638 638 def uparrowevent(self):
639 639 """
640 640 try to select the previous item to the current item that has the
641 641 most-indented level. for example, if a hunk is selected, try to select
642 642 the last hunkline of the hunk prior to the selected hunk. or, if
643 643 the first hunkline of a hunk is currently selected, then select the
644 644 hunk itself.
645 645 """
646 646 currentitem = self.currentselecteditem
647 647
648 648 nextitem = currentitem.previtem()
649 649
650 650 if nextitem is None:
651 651 # if no parent item (i.e. currentitem is the first header), then
652 652 # no change...
653 653 nextitem = currentitem
654 654
655 655 self.currentselecteditem = nextitem
656 656
657 657 def uparrowshiftevent(self):
658 658 """
659 659 select (if possible) the previous item on the same level as the
660 660 currently selected item. otherwise, select (if possible) the
661 661 parent-item of the currently selected item.
662 662 """
663 663 currentitem = self.currentselecteditem
664 664 nextitem = currentitem.prevsibling()
665 665 # if there's no previous sibling, try choosing the parent
666 666 if nextitem is None:
667 667 nextitem = currentitem.parentitem()
668 668 if nextitem is None:
669 669 # if no parent item (i.e. currentitem is the first header), then
670 670 # no change...
671 671 nextitem = currentitem
672 672
673 673 self.currentselecteditem = nextitem
674 674 self.recenterdisplayedarea()
675 675
676 676 def downarrowevent(self):
677 677 """
678 678 try to select the next item to the current item that has the
679 679 most-indented level. for example, if a hunk is selected, select
680 680 the first hunkline of the selected hunk. or, if the last hunkline of
681 681 a hunk is currently selected, then select the next hunk, if one exists,
682 682 or if not, the next header if one exists.
683 683 """
684 684 #self.startprintline += 1 #debug
685 685 currentitem = self.currentselecteditem
686 686
687 687 nextitem = currentitem.nextitem()
688 688 # if there's no next item, keep the selection as-is
689 689 if nextitem is None:
690 690 nextitem = currentitem
691 691
692 692 self.currentselecteditem = nextitem
693 693
694 694 def downarrowshiftevent(self):
695 695 """
696 696 select (if possible) the next item on the same level as the currently
697 697 selected item. otherwise, select (if possible) the next item on the
698 698 same level as the parent item of the currently selected item.
699 699 """
700 700 currentitem = self.currentselecteditem
701 701 nextitem = currentitem.nextsibling()
702 702 # if there's no next sibling, try choosing the parent's nextsibling
703 703 if nextitem is None:
704 704 try:
705 705 nextitem = currentitem.parentitem().nextsibling()
706 706 except AttributeError:
707 707 # parentitem returned None, so nextsibling() can't be called
708 708 nextitem = None
709 709 if nextitem is None:
710 710 # if parent has no next sibling, then no change...
711 711 nextitem = currentitem
712 712
713 713 self.currentselecteditem = nextitem
714 714 self.recenterdisplayedarea()
715 715
716 def nextsametype(self):
717 currentitem = self.currentselecteditem
718 sametype = lambda item: isinstance(item, type(currentitem))
719 nextitem = currentitem.nextitem()
720
721 while nextitem is not None and not sametype(nextitem):
722 nextitem = nextitem.nextitem()
723
724 if nextitem is None:
725 nextitem = currentitem
726 else:
727 parent = nextitem.parentitem()
728 if parent.folded:
729 self.togglefolded(parent)
730
731 self.currentselecteditem = nextitem
732 self.recenterdisplayedarea()
733
716 734 def rightarrowevent(self):
717 735 """
718 736 select (if possible) the first of this item's child-items.
719 737 """
720 738 currentitem = self.currentselecteditem
721 739 nextitem = currentitem.firstchild()
722 740
723 741 # turn off folding if we want to show a child-item
724 742 if currentitem.folded:
725 743 self.togglefolded(currentitem)
726 744
727 745 if nextitem is None:
728 746 # if no next item on parent-level, then no change...
729 747 nextitem = currentitem
730 748
731 749 self.currentselecteditem = nextitem
732 750
733 751 def leftarrowevent(self):
734 752 """
735 753 if the current item can be folded (i.e. it is an unfolded header or
736 754 hunk), then fold it. otherwise try select (if possible) the parent
737 755 of this item.
738 756 """
739 757 currentitem = self.currentselecteditem
740 758
741 759 # try to fold the item
742 760 if not isinstance(currentitem, uihunkline):
743 761 if not currentitem.folded:
744 762 self.togglefolded(item=currentitem)
745 763 return
746 764
747 765 # if it can't be folded, try to select the parent item
748 766 nextitem = currentitem.parentitem()
749 767
750 768 if nextitem is None:
751 769 # if no item on parent-level, then no change...
752 770 nextitem = currentitem
753 771 if not nextitem.folded:
754 772 self.togglefolded(item=nextitem)
755 773
756 774 self.currentselecteditem = nextitem
757 775
758 776 def leftarrowshiftevent(self):
759 777 """
760 778 select the header of the current item (or fold current item if the
761 779 current item is already a header).
762 780 """
763 781 currentitem = self.currentselecteditem
764 782
765 783 if isinstance(currentitem, uiheader):
766 784 if not currentitem.folded:
767 785 self.togglefolded(item=currentitem)
768 786 return
769 787
770 788 # select the parent item recursively until we're at a header
771 789 while True:
772 790 nextitem = currentitem.parentitem()
773 791 if nextitem is None:
774 792 break
775 793 else:
776 794 currentitem = nextitem
777 795
778 796 self.currentselecteditem = currentitem
779 797
780 798 def updatescroll(self):
781 799 "scroll the screen to fully show the currently-selected"
782 800 selstart = self.selecteditemstartline
783 801 selend = self.selecteditemendline
784 802
785 803 padstart = self.firstlineofpadtoprint
786 804 padend = padstart + self.yscreensize - self.numstatuslines - 1
787 805 # 'buffered' pad start/end values which scroll with a certain
788 806 # top/bottom context margin
789 807 padstartbuffered = padstart + 3
790 808 padendbuffered = padend - 3
791 809
792 810 if selend > padendbuffered:
793 811 self.scrolllines(selend - padendbuffered)
794 812 elif selstart < padstartbuffered:
795 813 # negative values scroll in pgup direction
796 814 self.scrolllines(selstart - padstartbuffered)
797 815
798 816 def scrolllines(self, numlines):
799 817 "scroll the screen up (down) by numlines when numlines >0 (<0)."
800 818 self.firstlineofpadtoprint += numlines
801 819 if self.firstlineofpadtoprint < 0:
802 820 self.firstlineofpadtoprint = 0
803 821 if self.firstlineofpadtoprint > self.numpadlines - 1:
804 822 self.firstlineofpadtoprint = self.numpadlines - 1
805 823
806 824 def toggleapply(self, item=None):
807 825 """
808 826 toggle the applied flag of the specified item. if no item is specified,
809 827 toggle the flag of the currently selected item.
810 828 """
811 829 if item is None:
812 830 item = self.currentselecteditem
813 831
814 832 item.applied = not item.applied
815 833
816 834 if isinstance(item, uiheader):
817 835 item.partial = False
818 836 if item.applied:
819 837 # apply all its hunks
820 838 for hnk in item.hunks:
821 839 hnk.applied = True
822 840 # apply all their hunklines
823 841 for hunkline in hnk.changedlines:
824 842 hunkline.applied = True
825 843 else:
826 844 # un-apply all its hunks
827 845 for hnk in item.hunks:
828 846 hnk.applied = False
829 847 hnk.partial = False
830 848 # un-apply all their hunklines
831 849 for hunkline in hnk.changedlines:
832 850 hunkline.applied = False
833 851 elif isinstance(item, uihunk):
834 852 item.partial = False
835 853 # apply all it's hunklines
836 854 for hunkline in item.changedlines:
837 855 hunkline.applied = item.applied
838 856
839 857 siblingappliedstatus = [hnk.applied for hnk in item.header.hunks]
840 858 allsiblingsapplied = not (False in siblingappliedstatus)
841 859 nosiblingsapplied = not (True in siblingappliedstatus)
842 860
843 861 siblingspartialstatus = [hnk.partial for hnk in item.header.hunks]
844 862 somesiblingspartial = (True in siblingspartialstatus)
845 863
846 864 #cases where applied or partial should be removed from header
847 865
848 866 # if no 'sibling' hunks are applied (including this hunk)
849 867 if nosiblingsapplied:
850 868 if not item.header.special():
851 869 item.header.applied = False
852 870 item.header.partial = False
853 871 else: # some/all parent siblings are applied
854 872 item.header.applied = True
855 873 item.header.partial = (somesiblingspartial or
856 874 not allsiblingsapplied)
857 875
858 876 elif isinstance(item, uihunkline):
859 877 siblingappliedstatus = [ln.applied for ln in item.hunk.changedlines]
860 878 allsiblingsapplied = not (False in siblingappliedstatus)
861 879 nosiblingsapplied = not (True in siblingappliedstatus)
862 880
863 881 # if no 'sibling' lines are applied
864 882 if nosiblingsapplied:
865 883 item.hunk.applied = False
866 884 item.hunk.partial = False
867 885 elif allsiblingsapplied:
868 886 item.hunk.applied = True
869 887 item.hunk.partial = False
870 888 else: # some siblings applied
871 889 item.hunk.applied = True
872 890 item.hunk.partial = True
873 891
874 892 parentsiblingsapplied = [hnk.applied for hnk
875 893 in item.hunk.header.hunks]
876 894 noparentsiblingsapplied = not (True in parentsiblingsapplied)
877 895 allparentsiblingsapplied = not (False in parentsiblingsapplied)
878 896
879 897 parentsiblingspartial = [hnk.partial for hnk
880 898 in item.hunk.header.hunks]
881 899 someparentsiblingspartial = (True in parentsiblingspartial)
882 900
883 901 # if all parent hunks are not applied, un-apply header
884 902 if noparentsiblingsapplied:
885 903 if not item.hunk.header.special():
886 904 item.hunk.header.applied = False
887 905 item.hunk.header.partial = False
888 906 # set the applied and partial status of the header if needed
889 907 else: # some/all parent siblings are applied
890 908 item.hunk.header.applied = True
891 909 item.hunk.header.partial = (someparentsiblingspartial or
892 910 not allparentsiblingsapplied)
893 911
894 912 def toggleall(self):
895 913 "toggle the applied flag of all items."
896 914 if self.waslasttoggleallapplied: # then unapply them this time
897 915 for item in self.headerlist:
898 916 if item.applied:
899 917 self.toggleapply(item)
900 918 else:
901 919 for item in self.headerlist:
902 920 if not item.applied:
903 921 self.toggleapply(item)
904 922 self.waslasttoggleallapplied = not self.waslasttoggleallapplied
905 923
906 924 def togglefolded(self, item=None, foldparent=False):
907 925 "toggle folded flag of specified item (defaults to currently selected)"
908 926 if item is None:
909 927 item = self.currentselecteditem
910 928 if foldparent or (isinstance(item, uiheader) and item.neverunfolded):
911 929 if not isinstance(item, uiheader):
912 930 # we need to select the parent item in this case
913 931 self.currentselecteditem = item = item.parentitem()
914 932 elif item.neverunfolded:
915 933 item.neverunfolded = False
916 934
917 935 # also fold any foldable children of the parent/current item
918 936 if isinstance(item, uiheader): # the original or 'new' item
919 937 for child in item.allchildren():
920 938 child.folded = not item.folded
921 939
922 940 if isinstance(item, (uiheader, uihunk)):
923 941 item.folded = not item.folded
924 942
925 943 def alignstring(self, instr, window):
926 944 """
927 945 add whitespace to the end of a string in order to make it fill
928 946 the screen in the x direction. the current cursor position is
929 947 taken into account when making this calculation. the string can span
930 948 multiple lines.
931 949 """
932 950 y, xstart = window.getyx()
933 951 width = self.xscreensize
934 952 # turn tabs into spaces
935 953 instr = instr.expandtabs(4)
936 954 strwidth = encoding.colwidth(instr)
937 955 numspaces = (width - ((strwidth + xstart) % width) - 1)
938 956 return instr + " " * numspaces + "\n"
939 957
940 958 def printstring(self, window, text, fgcolor=None, bgcolor=None, pair=None,
941 959 pairname=None, attrlist=None, towin=True, align=True, showwhtspc=False):
942 960 """
943 961 print the string, text, with the specified colors and attributes, to
944 962 the specified curses window object.
945 963
946 964 the foreground and background colors are of the form
947 965 curses.color_xxxx, where xxxx is one of: [black, blue, cyan, green,
948 966 magenta, red, white, yellow]. if pairname is provided, a color
949 967 pair will be looked up in the self.colorpairnames dictionary.
950 968
951 969 attrlist is a list containing text attributes in the form of
952 970 curses.a_xxxx, where xxxx can be: [bold, dim, normal, standout,
953 971 underline].
954 972
955 973 if align == True, whitespace is added to the printed string such that
956 974 the string stretches to the right border of the window.
957 975
958 976 if showwhtspc == True, trailing whitespace of a string is highlighted.
959 977 """
960 978 # preprocess the text, converting tabs to spaces
961 979 text = text.expandtabs(4)
962 980 # strip \n, and convert control characters to ^[char] representation
963 981 text = re.sub(br'[\x00-\x08\x0a-\x1f]',
964 982 lambda m:'^' + chr(ord(m.group()) + 64), text.strip('\n'))
965 983
966 984 if pair is not None:
967 985 colorpair = pair
968 986 elif pairname is not None:
969 987 colorpair = self.colorpairnames[pairname]
970 988 else:
971 989 if fgcolor is None:
972 990 fgcolor = -1
973 991 if bgcolor is None:
974 992 bgcolor = -1
975 993 if (fgcolor, bgcolor) in self.colorpairs:
976 994 colorpair = self.colorpairs[(fgcolor, bgcolor)]
977 995 else:
978 996 colorpair = self.getcolorpair(fgcolor, bgcolor)
979 997 # add attributes if possible
980 998 if attrlist is None:
981 999 attrlist = []
982 1000 if colorpair < 256:
983 1001 # then it is safe to apply all attributes
984 1002 for textattr in attrlist:
985 1003 colorpair |= textattr
986 1004 else:
987 1005 # just apply a select few (safe?) attributes
988 1006 for textattr in (curses.A_UNDERLINE, curses.A_BOLD):
989 1007 if textattr in attrlist:
990 1008 colorpair |= textattr
991 1009
992 1010 y, xstart = self.chunkpad.getyx()
993 1011 t = "" # variable for counting lines printed
994 1012 # if requested, show trailing whitespace
995 1013 if showwhtspc:
996 1014 origlen = len(text)
997 1015 text = text.rstrip(' \n') # tabs have already been expanded
998 1016 strippedlen = len(text)
999 1017 numtrailingspaces = origlen - strippedlen
1000 1018
1001 1019 if towin:
1002 1020 window.addstr(text, colorpair)
1003 1021 t += text
1004 1022
1005 1023 if showwhtspc:
1006 1024 wscolorpair = colorpair | curses.A_REVERSE
1007 1025 if towin:
1008 1026 for i in range(numtrailingspaces):
1009 1027 window.addch(curses.ACS_CKBOARD, wscolorpair)
1010 1028 t += " " * numtrailingspaces
1011 1029
1012 1030 if align:
1013 1031 if towin:
1014 1032 extrawhitespace = self.alignstring("", window)
1015 1033 window.addstr(extrawhitespace, colorpair)
1016 1034 else:
1017 1035 # need to use t, since the x position hasn't incremented
1018 1036 extrawhitespace = self.alignstring(t, window)
1019 1037 t += extrawhitespace
1020 1038
1021 1039 # is reset to 0 at the beginning of printitem()
1022 1040
1023 1041 linesprinted = (xstart + len(t)) / self.xscreensize
1024 1042 self.linesprintedtopadsofar += linesprinted
1025 1043 return t
1026 1044
1027 1045 def _getstatuslinesegments(self):
1028 1046 """-> [str]. return segments"""
1029 1047 selected = self.currentselecteditem.applied
1030 spaceselect = _('space: select')
1031 spacedeselect = _('space: deselect')
1048 spaceselect = _('space/enter: select')
1049 spacedeselect = _('space/enter: deselect')
1032 1050 # Format the selected label into a place as long as the longer of the
1033 1051 # two possible labels. This may vary by language.
1034 1052 spacelen = max(len(spaceselect), len(spacedeselect))
1035 1053 selectedlabel = '%-*s' % (spacelen,
1036 1054 spacedeselect if selected else spaceselect)
1037 1055 segments = [
1038 1056 _headermessages[self.operation],
1039 1057 '-',
1040 1058 _('[x]=selected **=collapsed'),
1041 1059 _('c: confirm'),
1042 1060 _('q: abort'),
1043 1061 _('arrow keys: move/expand/collapse'),
1044 1062 selectedlabel,
1045 1063 _('?: help'),
1046 1064 ]
1047 1065 return segments
1048 1066
1049 1067 def _getstatuslines(self):
1050 1068 """() -> [str]. return short help used in the top status window"""
1051 1069 if self.errorstr is not None:
1052 1070 lines = [self.errorstr, _('Press any key to continue')]
1053 1071 else:
1054 1072 # wrap segments to lines
1055 1073 segments = self._getstatuslinesegments()
1056 1074 width = self.xscreensize
1057 1075 lines = []
1058 1076 lastwidth = width
1059 1077 for s in segments:
1060 1078 w = encoding.colwidth(s)
1061 1079 sep = ' ' * (1 + (s and s[0] not in '-['))
1062 1080 if lastwidth + w + len(sep) >= width:
1063 1081 lines.append(s)
1064 1082 lastwidth = w
1065 1083 else:
1066 1084 lines[-1] += sep + s
1067 1085 lastwidth += w + len(sep)
1068 1086 if len(lines) != self.numstatuslines:
1069 1087 self.numstatuslines = len(lines)
1070 1088 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1071 1089 return [stringutil.ellipsis(l, self.xscreensize - 1) for l in lines]
1072 1090
1073 1091 def updatescreen(self):
1074 1092 self.statuswin.erase()
1075 1093 self.chunkpad.erase()
1076 1094
1077 1095 printstring = self.printstring
1078 1096
1079 1097 # print out the status lines at the top
1080 1098 try:
1081 1099 for line in self._getstatuslines():
1082 1100 printstring(self.statuswin, line, pairname="legend")
1083 1101 self.statuswin.refresh()
1084 1102 except curses.error:
1085 1103 pass
1086 1104 if self.errorstr is not None:
1087 1105 return
1088 1106
1089 1107 # print out the patch in the remaining part of the window
1090 1108 try:
1091 1109 self.printitem()
1092 1110 self.updatescroll()
1093 1111 self.chunkpad.refresh(self.firstlineofpadtoprint, 0,
1094 1112 self.numstatuslines, 0,
1095 1113 self.yscreensize - self.numstatuslines,
1096 1114 self.xscreensize)
1097 1115 except curses.error:
1098 1116 pass
1099 1117
1100 1118 def getstatusprefixstring(self, item):
1101 1119 """
1102 1120 create a string to prefix a line with which indicates whether 'item'
1103 1121 is applied and/or folded.
1104 1122 """
1105 1123
1106 1124 # create checkbox string
1107 1125 if item.applied:
1108 1126 if not isinstance(item, uihunkline) and item.partial:
1109 1127 checkbox = "[~]"
1110 1128 else:
1111 1129 checkbox = "[x]"
1112 1130 else:
1113 1131 checkbox = "[ ]"
1114 1132
1115 1133 try:
1116 1134 if item.folded:
1117 1135 checkbox += "**"
1118 1136 if isinstance(item, uiheader):
1119 1137 # one of "m", "a", or "d" (modified, added, deleted)
1120 1138 filestatus = item.changetype
1121 1139
1122 1140 checkbox += filestatus + " "
1123 1141 else:
1124 1142 checkbox += " "
1125 1143 if isinstance(item, uiheader):
1126 1144 # add two more spaces for headers
1127 1145 checkbox += " "
1128 1146 except AttributeError: # not foldable
1129 1147 checkbox += " "
1130 1148
1131 1149 return checkbox
1132 1150
1133 1151 def printheader(self, header, selected=False, towin=True,
1134 1152 ignorefolding=False):
1135 1153 """
1136 1154 print the header to the pad. if countlines is True, don't print
1137 1155 anything, but just count the number of lines which would be printed.
1138 1156 """
1139 1157
1140 1158 outstr = ""
1141 1159 text = header.prettystr()
1142 1160 chunkindex = self.chunklist.index(header)
1143 1161
1144 1162 if chunkindex != 0 and not header.folded:
1145 1163 # add separating line before headers
1146 1164 outstr += self.printstring(self.chunkpad, '_' * self.xscreensize,
1147 1165 towin=towin, align=False)
1148 1166 # select color-pair based on if the header is selected
1149 1167 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1150 1168 attrlist=[curses.A_BOLD])
1151 1169
1152 1170 # print out each line of the chunk, expanding it to screen width
1153 1171
1154 1172 # number of characters to indent lines on this level by
1155 1173 indentnumchars = 0
1156 1174 checkbox = self.getstatusprefixstring(header)
1157 1175 if not header.folded or ignorefolding:
1158 1176 textlist = text.split("\n")
1159 1177 linestr = checkbox + textlist[0]
1160 1178 else:
1161 1179 linestr = checkbox + header.filename()
1162 1180 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1163 1181 towin=towin)
1164 1182 if not header.folded or ignorefolding:
1165 1183 if len(textlist) > 1:
1166 1184 for line in textlist[1:]:
1167 1185 linestr = " "*(indentnumchars + len(checkbox)) + line
1168 1186 outstr += self.printstring(self.chunkpad, linestr,
1169 1187 pair=colorpair, towin=towin)
1170 1188
1171 1189 return outstr
1172 1190
1173 1191 def printhunklinesbefore(self, hunk, selected=False, towin=True,
1174 1192 ignorefolding=False):
1175 1193 "includes start/end line indicator"
1176 1194 outstr = ""
1177 1195 # where hunk is in list of siblings
1178 1196 hunkindex = hunk.header.hunks.index(hunk)
1179 1197
1180 1198 if hunkindex != 0:
1181 1199 # add separating line before headers
1182 1200 outstr += self.printstring(self.chunkpad, ' '*self.xscreensize,
1183 1201 towin=towin, align=False)
1184 1202
1185 1203 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1186 1204 attrlist=[curses.A_BOLD])
1187 1205
1188 1206 # print out from-to line with checkbox
1189 1207 checkbox = self.getstatusprefixstring(hunk)
1190 1208
1191 1209 lineprefix = " "*self.hunkindentnumchars + checkbox
1192 1210 frtoline = " " + hunk.getfromtoline().strip("\n")
1193 1211
1194 1212 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1195 1213 align=False) # add uncolored checkbox/indent
1196 1214 outstr += self.printstring(self.chunkpad, frtoline, pair=colorpair,
1197 1215 towin=towin)
1198 1216
1199 1217 if hunk.folded and not ignorefolding:
1200 1218 # skip remainder of output
1201 1219 return outstr
1202 1220
1203 1221 # print out lines of the chunk preceeding changed-lines
1204 1222 for line in hunk.before:
1205 1223 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1206 1224 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1207 1225
1208 1226 return outstr
1209 1227
1210 1228 def printhunklinesafter(self, hunk, towin=True, ignorefolding=False):
1211 1229 outstr = ""
1212 1230 if hunk.folded and not ignorefolding:
1213 1231 return outstr
1214 1232
1215 1233 # a bit superfluous, but to avoid hard-coding indent amount
1216 1234 checkbox = self.getstatusprefixstring(hunk)
1217 1235 for line in hunk.after:
1218 1236 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1219 1237 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1220 1238
1221 1239 return outstr
1222 1240
1223 1241 def printhunkchangedline(self, hunkline, selected=False, towin=True):
1224 1242 outstr = ""
1225 1243 checkbox = self.getstatusprefixstring(hunkline)
1226 1244
1227 1245 linestr = hunkline.prettystr().strip("\n")
1228 1246
1229 1247 # select color-pair based on whether line is an addition/removal
1230 1248 if selected:
1231 1249 colorpair = self.getcolorpair(name="selected")
1232 1250 elif linestr.startswith("+"):
1233 1251 colorpair = self.getcolorpair(name="addition")
1234 1252 elif linestr.startswith("-"):
1235 1253 colorpair = self.getcolorpair(name="deletion")
1236 1254 elif linestr.startswith("\\"):
1237 1255 colorpair = self.getcolorpair(name="normal")
1238 1256
1239 1257 lineprefix = " "*self.hunklineindentnumchars + checkbox
1240 1258 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1241 1259 align=False) # add uncolored checkbox/indent
1242 1260 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1243 1261 towin=towin, showwhtspc=True)
1244 1262 return outstr
1245 1263
1246 1264 def printitem(self, item=None, ignorefolding=False, recursechildren=True,
1247 1265 towin=True):
1248 1266 """
1249 1267 use __printitem() to print the the specified item.applied.
1250 1268 if item is not specified, then print the entire patch.
1251 1269 (hiding folded elements, etc. -- see __printitem() docstring)
1252 1270 """
1253 1271
1254 1272 if item is None:
1255 1273 item = self.headerlist
1256 1274 if recursechildren:
1257 1275 self.linesprintedtopadsofar = 0
1258 1276
1259 1277 outstr = []
1260 1278 self.__printitem(item, ignorefolding, recursechildren, outstr,
1261 1279 towin=towin)
1262 1280 return ''.join(outstr)
1263 1281
1264 1282 def outofdisplayedarea(self):
1265 1283 y, _ = self.chunkpad.getyx() # cursor location
1266 1284 # * 2 here works but an optimization would be the max number of
1267 1285 # consecutive non selectable lines
1268 1286 # i.e the max number of context line for any hunk in the patch
1269 1287 miny = min(0, self.firstlineofpadtoprint - self.yscreensize)
1270 1288 maxy = self.firstlineofpadtoprint + self.yscreensize * 2
1271 1289 return y < miny or y > maxy
1272 1290
1273 1291 def handleselection(self, item, recursechildren):
1274 1292 selected = (item is self.currentselecteditem)
1275 1293 if selected and recursechildren:
1276 1294 # assumes line numbering starting from line 0
1277 1295 self.selecteditemstartline = self.linesprintedtopadsofar
1278 1296 selecteditemlines = self.getnumlinesdisplayed(item,
1279 1297 recursechildren=False)
1280 1298 self.selecteditemendline = (self.selecteditemstartline +
1281 1299 selecteditemlines - 1)
1282 1300 return selected
1283 1301
1284 1302 def __printitem(self, item, ignorefolding, recursechildren, outstr,
1285 1303 towin=True):
1286 1304 """
1287 1305 recursive method for printing out patch/header/hunk/hunk-line data to
1288 1306 screen. also returns a string with all of the content of the displayed
1289 1307 patch (not including coloring, etc.).
1290 1308
1291 1309 if ignorefolding is True, then folded items are printed out.
1292 1310
1293 1311 if recursechildren is False, then only print the item without its
1294 1312 child items.
1295 1313 """
1296 1314
1297 1315 if towin and self.outofdisplayedarea():
1298 1316 return
1299 1317
1300 1318 selected = self.handleselection(item, recursechildren)
1301 1319
1302 1320 # patch object is a list of headers
1303 1321 if isinstance(item, patch):
1304 1322 if recursechildren:
1305 1323 for hdr in item:
1306 1324 self.__printitem(hdr, ignorefolding,
1307 1325 recursechildren, outstr, towin)
1308 1326 # todo: eliminate all isinstance() calls
1309 1327 if isinstance(item, uiheader):
1310 1328 outstr.append(self.printheader(item, selected, towin=towin,
1311 1329 ignorefolding=ignorefolding))
1312 1330 if recursechildren:
1313 1331 for hnk in item.hunks:
1314 1332 self.__printitem(hnk, ignorefolding,
1315 1333 recursechildren, outstr, towin)
1316 1334 elif (isinstance(item, uihunk) and
1317 1335 ((not item.header.folded) or ignorefolding)):
1318 1336 # print the hunk data which comes before the changed-lines
1319 1337 outstr.append(self.printhunklinesbefore(item, selected, towin=towin,
1320 1338 ignorefolding=ignorefolding))
1321 1339 if recursechildren:
1322 1340 for l in item.changedlines:
1323 1341 self.__printitem(l, ignorefolding,
1324 1342 recursechildren, outstr, towin)
1325 1343 outstr.append(self.printhunklinesafter(item, towin=towin,
1326 1344 ignorefolding=ignorefolding))
1327 1345 elif (isinstance(item, uihunkline) and
1328 1346 ((not item.hunk.folded) or ignorefolding)):
1329 1347 outstr.append(self.printhunkchangedline(item, selected,
1330 1348 towin=towin))
1331 1349
1332 1350 return outstr
1333 1351
1334 1352 def getnumlinesdisplayed(self, item=None, ignorefolding=False,
1335 1353 recursechildren=True):
1336 1354 """
1337 1355 return the number of lines which would be displayed if the item were
1338 1356 to be printed to the display. the item will not be printed to the
1339 1357 display (pad).
1340 1358 if no item is given, assume the entire patch.
1341 1359 if ignorefolding is True, folded items will be unfolded when counting
1342 1360 the number of lines.
1343 1361 """
1344 1362
1345 1363 # temporarily disable printing to windows by printstring
1346 1364 patchdisplaystring = self.printitem(item, ignorefolding,
1347 1365 recursechildren, towin=False)
1348 1366 numlines = len(patchdisplaystring) // self.xscreensize
1349 1367 return numlines
1350 1368
1351 1369 def sigwinchhandler(self, n, frame):
1352 1370 "handle window resizing"
1353 1371 try:
1354 1372 curses.endwin()
1355 1373 self.xscreensize, self.yscreensize = scmutil.termsize(self.ui)
1356 1374 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1357 1375 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1358 1376 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1359 1377 except curses.error:
1360 1378 pass
1361 1379
1362 1380 def getcolorpair(self, fgcolor=None, bgcolor=None, name=None,
1363 1381 attrlist=None):
1364 1382 """
1365 1383 get a curses color pair, adding it to self.colorpairs if it is not
1366 1384 already defined. an optional string, name, can be passed as a shortcut
1367 1385 for referring to the color-pair. by default, if no arguments are
1368 1386 specified, the white foreground / black background color-pair is
1369 1387 returned.
1370 1388
1371 1389 it is expected that this function will be used exclusively for
1372 1390 initializing color pairs, and not curses.init_pair().
1373 1391
1374 1392 attrlist is used to 'flavor' the returned color-pair. this information
1375 1393 is not stored in self.colorpairs. it contains attribute values like
1376 1394 curses.A_BOLD.
1377 1395 """
1378 1396
1379 1397 if (name is not None) and name in self.colorpairnames:
1380 1398 # then get the associated color pair and return it
1381 1399 colorpair = self.colorpairnames[name]
1382 1400 else:
1383 1401 if fgcolor is None:
1384 1402 fgcolor = -1
1385 1403 if bgcolor is None:
1386 1404 bgcolor = -1
1387 1405 if (fgcolor, bgcolor) in self.colorpairs:
1388 1406 colorpair = self.colorpairs[(fgcolor, bgcolor)]
1389 1407 else:
1390 1408 pairindex = len(self.colorpairs) + 1
1391 1409 if self.usecolor:
1392 1410 curses.init_pair(pairindex, fgcolor, bgcolor)
1393 1411 colorpair = self.colorpairs[(fgcolor, bgcolor)] = (
1394 1412 curses.color_pair(pairindex))
1395 1413 if name is not None:
1396 1414 self.colorpairnames[name] = curses.color_pair(pairindex)
1397 1415 else:
1398 1416 cval = 0
1399 1417 if name is not None:
1400 1418 if name == 'selected':
1401 1419 cval = curses.A_REVERSE
1402 1420 self.colorpairnames[name] = cval
1403 1421 colorpair = self.colorpairs[(fgcolor, bgcolor)] = cval
1404 1422
1405 1423 # add attributes if possible
1406 1424 if attrlist is None:
1407 1425 attrlist = []
1408 1426 if colorpair < 256:
1409 1427 # then it is safe to apply all attributes
1410 1428 for textattr in attrlist:
1411 1429 colorpair |= textattr
1412 1430 else:
1413 1431 # just apply a select few (safe?) attributes
1414 1432 for textattrib in (curses.A_UNDERLINE, curses.A_BOLD):
1415 1433 if textattrib in attrlist:
1416 1434 colorpair |= textattrib
1417 1435 return colorpair
1418 1436
1419 1437 def initcolorpair(self, *args, **kwargs):
1420 1438 "same as getcolorpair."
1421 1439 self.getcolorpair(*args, **kwargs)
1422 1440
1423 1441 def helpwindow(self):
1424 1442 "print a help window to the screen. exit after any keypress."
1425 1443 helptext = _(
1426 1444 """ [press any key to return to the patch-display]
1427 1445
1428 1446 crecord allows you to interactively choose among the changes you have made,
1429 1447 and confirm only those changes you select for further processing by the command
1430 1448 you are running (commit/shelve/revert), after confirming the selected
1431 1449 changes, the unselected changes are still present in your working copy, so you
1432 1450 can use crecord multiple times to split large changes into smaller changesets.
1433 1451 the following are valid keystrokes:
1434 1452
1435 1453 [space] : (un-)select item ([~]/[x] = partly/fully applied)
1454 [enter] : (un-)select item and go to next item of same type
1436 1455 A : (un-)select all items
1437 1456 up/down-arrow [k/j] : go to previous/next unfolded item
1438 1457 pgup/pgdn [K/J] : go to previous/next item of same type
1439 1458 right/left-arrow [l/h] : go to child item / parent item
1440 1459 shift-left-arrow [H] : go to parent header / fold selected header
1441 1460 f : fold / unfold item, hiding/revealing its children
1442 1461 F : fold / unfold parent item and all of its ancestors
1443 1462 ctrl-l : scroll the selected line to the top of the screen
1444 1463 m : edit / resume editing the commit message
1445 1464 e : edit the currently selected hunk
1446 1465 a : toggle amend mode, only with commit -i
1447 1466 c : confirm selected changes
1448 1467 r : review/edit and confirm selected changes
1449 1468 q : quit without confirming (no changes will be made)
1450 1469 ? : help (what you're currently reading)""")
1451 1470
1452 1471 helpwin = curses.newwin(self.yscreensize, 0, 0, 0)
1453 1472 helplines = helptext.split("\n")
1454 1473 helplines = helplines + [" "]*(
1455 1474 self.yscreensize - self.numstatuslines - len(helplines) - 1)
1456 1475 try:
1457 1476 for line in helplines:
1458 1477 self.printstring(helpwin, line, pairname="legend")
1459 1478 except curses.error:
1460 1479 pass
1461 1480 helpwin.refresh()
1462 1481 try:
1463 1482 with self.ui.timeblockedsection('crecord'):
1464 1483 helpwin.getkey()
1465 1484 except curses.error:
1466 1485 pass
1467 1486
1468 1487 def commitMessageWindow(self):
1469 1488 "Create a temporary commit message editing window on the screen."
1470 1489
1471 1490 curses.raw()
1472 1491 curses.def_prog_mode()
1473 1492 curses.endwin()
1474 1493 self.commenttext = self.ui.edit(self.commenttext, self.ui.username())
1475 1494 curses.cbreak()
1476 1495 self.stdscr.refresh()
1477 1496 self.stdscr.keypad(1) # allow arrow-keys to continue to function
1478 1497
1479 1498 def confirmationwindow(self, windowtext):
1480 1499 "display an informational window, then wait for and return a keypress."
1481 1500
1482 1501 confirmwin = curses.newwin(self.yscreensize, 0, 0, 0)
1483 1502 try:
1484 1503 lines = windowtext.split("\n")
1485 1504 for line in lines:
1486 1505 self.printstring(confirmwin, line, pairname="selected")
1487 1506 except curses.error:
1488 1507 pass
1489 1508 self.stdscr.refresh()
1490 1509 confirmwin.refresh()
1491 1510 try:
1492 1511 with self.ui.timeblockedsection('crecord'):
1493 1512 response = chr(self.stdscr.getch())
1494 1513 except ValueError:
1495 1514 response = None
1496 1515
1497 1516 return response
1498 1517
1499 1518 def reviewcommit(self):
1500 1519 """ask for 'y' to be pressed to confirm selected. return True if
1501 1520 confirmed."""
1502 1521 confirmtext = _(
1503 1522 """if you answer yes to the following, the your currently chosen patch chunks
1504 1523 will be loaded into an editor. you may modify the patch from the editor, and
1505 1524 save the changes if you wish to change the patch. otherwise, you can just
1506 1525 close the editor without saving to accept the current patch as-is.
1507 1526
1508 1527 note: don't add/remove lines unless you also modify the range information.
1509 1528 failing to follow this rule will result in the commit aborting.
1510 1529
1511 1530 are you sure you want to review/edit and confirm the selected changes [yn]?
1512 1531 """)
1513 1532 with self.ui.timeblockedsection('crecord'):
1514 1533 response = self.confirmationwindow(confirmtext)
1515 1534 if response is None:
1516 1535 response = "n"
1517 1536 if response.lower().startswith("y"):
1518 1537 return True
1519 1538 else:
1520 1539 return False
1521 1540
1522 1541 def toggleamend(self, opts, test):
1523 1542 """Toggle the amend flag.
1524 1543
1525 1544 When the amend flag is set, a commit will modify the most recently
1526 1545 committed changeset, instead of creating a new changeset. Otherwise, a
1527 1546 new changeset will be created (the normal commit behavior).
1528 1547 """
1529 1548
1530 1549 try:
1531 1550 ver = float(util.version()[:3])
1532 1551 except ValueError:
1533 1552 ver = 1
1534 1553 if ver < 2.19:
1535 1554 msg = _("The amend option is unavailable with hg versions < 2.2\n\n"
1536 1555 "Press any key to continue.")
1537 1556 elif opts.get('amend') is None:
1538 1557 opts['amend'] = True
1539 1558 msg = _("Amend option is turned on -- committing the currently "
1540 1559 "selected changes will not create a new changeset, but "
1541 1560 "instead update the most recently committed changeset.\n\n"
1542 1561 "Press any key to continue.")
1543 1562 elif opts.get('amend') is True:
1544 1563 opts['amend'] = None
1545 1564 msg = _("Amend option is turned off -- committing the currently "
1546 1565 "selected changes will create a new changeset.\n\n"
1547 1566 "Press any key to continue.")
1548 1567 if not test:
1549 1568 self.confirmationwindow(msg)
1550 1569
1551 1570 def recenterdisplayedarea(self):
1552 1571 """
1553 1572 once we scrolled with pg up pg down we can be pointing outside of the
1554 1573 display zone. we print the patch with towin=False to compute the
1555 1574 location of the selected item even though it is outside of the displayed
1556 1575 zone and then update the scroll.
1557 1576 """
1558 1577 self.printitem(towin=False)
1559 1578 self.updatescroll()
1560 1579
1561 1580 def toggleedit(self, item=None, test=False):
1562 1581 """
1563 1582 edit the currently selected chunk
1564 1583 """
1565 1584 def updateui(self):
1566 1585 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1567 1586 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1568 1587 self.updatescroll()
1569 1588 self.stdscr.refresh()
1570 1589 self.statuswin.refresh()
1571 1590 self.stdscr.keypad(1)
1572 1591
1573 1592 def editpatchwitheditor(self, chunk):
1574 1593 if chunk is None:
1575 1594 self.ui.write(_('cannot edit patch for whole file'))
1576 1595 self.ui.write("\n")
1577 1596 return None
1578 1597 if chunk.header.binary():
1579 1598 self.ui.write(_('cannot edit patch for binary file'))
1580 1599 self.ui.write("\n")
1581 1600 return None
1582 1601
1583 1602 # write the initial patch
1584 1603 patch = stringio()
1585 1604 patch.write(diffhelptext + hunkhelptext)
1586 1605 chunk.header.write(patch)
1587 1606 chunk.write(patch)
1588 1607
1589 1608 # start the editor and wait for it to complete
1590 1609 try:
1591 1610 patch = self.ui.edit(patch.getvalue(), "", action="diff")
1592 1611 except error.Abort as exc:
1593 1612 self.errorstr = str(exc)
1594 1613 return None
1595 1614
1596 1615 # remove comment lines
1597 1616 patch = [line + '\n' for line in patch.splitlines()
1598 1617 if not line.startswith('#')]
1599 1618 return patchmod.parsepatch(patch)
1600 1619
1601 1620 if item is None:
1602 1621 item = self.currentselecteditem
1603 1622 if isinstance(item, uiheader):
1604 1623 return
1605 1624 if isinstance(item, uihunkline):
1606 1625 item = item.parentitem()
1607 1626 if not isinstance(item, uihunk):
1608 1627 return
1609 1628
1610 1629 # To go back to that hunk or its replacement at the end of the edit
1611 1630 itemindex = item.parentitem().hunks.index(item)
1612 1631
1613 1632 beforeadded, beforeremoved = item.added, item.removed
1614 1633 newpatches = editpatchwitheditor(self, item)
1615 1634 if newpatches is None:
1616 1635 if not test:
1617 1636 updateui(self)
1618 1637 return
1619 1638 header = item.header
1620 1639 editedhunkindex = header.hunks.index(item)
1621 1640 hunksbefore = header.hunks[:editedhunkindex]
1622 1641 hunksafter = header.hunks[editedhunkindex + 1:]
1623 1642 newpatchheader = newpatches[0]
1624 1643 newhunks = [uihunk(h, header) for h in newpatchheader.hunks]
1625 1644 newadded = sum([h.added for h in newhunks])
1626 1645 newremoved = sum([h.removed for h in newhunks])
1627 1646 offset = (newadded - beforeadded) - (newremoved - beforeremoved)
1628 1647
1629 1648 for h in hunksafter:
1630 1649 h.toline += offset
1631 1650 for h in newhunks:
1632 1651 h.folded = False
1633 1652 header.hunks = hunksbefore + newhunks + hunksafter
1634 1653 if self.emptypatch():
1635 1654 header.hunks = hunksbefore + [item] + hunksafter
1636 1655 self.currentselecteditem = header
1637 1656 if len(header.hunks) > itemindex:
1638 1657 self.currentselecteditem = header.hunks[itemindex]
1639 1658
1640 1659 if not test:
1641 1660 updateui(self)
1642 1661
1643 1662 def emptypatch(self):
1644 1663 item = self.headerlist
1645 1664 if not item:
1646 1665 return True
1647 1666 for header in item:
1648 1667 if header.hunks:
1649 1668 return False
1650 1669 return True
1651 1670
1652 1671 def handlekeypressed(self, keypressed, test=False):
1653 1672 """
1654 1673 Perform actions based on pressed keys.
1655 1674
1656 1675 Return true to exit the main loop.
1657 1676 """
1658 1677 if keypressed in ["k", "KEY_UP"]:
1659 1678 self.uparrowevent()
1660 1679 if keypressed in ["K", "KEY_PPAGE"]:
1661 1680 self.uparrowshiftevent()
1662 1681 elif keypressed in ["j", "KEY_DOWN"]:
1663 1682 self.downarrowevent()
1664 1683 elif keypressed in ["J", "KEY_NPAGE"]:
1665 1684 self.downarrowshiftevent()
1666 1685 elif keypressed in ["l", "KEY_RIGHT"]:
1667 1686 self.rightarrowevent()
1668 1687 elif keypressed in ["h", "KEY_LEFT"]:
1669 1688 self.leftarrowevent()
1670 1689 elif keypressed in ["H", "KEY_SLEFT"]:
1671 1690 self.leftarrowshiftevent()
1672 1691 elif keypressed in ["q"]:
1673 1692 raise error.Abort(_('user quit'))
1674 1693 elif keypressed in ['a']:
1675 1694 self.toggleamend(self.opts, test)
1676 1695 elif keypressed in ["c"]:
1677 1696 return True
1678 1697 elif test and keypressed in ['X']:
1679 1698 return True
1680 1699 elif keypressed in ["r"]:
1681 1700 if self.reviewcommit():
1682 1701 self.opts['review'] = True
1683 1702 return True
1684 1703 elif test and keypressed in ['R']:
1685 1704 self.opts['review'] = True
1686 1705 return True
1687 1706 elif keypressed in [' '] or (test and keypressed in ["TOGGLE"]):
1688 1707 self.toggleapply()
1689 if self.ui.configbool('experimental', 'spacemovesdown'):
1690 self.downarrowevent()
1708 elif keypressed in ['\n', 'KEY_ENTER']:
1709 self.toggleapply()
1710 self.nextsametype()
1691 1711 elif keypressed in ['A']:
1692 1712 self.toggleall()
1693 1713 elif keypressed in ['e']:
1694 1714 self.toggleedit(test=test)
1695 1715 elif keypressed in ["f"]:
1696 1716 self.togglefolded()
1697 1717 elif keypressed in ["F"]:
1698 1718 self.togglefolded(foldparent=True)
1699 1719 elif keypressed in ["m"]:
1700 1720 self.commitMessageWindow()
1701 1721 elif keypressed in ["?"]:
1702 1722 self.helpwindow()
1703 1723 self.stdscr.clear()
1704 1724 self.stdscr.refresh()
1705 1725 elif curses.unctrl(keypressed) in ["^L"]:
1706 1726 # scroll the current line to the top of the screen
1707 1727 self.scrolllines(self.selecteditemstartline)
1708 1728
1709 1729 def main(self, stdscr):
1710 1730 """
1711 1731 method to be wrapped by curses.wrapper() for selecting chunks.
1712 1732 """
1713 1733
1714 1734 origsigwinch = sentinel = object()
1715 1735 if util.safehasattr(signal, 'SIGWINCH'):
1716 1736 origsigwinch = signal.signal(signal.SIGWINCH,
1717 1737 self.sigwinchhandler)
1718 1738 try:
1719 1739 return self._main(stdscr)
1720 1740 finally:
1721 1741 if origsigwinch is not sentinel:
1722 1742 signal.signal(signal.SIGWINCH, origsigwinch)
1723 1743
1724 1744 def _main(self, stdscr):
1725 1745 self.stdscr = stdscr
1726 1746 # error during initialization, cannot be printed in the curses
1727 1747 # interface, it should be printed by the calling code
1728 1748 self.initexc = None
1729 1749 self.yscreensize, self.xscreensize = self.stdscr.getmaxyx()
1730 1750
1731 1751 curses.start_color()
1732 1752 try:
1733 1753 curses.use_default_colors()
1734 1754 except curses.error:
1735 1755 self.usecolor = False
1736 1756
1737 1757 # available colors: black, blue, cyan, green, magenta, white, yellow
1738 1758 # init_pair(color_id, foreground_color, background_color)
1739 1759 self.initcolorpair(None, None, name="normal")
1740 1760 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_MAGENTA,
1741 1761 name="selected")
1742 1762 self.initcolorpair(curses.COLOR_RED, None, name="deletion")
1743 1763 self.initcolorpair(curses.COLOR_GREEN, None, name="addition")
1744 1764 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_BLUE, name="legend")
1745 1765 # newwin([height, width,] begin_y, begin_x)
1746 1766 self.statuswin = curses.newwin(self.numstatuslines, 0, 0, 0)
1747 1767 self.statuswin.keypad(1) # interpret arrow-key, etc. esc sequences
1748 1768
1749 1769 # figure out how much space to allocate for the chunk-pad which is
1750 1770 # used for displaying the patch
1751 1771
1752 1772 # stupid hack to prevent getnumlinesdisplayed from failing
1753 1773 self.chunkpad = curses.newpad(1, self.xscreensize)
1754 1774
1755 1775 # add 1 so to account for last line text reaching end of line
1756 1776 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1757 1777
1758 1778 try:
1759 1779 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1760 1780 except curses.error:
1761 1781 self.initexc = fallbackerror(
1762 1782 _('this diff is too large to be displayed'))
1763 1783 return
1764 1784 # initialize selecteditemendline (initial start-line is 0)
1765 1785 self.selecteditemendline = self.getnumlinesdisplayed(
1766 1786 self.currentselecteditem, recursechildren=False)
1767 1787
1768 1788 while True:
1769 1789 self.updatescreen()
1770 1790 try:
1771 1791 with self.ui.timeblockedsection('crecord'):
1772 1792 keypressed = self.statuswin.getkey()
1773 1793 if self.errorstr is not None:
1774 1794 self.errorstr = None
1775 1795 continue
1776 1796 except curses.error:
1777 1797 keypressed = "foobar"
1778 1798 if self.handlekeypressed(keypressed):
1779 1799 break
1780 1800
1781 1801 if self.commenttext != "":
1782 1802 whitespaceremoved = re.sub("(?m)^\s.*(\n|$)", "", self.commenttext)
1783 1803 if whitespaceremoved != "":
1784 1804 self.opts['message'] = self.commenttext
@@ -1,444 +1,426 b''
1 1 #require tic
2 2
3 3 Set up a repo
4 4
5 5 $ cp $HGRCPATH $HGRCPATH.pretest
6 6 $ cat <<EOF >> $HGRCPATH
7 7 > [ui]
8 8 > interactive = true
9 9 > interface = curses
10 10 > [experimental]
11 11 > crecordtest = testModeCommands
12 12 > EOF
13 13
14 14 Record with noeol at eof (issue5268)
15 15 $ hg init noeol
16 16 $ cd noeol
17 17 $ printf '0' > a
18 18 $ printf '0\n' > b
19 19 $ hg ci -Aqm initial
20 20 $ printf '1\n0' > a
21 21 $ printf '1\n0\n' > b
22 22 $ cat <<EOF >testModeCommands
23 23 > c
24 24 > EOF
25 25 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "add hunks" -d "0 0"
26 26 $ cd ..
27 27
28 28 Normal repo
29 29 $ hg init a
30 30 $ cd a
31 31
32 32 Committing some changes but stopping on the way
33 33
34 34 $ echo "a" > a
35 35 $ hg add a
36 36 $ cat <<EOF >testModeCommands
37 37 > TOGGLE
38 38 > X
39 39 > EOF
40 40 $ hg commit -i -m "a" -d "0 0"
41 41 no changes to record
42 42 [1]
43 43 $ hg tip
44 44 changeset: -1:000000000000
45 45 tag: tip
46 46 user:
47 47 date: Thu Jan 01 00:00:00 1970 +0000
48 48
49 49
50 50 Committing some changes
51 51
52 52 $ cat <<EOF >testModeCommands
53 53 > X
54 54 > EOF
55 55 $ hg commit -i -m "a" -d "0 0"
56 56 $ hg tip
57 57 changeset: 0:cb9a9f314b8b
58 58 tag: tip
59 59 user: test
60 60 date: Thu Jan 01 00:00:00 1970 +0000
61 61 summary: a
62 62
63 63 Check that commit -i works with no changes
64 64 $ hg commit -i
65 65 no changes to record
66 66 [1]
67 67
68 68 Committing only one file
69 69
70 70 $ echo "a" >> a
71 71 >>> open('b', 'wb').write(b"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") and None
72 72 $ hg add b
73 73 $ cat <<EOF >testModeCommands
74 74 > TOGGLE
75 75 > KEY_DOWN
76 76 > X
77 77 > EOF
78 78 $ hg commit -i -m "one file" -d "0 0"
79 79 $ hg tip
80 80 changeset: 1:fb2705a663ea
81 81 tag: tip
82 82 user: test
83 83 date: Thu Jan 01 00:00:00 1970 +0000
84 84 summary: one file
85 85
86 86 $ hg cat -r tip a
87 87 a
88 88 $ cat a
89 89 a
90 90 a
91 91
92 92 Committing only one hunk while aborting edition of hunk
93 93
94 94 - Untoggle all the hunks, go down to the second file
95 95 - unfold it
96 96 - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike)
97 97 - toggle the second hunk
98 98 - toggle on and off the amend mode (to check that it toggles off)
99 99 - edit the hunk and quit the editor immediately with non-zero status
100 100 - commit
101 101
102 102 $ printf "printf 'editor ran\n'; exit 1" > editor.sh
103 103 $ echo "x" > c
104 104 $ cat b >> c
105 105 $ echo "y" >> c
106 106 $ mv c b
107 107 $ cat <<EOF >testModeCommands
108 108 > A
109 109 > KEY_DOWN
110 110 > f
111 111 > KEY_DOWN
112 112 > KEY_DOWN
113 113 > KEY_DOWN
114 114 > KEY_DOWN
115 115 > TOGGLE
116 116 > a
117 117 > a
118 118 > e
119 119 > X
120 120 > EOF
121 121 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "one hunk" -d "0 0"
122 122 editor ran
123 123 $ rm editor.sh
124 124 $ hg tip
125 125 changeset: 2:7d10dfe755a8
126 126 tag: tip
127 127 user: test
128 128 date: Thu Jan 01 00:00:00 1970 +0000
129 129 summary: one hunk
130 130
131 131 $ hg cat -r tip b
132 132 1
133 133 2
134 134 3
135 135 4
136 136 5
137 137 6
138 138 7
139 139 8
140 140 9
141 141 10
142 142 y
143 143 $ cat b
144 144 x
145 145 1
146 146 2
147 147 3
148 148 4
149 149 5
150 150 6
151 151 7
152 152 8
153 153 9
154 154 10
155 155 y
156 156 $ hg commit -m "other hunks"
157 157 $ hg tip
158 158 changeset: 3:a6735021574d
159 159 tag: tip
160 160 user: test
161 161 date: Thu Jan 01 00:00:00 1970 +0000
162 162 summary: other hunks
163 163
164 164 $ hg cat -r tip b
165 165 x
166 166 1
167 167 2
168 168 3
169 169 4
170 170 5
171 171 6
172 172 7
173 173 8
174 174 9
175 175 10
176 176 y
177 177
178 178 Newly added files can be selected with the curses interface
179 179
180 180 $ hg update -C .
181 181 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
182 182 $ echo "hello" > x
183 183 $ hg add x
184 184 $ cat <<EOF >testModeCommands
185 185 > TOGGLE
186 186 > TOGGLE
187 187 > X
188 188 > EOF
189 189 $ hg st
190 190 A x
191 191 ? testModeCommands
192 192 $ hg commit -i -m "newly added file" -d "0 0"
193 193 $ hg st
194 194 ? testModeCommands
195 195
196 196 Amend option works
197 197 $ echo "hello world" > x
198 198 $ hg diff -c .
199 199 diff -r a6735021574d -r 2b0e9be4d336 x
200 200 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
201 201 +++ b/x Thu Jan 01 00:00:00 1970 +0000
202 202 @@ -0,0 +1,1 @@
203 203 +hello
204 204 $ cat <<EOF >testModeCommands
205 205 > a
206 206 > X
207 207 > EOF
208 208 $ hg commit -i -m "newly added file" -d "0 0"
209 209 saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-3cf0bc8c-amend.hg
210 210 $ hg diff -c .
211 211 diff -r a6735021574d -r c1d239d165ae x
212 212 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
213 213 +++ b/x Thu Jan 01 00:00:00 1970 +0000
214 214 @@ -0,0 +1,1 @@
215 215 +hello world
216 216
217 217 Make file empty
218 218 $ printf "" > x
219 219 $ cat <<EOF >testModeCommands
220 220 > X
221 221 > EOF
222 222 $ hg ci -i -m emptify -d "0 0"
223 223 $ hg update -C '.^' -q
224 224
225 225 Editing a hunk puts you back on that hunk when done editing (issue5041)
226 226 To do that, we change two lines in a file, pretend to edit the second line,
227 227 exit, toggle the line selected at the end of the edit and commit.
228 228 The first line should be recorded if we were put on the second line at the end
229 229 of the edit.
230 230
231 231 $ hg update -C .
232 232 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
233 233 $ echo "foo" > x
234 234 $ echo "hello world" >> x
235 235 $ echo "bar" >> x
236 236 $ cat <<EOF >testModeCommands
237 237 > f
238 238 > KEY_DOWN
239 239 > KEY_DOWN
240 240 > KEY_DOWN
241 241 > KEY_DOWN
242 242 > e
243 243 > TOGGLE
244 244 > X
245 245 > EOF
246 246 $ printf "printf 'editor ran\n'; exit 0" > editor.sh
247 247 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "edit hunk" -d "0 0" -q
248 248 editor ran
249 249 $ hg cat -r . x
250 250 foo
251 251 hello world
252 252
253 253 Testing the review option. The entire final filtered patch should show
254 254 up in the editor and be editable. We will unselect the second file and
255 255 the first hunk of the third file. During review, we will decide that
256 256 "lower" sounds better than "bottom", and the final commit should
257 257 reflect this edition.
258 258
259 259 $ hg update -C .
260 260 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
261 261 $ echo "top" > c
262 262 $ cat x >> c
263 263 $ echo "bottom" >> c
264 264 $ mv c x
265 265 $ echo "third a" >> a
266 266 $ echo "we will unselect this" >> b
267 267
268 268 $ cat > editor.sh <<EOF
269 269 > cat "\$1"
270 270 > cat "\$1" | sed s/bottom/lower/ > tmp
271 271 > mv tmp "\$1"
272 272 > EOF
273 273 $ cat > testModeCommands <<EOF
274 274 > KEY_DOWN
275 275 > TOGGLE
276 276 > KEY_DOWN
277 277 > f
278 278 > KEY_DOWN
279 279 > TOGGLE
280 280 > R
281 281 > EOF
282 282
283 283 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "review hunks" -d "0 0"
284 284 # To remove '-' lines, make them ' ' lines (context).
285 285 # To remove '+' lines, delete them.
286 286 # Lines starting with # will be removed from the patch.
287 287 #
288 288 # If the patch applies cleanly, the edited patch will immediately
289 289 # be finalised. If it does not apply cleanly, rejects files will be
290 290 # generated. You can use those when you try again.
291 291 diff --git a/a b/a
292 292 --- a/a
293 293 +++ b/a
294 294 @@ -1,2 +1,3 @@
295 295 a
296 296 a
297 297 +third a
298 298 diff --git a/x b/x
299 299 --- a/x
300 300 +++ b/x
301 301 @@ -1,2 +1,3 @@
302 302 foo
303 303 hello world
304 304 +bottom
305 305
306 306 $ hg cat -r . a
307 307 a
308 308 a
309 309 third a
310 310
311 311 $ hg cat -r . b
312 312 x
313 313 1
314 314 2
315 315 3
316 316 4
317 317 5
318 318 6
319 319 7
320 320 8
321 321 9
322 322 10
323 323 y
324 324
325 325 $ hg cat -r . x
326 326 foo
327 327 hello world
328 328 lower
329 329
330 Check spacemovesdown
331
332 $ cat <<EOF >> $HGRCPATH
333 > [experimental]
334 > spacemovesdown = true
335 > EOF
336 $ cat <<EOF >testModeCommands
337 > TOGGLE
338 > TOGGLE
339 > X
340 > EOF
341 $ hg status -q
342 M b
343 M x
344 $ hg commit -i -m "nothing to commit?" -d "0 0"
345 no changes to record
346 [1]
347
348 330 Check ui.interface logic for the chunkselector
349 331
350 332 The default interface is text
351 333 $ cp $HGRCPATH.pretest $HGRCPATH
352 334 $ chunkselectorinterface() {
353 335 > "$PYTHON" <<EOF
354 336 > from mercurial import hg, ui;\
355 337 > repo = hg.repository(ui.ui.load(), ".");\
356 338 > print(repo.ui.interface("chunkselector"))
357 339 > EOF
358 340 > }
359 341 $ chunkselectorinterface
360 342 text
361 343
362 344 If only the default is set, we'll use that for the feature, too
363 345 $ cp $HGRCPATH.pretest $HGRCPATH
364 346 $ cat <<EOF >> $HGRCPATH
365 347 > [ui]
366 348 > interface = curses
367 349 > EOF
368 350 $ chunkselectorinterface
369 351 curses
370 352
371 353 If TERM=dumb, we use text, even if the config says curses
372 354 $ chunkselectorinterface
373 355 curses
374 356 $ TERM=dumb chunkselectorinterface
375 357 text
376 358 (Something is keeping TERM=dumb in the environment unless I do this, it's not
377 359 scoped to just that previous command like in many shells)
378 360 $ TERM=xterm chunkselectorinterface
379 361 curses
380 362
381 363 It is possible to override the default interface with a feature specific
382 364 interface
383 365 $ cp $HGRCPATH.pretest $HGRCPATH
384 366 $ cat <<EOF >> $HGRCPATH
385 367 > [ui]
386 368 > interface = text
387 369 > interface.chunkselector = curses
388 370 > EOF
389 371
390 372 $ chunkselectorinterface
391 373 curses
392 374
393 375 $ cp $HGRCPATH.pretest $HGRCPATH
394 376 $ cat <<EOF >> $HGRCPATH
395 377 > [ui]
396 378 > interface = curses
397 379 > interface.chunkselector = text
398 380 > EOF
399 381
400 382 $ chunkselectorinterface
401 383 text
402 384
403 385 If a bad interface name is given, we use the default value (with a nice
404 386 error message to suggest that the configuration needs to be fixed)
405 387
406 388 $ cp $HGRCPATH.pretest $HGRCPATH
407 389 $ cat <<EOF >> $HGRCPATH
408 390 > [ui]
409 391 > interface = blah
410 392 > EOF
411 393 $ chunkselectorinterface
412 394 invalid value for ui.interface: blah (using text)
413 395 text
414 396
415 397 $ cp $HGRCPATH.pretest $HGRCPATH
416 398 $ cat <<EOF >> $HGRCPATH
417 399 > [ui]
418 400 > interface = curses
419 401 > interface.chunkselector = blah
420 402 > EOF
421 403 $ chunkselectorinterface
422 404 invalid value for ui.interface.chunkselector: blah (using curses)
423 405 curses
424 406
425 407 $ cp $HGRCPATH.pretest $HGRCPATH
426 408 $ cat <<EOF >> $HGRCPATH
427 409 > [ui]
428 410 > interface = blah
429 411 > interface.chunkselector = curses
430 412 > EOF
431 413 $ chunkselectorinterface
432 414 invalid value for ui.interface: blah
433 415 curses
434 416
435 417 $ cp $HGRCPATH.pretest $HGRCPATH
436 418 $ cat <<EOF >> $HGRCPATH
437 419 > [ui]
438 420 > interface = blah
439 421 > interface.chunkselector = blah
440 422 > EOF
441 423 $ chunkselectorinterface
442 424 invalid value for ui.interface: blah
443 425 invalid value for ui.interface.chunkselector: blah (using text)
444 426 text
General Comments 0
You need to be logged in to leave comments. Login now