##// END OF EJS Templates
copytrace: add a a new config to limit the number of drafts in heuristics...
Pulkit Goyal -
r34312:1826d695 default
parent child Browse files
Show More
@@ -1,665 +1,668 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
12 12 from . import (
13 13 encoding,
14 14 error,
15 15 )
16 16
17 17 def loadconfigtable(ui, extname, configtable):
18 18 """update config item known to the ui with the extension ones"""
19 19 for section, items in configtable.items():
20 20 knownitems = ui._knownconfig.setdefault(section, {})
21 21 knownkeys = set(knownitems)
22 22 newkeys = set(items)
23 23 for key in sorted(knownkeys & newkeys):
24 24 msg = "extension '%s' overwrite config item '%s.%s'"
25 25 msg %= (extname, section, key)
26 26 ui.develwarn(msg, config='warn-config')
27 27
28 28 knownitems.update(items)
29 29
30 30 class configitem(object):
31 31 """represent a known config item
32 32
33 33 :section: the official config section where to find this item,
34 34 :name: the official name within the section,
35 35 :default: default value for this item,
36 36 :alias: optional list of tuples as alternatives.
37 37 """
38 38
39 39 def __init__(self, section, name, default=None, alias=()):
40 40 self.section = section
41 41 self.name = name
42 42 self.default = default
43 43 self.alias = list(alias)
44 44
45 45 coreitems = {}
46 46
47 47 def _register(configtable, *args, **kwargs):
48 48 item = configitem(*args, **kwargs)
49 49 section = configtable.setdefault(item.section, {})
50 50 if item.name in section:
51 51 msg = "duplicated config item registration for '%s.%s'"
52 52 raise error.ProgrammingError(msg % (item.section, item.name))
53 53 section[item.name] = item
54 54
55 55 # special value for case where the default is derived from other values
56 56 dynamicdefault = object()
57 57
58 58 # Registering actual config items
59 59
60 60 def getitemregister(configtable):
61 61 return functools.partial(_register, configtable)
62 62
63 63 coreconfigitem = getitemregister(coreitems)
64 64
65 65 coreconfigitem('auth', 'cookiefile',
66 66 default=None,
67 67 )
68 68 # bookmarks.pushing: internal hack for discovery
69 69 coreconfigitem('bookmarks', 'pushing',
70 70 default=list,
71 71 )
72 72 # bundle.mainreporoot: internal hack for bundlerepo
73 73 coreconfigitem('bundle', 'mainreporoot',
74 74 default='',
75 75 )
76 76 # bundle.reorder: experimental config
77 77 coreconfigitem('bundle', 'reorder',
78 78 default='auto',
79 79 )
80 80 coreconfigitem('censor', 'policy',
81 81 default='abort',
82 82 )
83 83 coreconfigitem('chgserver', 'idletimeout',
84 84 default=3600,
85 85 )
86 86 coreconfigitem('chgserver', 'skiphash',
87 87 default=False,
88 88 )
89 89 coreconfigitem('cmdserver', 'log',
90 90 default=None,
91 91 )
92 92 coreconfigitem('color', 'mode',
93 93 default='auto',
94 94 )
95 95 coreconfigitem('color', 'pagermode',
96 96 default=dynamicdefault,
97 97 )
98 98 coreconfigitem('commands', 'status.relative',
99 99 default=False,
100 100 )
101 101 coreconfigitem('commands', 'status.skipstates',
102 102 default=[],
103 103 )
104 104 coreconfigitem('commands', 'status.verbose',
105 105 default=False,
106 106 )
107 107 coreconfigitem('commands', 'update.requiredest',
108 108 default=False,
109 109 )
110 110 coreconfigitem('devel', 'all-warnings',
111 111 default=False,
112 112 )
113 113 coreconfigitem('devel', 'bundle2.debug',
114 114 default=False,
115 115 )
116 116 coreconfigitem('devel', 'check-locks',
117 117 default=False,
118 118 )
119 119 coreconfigitem('devel', 'check-relroot',
120 120 default=False,
121 121 )
122 122 coreconfigitem('devel', 'default-date',
123 123 default=None,
124 124 )
125 125 coreconfigitem('devel', 'deprec-warn',
126 126 default=False,
127 127 )
128 128 coreconfigitem('devel', 'disableloaddefaultcerts',
129 129 default=False,
130 130 )
131 131 coreconfigitem('devel', 'legacy.exchange',
132 132 default=list,
133 133 )
134 134 coreconfigitem('devel', 'servercafile',
135 135 default='',
136 136 )
137 137 coreconfigitem('devel', 'serverexactprotocol',
138 138 default='',
139 139 )
140 140 coreconfigitem('devel', 'serverrequirecert',
141 141 default=False,
142 142 )
143 143 coreconfigitem('devel', 'strip-obsmarkers',
144 144 default=True,
145 145 )
146 146 coreconfigitem('email', 'charsets',
147 147 default=list,
148 148 )
149 149 coreconfigitem('email', 'method',
150 150 default='smtp',
151 151 )
152 152 coreconfigitem('experimental', 'bundle-phases',
153 153 default=False,
154 154 )
155 155 coreconfigitem('experimental', 'bundle2-advertise',
156 156 default=True,
157 157 )
158 158 coreconfigitem('experimental', 'bundle2-output-capture',
159 159 default=False,
160 160 )
161 161 coreconfigitem('experimental', 'bundle2.pushback',
162 162 default=False,
163 163 )
164 164 coreconfigitem('experimental', 'bundle2lazylocking',
165 165 default=False,
166 166 )
167 167 coreconfigitem('experimental', 'bundlecomplevel',
168 168 default=None,
169 169 )
170 170 coreconfigitem('experimental', 'changegroup3',
171 171 default=False,
172 172 )
173 173 coreconfigitem('experimental', 'clientcompressionengines',
174 174 default=list,
175 175 )
176 176 coreconfigitem('experimental', 'copytrace',
177 177 default='on',
178 178 )
179 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
180 default=100,
181 )
179 182 coreconfigitem('experimental', 'crecordtest',
180 183 default=None,
181 184 )
182 185 coreconfigitem('experimental', 'editortmpinhg',
183 186 default=False,
184 187 )
185 188 coreconfigitem('experimental', 'stabilization',
186 189 default=list,
187 190 alias=[('experimental', 'evolution')],
188 191 )
189 192 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
190 193 default=False,
191 194 alias=[('experimental', 'evolution.bundle-obsmarker')],
192 195 )
193 196 coreconfigitem('experimental', 'stabilization.track-operation',
194 197 default=True,
195 198 alias=[('experimental', 'evolution.track-operation')]
196 199 )
197 200 coreconfigitem('experimental', 'exportableenviron',
198 201 default=list,
199 202 )
200 203 coreconfigitem('experimental', 'extendedheader.index',
201 204 default=None,
202 205 )
203 206 coreconfigitem('experimental', 'extendedheader.similarity',
204 207 default=False,
205 208 )
206 209 coreconfigitem('experimental', 'format.compression',
207 210 default='zlib',
208 211 )
209 212 coreconfigitem('experimental', 'graphshorten',
210 213 default=False,
211 214 )
212 215 coreconfigitem('experimental', 'hook-track-tags',
213 216 default=False,
214 217 )
215 218 coreconfigitem('experimental', 'httppostargs',
216 219 default=False,
217 220 )
218 221 coreconfigitem('experimental', 'manifestv2',
219 222 default=False,
220 223 )
221 224 coreconfigitem('experimental', 'mergedriver',
222 225 default=None,
223 226 )
224 227 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
225 228 default=False,
226 229 )
227 230 coreconfigitem('experimental', 'rebase.multidest',
228 231 default=False,
229 232 )
230 233 coreconfigitem('experimental', 'revertalternateinteractivemode',
231 234 default=True,
232 235 )
233 236 coreconfigitem('experimental', 'revlogv2',
234 237 default=None,
235 238 )
236 239 coreconfigitem('experimental', 'spacemovesdown',
237 240 default=False,
238 241 )
239 242 coreconfigitem('experimental', 'treemanifest',
240 243 default=False,
241 244 )
242 245 coreconfigitem('experimental', 'updatecheck',
243 246 default=None,
244 247 )
245 248 coreconfigitem('format', 'aggressivemergedeltas',
246 249 default=False,
247 250 )
248 251 coreconfigitem('format', 'chunkcachesize',
249 252 default=None,
250 253 )
251 254 coreconfigitem('format', 'dotencode',
252 255 default=True,
253 256 )
254 257 coreconfigitem('format', 'generaldelta',
255 258 default=False,
256 259 )
257 260 coreconfigitem('format', 'manifestcachesize',
258 261 default=None,
259 262 )
260 263 coreconfigitem('format', 'maxchainlen',
261 264 default=None,
262 265 )
263 266 coreconfigitem('format', 'obsstore-version',
264 267 default=None,
265 268 )
266 269 coreconfigitem('format', 'usefncache',
267 270 default=True,
268 271 )
269 272 coreconfigitem('format', 'usegeneraldelta',
270 273 default=True,
271 274 )
272 275 coreconfigitem('format', 'usestore',
273 276 default=True,
274 277 )
275 278 coreconfigitem('hostsecurity', 'ciphers',
276 279 default=None,
277 280 )
278 281 coreconfigitem('hostsecurity', 'disabletls10warning',
279 282 default=False,
280 283 )
281 284 coreconfigitem('http_proxy', 'always',
282 285 default=False,
283 286 )
284 287 coreconfigitem('http_proxy', 'host',
285 288 default=None,
286 289 )
287 290 coreconfigitem('http_proxy', 'no',
288 291 default=list,
289 292 )
290 293 coreconfigitem('http_proxy', 'passwd',
291 294 default=None,
292 295 )
293 296 coreconfigitem('http_proxy', 'user',
294 297 default=None,
295 298 )
296 299 coreconfigitem('merge', 'followcopies',
297 300 default=True,
298 301 )
299 302 coreconfigitem('pager', 'ignore',
300 303 default=list,
301 304 )
302 305 coreconfigitem('patch', 'eol',
303 306 default='strict',
304 307 )
305 308 coreconfigitem('patch', 'fuzz',
306 309 default=2,
307 310 )
308 311 coreconfigitem('paths', 'default',
309 312 default=None,
310 313 )
311 314 coreconfigitem('paths', 'default-push',
312 315 default=None,
313 316 )
314 317 coreconfigitem('phases', 'checksubrepos',
315 318 default='follow',
316 319 )
317 320 coreconfigitem('phases', 'publish',
318 321 default=True,
319 322 )
320 323 coreconfigitem('profiling', 'enabled',
321 324 default=False,
322 325 )
323 326 coreconfigitem('profiling', 'format',
324 327 default='text',
325 328 )
326 329 coreconfigitem('profiling', 'freq',
327 330 default=1000,
328 331 )
329 332 coreconfigitem('profiling', 'limit',
330 333 default=30,
331 334 )
332 335 coreconfigitem('profiling', 'nested',
333 336 default=0,
334 337 )
335 338 coreconfigitem('profiling', 'sort',
336 339 default='inlinetime',
337 340 )
338 341 coreconfigitem('profiling', 'statformat',
339 342 default='hotpath',
340 343 )
341 344 coreconfigitem('progress', 'assume-tty',
342 345 default=False,
343 346 )
344 347 coreconfigitem('progress', 'changedelay',
345 348 default=1,
346 349 )
347 350 coreconfigitem('progress', 'clear-complete',
348 351 default=True,
349 352 )
350 353 coreconfigitem('progress', 'debug',
351 354 default=False,
352 355 )
353 356 coreconfigitem('progress', 'delay',
354 357 default=3,
355 358 )
356 359 coreconfigitem('progress', 'disable',
357 360 default=False,
358 361 )
359 362 coreconfigitem('progress', 'estimate',
360 363 default=2,
361 364 )
362 365 coreconfigitem('progress', 'refresh',
363 366 default=0.1,
364 367 )
365 368 coreconfigitem('progress', 'width',
366 369 default=dynamicdefault,
367 370 )
368 371 coreconfigitem('push', 'pushvars.server',
369 372 default=False,
370 373 )
371 374 coreconfigitem('server', 'bundle1',
372 375 default=True,
373 376 )
374 377 coreconfigitem('server', 'bundle1gd',
375 378 default=None,
376 379 )
377 380 coreconfigitem('server', 'compressionengines',
378 381 default=list,
379 382 )
380 383 coreconfigitem('server', 'concurrent-push-mode',
381 384 default='strict',
382 385 )
383 386 coreconfigitem('server', 'disablefullbundle',
384 387 default=False,
385 388 )
386 389 coreconfigitem('server', 'maxhttpheaderlen',
387 390 default=1024,
388 391 )
389 392 coreconfigitem('server', 'preferuncompressed',
390 393 default=False,
391 394 )
392 395 coreconfigitem('server', 'uncompressed',
393 396 default=True,
394 397 )
395 398 coreconfigitem('server', 'uncompressedallowsecret',
396 399 default=False,
397 400 )
398 401 coreconfigitem('server', 'validate',
399 402 default=False,
400 403 )
401 404 coreconfigitem('server', 'zliblevel',
402 405 default=-1,
403 406 )
404 407 coreconfigitem('smtp', 'host',
405 408 default=None,
406 409 )
407 410 coreconfigitem('smtp', 'local_hostname',
408 411 default=None,
409 412 )
410 413 coreconfigitem('smtp', 'password',
411 414 default=None,
412 415 )
413 416 coreconfigitem('smtp', 'tls',
414 417 default='none',
415 418 )
416 419 coreconfigitem('smtp', 'username',
417 420 default=None,
418 421 )
419 422 coreconfigitem('sparse', 'missingwarning',
420 423 default=True,
421 424 )
422 425 coreconfigitem('trusted', 'groups',
423 426 default=list,
424 427 )
425 428 coreconfigitem('trusted', 'users',
426 429 default=list,
427 430 )
428 431 coreconfigitem('ui', '_usedassubrepo',
429 432 default=False,
430 433 )
431 434 coreconfigitem('ui', 'allowemptycommit',
432 435 default=False,
433 436 )
434 437 coreconfigitem('ui', 'archivemeta',
435 438 default=True,
436 439 )
437 440 coreconfigitem('ui', 'askusername',
438 441 default=False,
439 442 )
440 443 coreconfigitem('ui', 'clonebundlefallback',
441 444 default=False,
442 445 )
443 446 coreconfigitem('ui', 'clonebundleprefers',
444 447 default=list,
445 448 )
446 449 coreconfigitem('ui', 'clonebundles',
447 450 default=True,
448 451 )
449 452 coreconfigitem('ui', 'color',
450 453 default='auto',
451 454 )
452 455 coreconfigitem('ui', 'commitsubrepos',
453 456 default=False,
454 457 )
455 458 coreconfigitem('ui', 'debug',
456 459 default=False,
457 460 )
458 461 coreconfigitem('ui', 'debugger',
459 462 default=None,
460 463 )
461 464 coreconfigitem('ui', 'fallbackencoding',
462 465 default=None,
463 466 )
464 467 coreconfigitem('ui', 'forcecwd',
465 468 default=None,
466 469 )
467 470 coreconfigitem('ui', 'forcemerge',
468 471 default=None,
469 472 )
470 473 coreconfigitem('ui', 'formatdebug',
471 474 default=False,
472 475 )
473 476 coreconfigitem('ui', 'formatjson',
474 477 default=False,
475 478 )
476 479 coreconfigitem('ui', 'formatted',
477 480 default=None,
478 481 )
479 482 coreconfigitem('ui', 'graphnodetemplate',
480 483 default=None,
481 484 )
482 485 coreconfigitem('ui', 'http2debuglevel',
483 486 default=None,
484 487 )
485 488 coreconfigitem('ui', 'interactive',
486 489 default=None,
487 490 )
488 491 coreconfigitem('ui', 'interface',
489 492 default=None,
490 493 )
491 494 coreconfigitem('ui', 'logblockedtimes',
492 495 default=False,
493 496 )
494 497 coreconfigitem('ui', 'logtemplate',
495 498 default=None,
496 499 )
497 500 coreconfigitem('ui', 'merge',
498 501 default=None,
499 502 )
500 503 coreconfigitem('ui', 'mergemarkers',
501 504 default='basic',
502 505 )
503 506 coreconfigitem('ui', 'mergemarkertemplate',
504 507 default=('{node|short} '
505 508 '{ifeq(tags, "tip", "", '
506 509 'ifeq(tags, "", "", "{tags} "))}'
507 510 '{if(bookmarks, "{bookmarks} ")}'
508 511 '{ifeq(branch, "default", "", "{branch} ")}'
509 512 '- {author|user}: {desc|firstline}')
510 513 )
511 514 coreconfigitem('ui', 'nontty',
512 515 default=False,
513 516 )
514 517 coreconfigitem('ui', 'origbackuppath',
515 518 default=None,
516 519 )
517 520 coreconfigitem('ui', 'paginate',
518 521 default=True,
519 522 )
520 523 coreconfigitem('ui', 'patch',
521 524 default=None,
522 525 )
523 526 coreconfigitem('ui', 'portablefilenames',
524 527 default='warn',
525 528 )
526 529 coreconfigitem('ui', 'promptecho',
527 530 default=False,
528 531 )
529 532 coreconfigitem('ui', 'quiet',
530 533 default=False,
531 534 )
532 535 coreconfigitem('ui', 'quietbookmarkmove',
533 536 default=False,
534 537 )
535 538 coreconfigitem('ui', 'remotecmd',
536 539 default='hg',
537 540 )
538 541 coreconfigitem('ui', 'report_untrusted',
539 542 default=True,
540 543 )
541 544 coreconfigitem('ui', 'rollback',
542 545 default=True,
543 546 )
544 547 coreconfigitem('ui', 'slash',
545 548 default=False,
546 549 )
547 550 coreconfigitem('ui', 'ssh',
548 551 default='ssh',
549 552 )
550 553 coreconfigitem('ui', 'statuscopies',
551 554 default=False,
552 555 )
553 556 coreconfigitem('ui', 'strict',
554 557 default=False,
555 558 )
556 559 coreconfigitem('ui', 'style',
557 560 default='',
558 561 )
559 562 coreconfigitem('ui', 'supportcontact',
560 563 default=None,
561 564 )
562 565 coreconfigitem('ui', 'textwidth',
563 566 default=78,
564 567 )
565 568 coreconfigitem('ui', 'timeout',
566 569 default='600',
567 570 )
568 571 coreconfigitem('ui', 'traceback',
569 572 default=False,
570 573 )
571 574 coreconfigitem('ui', 'tweakdefaults',
572 575 default=False,
573 576 )
574 577 coreconfigitem('ui', 'usehttp2',
575 578 default=False,
576 579 )
577 580 coreconfigitem('ui', 'username',
578 581 alias=[('ui', 'user')]
579 582 )
580 583 coreconfigitem('ui', 'verbose',
581 584 default=False,
582 585 )
583 586 coreconfigitem('verify', 'skipflags',
584 587 default=None,
585 588 )
586 589 coreconfigitem('web', 'accesslog',
587 590 default='-',
588 591 )
589 592 coreconfigitem('web', 'address',
590 593 default='',
591 594 )
592 595 coreconfigitem('web', 'allow_archive',
593 596 default=list,
594 597 )
595 598 coreconfigitem('web', 'allow_read',
596 599 default=list,
597 600 )
598 601 coreconfigitem('web', 'baseurl',
599 602 default=None,
600 603 )
601 604 coreconfigitem('web', 'cacerts',
602 605 default=None,
603 606 )
604 607 coreconfigitem('web', 'certificate',
605 608 default=None,
606 609 )
607 610 coreconfigitem('web', 'collapse',
608 611 default=False,
609 612 )
610 613 coreconfigitem('web', 'csp',
611 614 default=None,
612 615 )
613 616 coreconfigitem('web', 'deny_read',
614 617 default=list,
615 618 )
616 619 coreconfigitem('web', 'descend',
617 620 default=True,
618 621 )
619 622 coreconfigitem('web', 'description',
620 623 default="",
621 624 )
622 625 coreconfigitem('web', 'encoding',
623 626 default=lambda: encoding.encoding,
624 627 )
625 628 coreconfigitem('web', 'errorlog',
626 629 default='-',
627 630 )
628 631 coreconfigitem('web', 'ipv6',
629 632 default=False,
630 633 )
631 634 coreconfigitem('web', 'port',
632 635 default=8000,
633 636 )
634 637 coreconfigitem('web', 'prefix',
635 638 default='',
636 639 )
637 640 coreconfigitem('web', 'refreshinterval',
638 641 default=20,
639 642 )
640 643 coreconfigitem('web', 'stripes',
641 644 default=1,
642 645 )
643 646 coreconfigitem('web', 'style',
644 647 default='paper',
645 648 )
646 649 coreconfigitem('web', 'templates',
647 650 default=None,
648 651 )
649 652 coreconfigitem('worker', 'backgroundclose',
650 653 default=dynamicdefault,
651 654 )
652 655 # Windows defaults to a limit of 512 open files. A buffer of 128
653 656 # should give us enough headway.
654 657 coreconfigitem('worker', 'backgroundclosemaxqueue',
655 658 default=384,
656 659 )
657 660 coreconfigitem('worker', 'backgroundcloseminfilecount',
658 661 default=2048,
659 662 )
660 663 coreconfigitem('worker', 'backgroundclosethreadcount',
661 664 default=4,
662 665 )
663 666 coreconfigitem('worker', 'numcpus',
664 667 default=None,
665 668 )
@@ -1,862 +1,867 b''
1 1 # copies.py - copy detection for Mercurial
2 2 #
3 3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import heapq
12 12 import os
13 13
14 14 from . import (
15 15 match as matchmod,
16 16 node,
17 17 pathutil,
18 18 phases,
19 19 scmutil,
20 20 util,
21 21 )
22 22
23 23 def _findlimit(repo, a, b):
24 24 """
25 25 Find the last revision that needs to be checked to ensure that a full
26 26 transitive closure for file copies can be properly calculated.
27 27 Generally, this means finding the earliest revision number that's an
28 28 ancestor of a or b but not both, except when a or b is a direct descendent
29 29 of the other, in which case we can return the minimum revnum of a and b.
30 30 None if no such revision exists.
31 31 """
32 32
33 33 # basic idea:
34 34 # - mark a and b with different sides
35 35 # - if a parent's children are all on the same side, the parent is
36 36 # on that side, otherwise it is on no side
37 37 # - walk the graph in topological order with the help of a heap;
38 38 # - add unseen parents to side map
39 39 # - clear side of any parent that has children on different sides
40 40 # - track number of interesting revs that might still be on a side
41 41 # - track the lowest interesting rev seen
42 42 # - quit when interesting revs is zero
43 43
44 44 cl = repo.changelog
45 45 working = len(cl) # pseudo rev for the working directory
46 46 if a is None:
47 47 a = working
48 48 if b is None:
49 49 b = working
50 50
51 51 side = {a: -1, b: 1}
52 52 visit = [-a, -b]
53 53 heapq.heapify(visit)
54 54 interesting = len(visit)
55 55 hascommonancestor = False
56 56 limit = working
57 57
58 58 while interesting:
59 59 r = -heapq.heappop(visit)
60 60 if r == working:
61 61 parents = [cl.rev(p) for p in repo.dirstate.parents()]
62 62 else:
63 63 parents = cl.parentrevs(r)
64 64 for p in parents:
65 65 if p < 0:
66 66 continue
67 67 if p not in side:
68 68 # first time we see p; add it to visit
69 69 side[p] = side[r]
70 70 if side[p]:
71 71 interesting += 1
72 72 heapq.heappush(visit, -p)
73 73 elif side[p] and side[p] != side[r]:
74 74 # p was interesting but now we know better
75 75 side[p] = 0
76 76 interesting -= 1
77 77 hascommonancestor = True
78 78 if side[r]:
79 79 limit = r # lowest rev visited
80 80 interesting -= 1
81 81
82 82 if not hascommonancestor:
83 83 return None
84 84
85 85 # Consider the following flow (see test-commit-amend.t under issue4405):
86 86 # 1/ File 'a0' committed
87 87 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
88 88 # 3/ Move back to first commit
89 89 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
90 90 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
91 91 #
92 92 # During the amend in step five, we will be in this state:
93 93 #
94 94 # @ 3 temporary amend commit for a1-amend
95 95 # |
96 96 # o 2 a1-amend
97 97 # |
98 98 # | o 1 a1
99 99 # |/
100 100 # o 0 a0
101 101 #
102 102 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
103 103 # yet the filelog has the copy information in rev 1 and we will not look
104 104 # back far enough unless we also look at the a and b as candidates.
105 105 # This only occurs when a is a descendent of b or visa-versa.
106 106 return min(limit, a, b)
107 107
108 108 def _chain(src, dst, a, b):
109 109 '''chain two sets of copies a->b'''
110 110 t = a.copy()
111 111 for k, v in b.iteritems():
112 112 if v in t:
113 113 # found a chain
114 114 if t[v] != k:
115 115 # file wasn't renamed back to itself
116 116 t[k] = t[v]
117 117 if v not in dst:
118 118 # chain was a rename, not a copy
119 119 del t[v]
120 120 if v in src:
121 121 # file is a copy of an existing file
122 122 t[k] = v
123 123
124 124 # remove criss-crossed copies
125 125 for k, v in t.items():
126 126 if k in src and v in dst:
127 127 del t[k]
128 128
129 129 return t
130 130
131 131 def _tracefile(fctx, am, limit=-1):
132 132 '''return file context that is the ancestor of fctx present in ancestor
133 133 manifest am, stopping after the first ancestor lower than limit'''
134 134
135 135 for f in fctx.ancestors():
136 136 if am.get(f.path(), None) == f.filenode():
137 137 return f
138 138 if limit >= 0 and f.linkrev() < limit and f.rev() < limit:
139 139 return None
140 140
141 141 def _dirstatecopies(d):
142 142 ds = d._repo.dirstate
143 143 c = ds.copies().copy()
144 144 for k in c.keys():
145 145 if ds[k] not in 'anm':
146 146 del c[k]
147 147 return c
148 148
149 149 def _computeforwardmissing(a, b, match=None):
150 150 """Computes which files are in b but not a.
151 151 This is its own function so extensions can easily wrap this call to see what
152 152 files _forwardcopies is about to process.
153 153 """
154 154 ma = a.manifest()
155 155 mb = b.manifest()
156 156 return mb.filesnotin(ma, match=match)
157 157
158 158 def _forwardcopies(a, b, match=None):
159 159 '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
160 160
161 161 # check for working copy
162 162 w = None
163 163 if b.rev() is None:
164 164 w = b
165 165 b = w.p1()
166 166 if a == b:
167 167 # short-circuit to avoid issues with merge states
168 168 return _dirstatecopies(w)
169 169
170 170 # files might have to be traced back to the fctx parent of the last
171 171 # one-side-only changeset, but not further back than that
172 172 limit = _findlimit(a._repo, a.rev(), b.rev())
173 173 if limit is None:
174 174 limit = -1
175 175 am = a.manifest()
176 176
177 177 # find where new files came from
178 178 # we currently don't try to find where old files went, too expensive
179 179 # this means we can miss a case like 'hg rm b; hg cp a b'
180 180 cm = {}
181 181
182 182 # Computing the forward missing is quite expensive on large manifests, since
183 183 # it compares the entire manifests. We can optimize it in the common use
184 184 # case of computing what copies are in a commit versus its parent (like
185 185 # during a rebase or histedit). Note, we exclude merge commits from this
186 186 # optimization, since the ctx.files() for a merge commit is not correct for
187 187 # this comparison.
188 188 forwardmissingmatch = match
189 189 if b.p1() == a and b.p2().node() == node.nullid:
190 190 filesmatcher = scmutil.matchfiles(a._repo, b.files())
191 191 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
192 192 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
193 193
194 194 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
195 195 for f in missing:
196 196 fctx = b[f]
197 197 fctx._ancestrycontext = ancestrycontext
198 198 ofctx = _tracefile(fctx, am, limit)
199 199 if ofctx:
200 200 cm[f] = ofctx.path()
201 201
202 202 # combine copies from dirstate if necessary
203 203 if w is not None:
204 204 cm = _chain(a, w, cm, _dirstatecopies(w))
205 205
206 206 return cm
207 207
208 208 def _backwardrenames(a, b):
209 209 if a._repo.ui.config('experimental', 'copytrace') == 'off':
210 210 return {}
211 211
212 212 # Even though we're not taking copies into account, 1:n rename situations
213 213 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
214 214 # arbitrarily pick one of the renames.
215 215 f = _forwardcopies(b, a)
216 216 r = {}
217 217 for k, v in sorted(f.iteritems()):
218 218 # remove copies
219 219 if v in a:
220 220 continue
221 221 r[v] = k
222 222 return r
223 223
224 224 def pathcopies(x, y, match=None):
225 225 '''find {dst@y: src@x} copy mapping for directed compare'''
226 226 if x == y or not x or not y:
227 227 return {}
228 228 a = y.ancestor(x)
229 229 if a == x:
230 230 return _forwardcopies(x, y, match=match)
231 231 if a == y:
232 232 return _backwardrenames(x, y)
233 233 return _chain(x, y, _backwardrenames(x, a),
234 234 _forwardcopies(a, y, match=match))
235 235
236 236 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
237 237 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
238 238 and c2. This is its own function so extensions can easily wrap this call
239 239 to see what files mergecopies is about to process.
240 240
241 241 Even though c1 and c2 are not used in this function, they are useful in
242 242 other extensions for being able to read the file nodes of the changed files.
243 243
244 244 "baselabel" can be passed to help distinguish the multiple computations
245 245 done in the graft case.
246 246 """
247 247 u1 = sorted(addedinm1 - addedinm2)
248 248 u2 = sorted(addedinm2 - addedinm1)
249 249
250 250 header = " unmatched files in %s"
251 251 if baselabel:
252 252 header += ' (from %s)' % baselabel
253 253 if u1:
254 254 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
255 255 if u2:
256 256 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
257 257 return u1, u2
258 258
259 259 def _makegetfctx(ctx):
260 260 """return a 'getfctx' function suitable for _checkcopies usage
261 261
262 262 We have to re-setup the function building 'filectx' for each
263 263 '_checkcopies' to ensure the linkrev adjustment is properly setup for
264 264 each. Linkrev adjustment is important to avoid bug in rename
265 265 detection. Moreover, having a proper '_ancestrycontext' setup ensures
266 266 the performance impact of this adjustment is kept limited. Without it,
267 267 each file could do a full dag traversal making the time complexity of
268 268 the operation explode (see issue4537).
269 269
270 270 This function exists here mostly to limit the impact on stable. Feel
271 271 free to refactor on default.
272 272 """
273 273 rev = ctx.rev()
274 274 repo = ctx._repo
275 275 ac = getattr(ctx, '_ancestrycontext', None)
276 276 if ac is None:
277 277 revs = [rev]
278 278 if rev is None:
279 279 revs = [p.rev() for p in ctx.parents()]
280 280 ac = repo.changelog.ancestors(revs, inclusive=True)
281 281 ctx._ancestrycontext = ac
282 282 def makectx(f, n):
283 283 if n in node.wdirnodes: # in a working context?
284 284 if ctx.rev() is None:
285 285 return ctx.filectx(f)
286 286 return repo[None][f]
287 287 fctx = repo.filectx(f, fileid=n)
288 288 # setup only needed for filectx not create from a changectx
289 289 fctx._ancestrycontext = ac
290 290 fctx._descendantrev = rev
291 291 return fctx
292 292 return util.lrucachefunc(makectx)
293 293
294 294 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
295 295 """combine partial copy paths"""
296 296 remainder = {}
297 297 for f in copyfrom:
298 298 if f in copyto:
299 299 finalcopy[copyto[f]] = copyfrom[f]
300 300 del copyto[f]
301 301 for f in incompletediverge:
302 302 assert f not in diverge
303 303 ic = incompletediverge[f]
304 304 if ic[0] in copyto:
305 305 diverge[f] = [copyto[ic[0]], ic[1]]
306 306 else:
307 307 remainder[f] = ic
308 308 return remainder
309 309
310 310 def mergecopies(repo, c1, c2, base):
311 311 """
312 312 The function calling different copytracing algorithms on the basis of config
313 313 which find moves and copies between context c1 and c2 that are relevant for
314 314 merging. 'base' will be used as the merge base.
315 315
316 316 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
317 317 files that were moved/ copied in one merge parent and modified in another.
318 318 For example:
319 319
320 320 o ---> 4 another commit
321 321 |
322 322 | o ---> 3 commit that modifies a.txt
323 323 | /
324 324 o / ---> 2 commit that moves a.txt to b.txt
325 325 |/
326 326 o ---> 1 merge base
327 327
328 328 If we try to rebase revision 3 on revision 4, since there is no a.txt in
329 329 revision 4, and if user have copytrace disabled, we prints the following
330 330 message:
331 331
332 332 ```other changed <file> which local deleted```
333 333
334 334 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
335 335 "dirmove".
336 336
337 337 "copy" is a mapping from destination name -> source name,
338 338 where source is in c1 and destination is in c2 or vice-versa.
339 339
340 340 "movewithdir" is a mapping from source name -> destination name,
341 341 where the file at source present in one context but not the other
342 342 needs to be moved to destination by the merge process, because the
343 343 other context moved the directory it is in.
344 344
345 345 "diverge" is a mapping of source name -> list of destination names
346 346 for divergent renames.
347 347
348 348 "renamedelete" is a mapping of source name -> list of destination
349 349 names for files deleted in c1 that were renamed in c2 or vice-versa.
350 350
351 351 "dirmove" is a mapping of detected source dir -> destination dir renames.
352 352 This is needed for handling changes to new files previously grafted into
353 353 renamed directories.
354 354 """
355 355 # avoid silly behavior for update from empty dir
356 356 if not c1 or not c2 or c1 == c2:
357 357 return {}, {}, {}, {}, {}
358 358
359 359 # avoid silly behavior for parent -> working dir
360 360 if c2.node() is None and c1.node() == repo.dirstate.p1():
361 361 return repo.dirstate.copies(), {}, {}, {}, {}
362 362
363 363 copytracing = repo.ui.config('experimental', 'copytrace')
364 364
365 365 # Copy trace disabling is explicitly below the node == p1 logic above
366 366 # because the logic above is required for a simple copy to be kept across a
367 367 # rebase.
368 368 if copytracing == 'off':
369 369 return {}, {}, {}, {}, {}
370 370 elif copytracing == 'heuristics':
371 371 # Do full copytracing if only drafts are involved as that will be fast
372 372 # enough and will also cover the copies which can be missed by
373 373 # heuristics
374 if _isfullcopytraceable(c1, base):
374 if _isfullcopytraceable(repo, c1, base):
375 375 return _fullcopytracing(repo, c1, c2, base)
376 376 return _heuristicscopytracing(repo, c1, c2, base)
377 377 else:
378 378 return _fullcopytracing(repo, c1, c2, base)
379 379
380 def _isfullcopytraceable(c1, base):
380 def _isfullcopytraceable(repo, c1, base):
381 381 """ Checks that if base, source and destination are all draft branches, if
382 382 yes let's use the full copytrace algorithm for increased capabilities since
383 383 it will be fast enough.
384 384 """
385 if c1.rev() is None:
386 c1 = c1.p1()
385 387
386 388 nonpublicphases = set([phases.draft, phases.secret])
387 389
388 390 if (c1.phase() in nonpublicphases) and (base.phase() in nonpublicphases):
389 return True
391 sourcecommitlimit = repo.ui.configint('experimental',
392 'copytrace.sourcecommitlimit')
393 commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
394 return commits < sourcecommitlimit
390 395 return False
391 396
392 397 def _fullcopytracing(repo, c1, c2, base):
393 398 """ The full copytracing algorithm which finds all the new files that were
394 399 added from merge base up to the top commit and for each file it checks if
395 400 this file was copied from another file.
396 401
397 402 This is pretty slow when a lot of changesets are involved but will track all
398 403 the copies.
399 404 """
400 405 # In certain scenarios (e.g. graft, update or rebase), base can be
401 406 # overridden We still need to know a real common ancestor in this case We
402 407 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
403 408 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
404 409 # caller may not know if the revision passed in lieu of the CA is a genuine
405 410 # common ancestor or not without explicitly checking it, it's better to
406 411 # determine that here.
407 412 #
408 413 # base.descendant(wc) and base.descendant(base) are False, work around that
409 414 _c1 = c1.p1() if c1.rev() is None else c1
410 415 _c2 = c2.p1() if c2.rev() is None else c2
411 416 # an endpoint is "dirty" if it isn't a descendant of the merge base
412 417 # if we have a dirty endpoint, we need to trigger graft logic, and also
413 418 # keep track of which endpoint is dirty
414 419 dirtyc1 = not (base == _c1 or base.descendant(_c1))
415 420 dirtyc2 = not (base == _c2 or base.descendant(_c2))
416 421 graft = dirtyc1 or dirtyc2
417 422 tca = base
418 423 if graft:
419 424 tca = _c1.ancestor(_c2)
420 425
421 426 limit = _findlimit(repo, c1.rev(), c2.rev())
422 427 if limit is None:
423 428 # no common ancestor, no copies
424 429 return {}, {}, {}, {}, {}
425 430 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
426 431
427 432 m1 = c1.manifest()
428 433 m2 = c2.manifest()
429 434 mb = base.manifest()
430 435
431 436 # gather data from _checkcopies:
432 437 # - diverge = record all diverges in this dict
433 438 # - copy = record all non-divergent copies in this dict
434 439 # - fullcopy = record all copies in this dict
435 440 # - incomplete = record non-divergent partial copies here
436 441 # - incompletediverge = record divergent partial copies here
437 442 diverge = {} # divergence data is shared
438 443 incompletediverge = {}
439 444 data1 = {'copy': {},
440 445 'fullcopy': {},
441 446 'incomplete': {},
442 447 'diverge': diverge,
443 448 'incompletediverge': incompletediverge,
444 449 }
445 450 data2 = {'copy': {},
446 451 'fullcopy': {},
447 452 'incomplete': {},
448 453 'diverge': diverge,
449 454 'incompletediverge': incompletediverge,
450 455 }
451 456
452 457 # find interesting file sets from manifests
453 458 addedinm1 = m1.filesnotin(mb)
454 459 addedinm2 = m2.filesnotin(mb)
455 460 bothnew = sorted(addedinm1 & addedinm2)
456 461 if tca == base:
457 462 # unmatched file from base
458 463 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
459 464 u1u, u2u = u1r, u2r
460 465 else:
461 466 # unmatched file from base (DAG rotation in the graft case)
462 467 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
463 468 baselabel='base')
464 469 # unmatched file from topological common ancestors (no DAG rotation)
465 470 # need to recompute this for directory move handling when grafting
466 471 mta = tca.manifest()
467 472 u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
468 473 m2.filesnotin(mta),
469 474 baselabel='topological common ancestor')
470 475
471 476 for f in u1u:
472 477 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
473 478
474 479 for f in u2u:
475 480 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
476 481
477 482 copy = dict(data1['copy'])
478 483 copy.update(data2['copy'])
479 484 fullcopy = dict(data1['fullcopy'])
480 485 fullcopy.update(data2['fullcopy'])
481 486
482 487 if dirtyc1:
483 488 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
484 489 incompletediverge)
485 490 else:
486 491 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
487 492 incompletediverge)
488 493
489 494 renamedelete = {}
490 495 renamedeleteset = set()
491 496 divergeset = set()
492 497 for of, fl in diverge.items():
493 498 if len(fl) == 1 or of in c1 or of in c2:
494 499 del diverge[of] # not actually divergent, or not a rename
495 500 if of not in c1 and of not in c2:
496 501 # renamed on one side, deleted on the other side, but filter
497 502 # out files that have been renamed and then deleted
498 503 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
499 504 renamedeleteset.update(fl) # reverse map for below
500 505 else:
501 506 divergeset.update(fl) # reverse map for below
502 507
503 508 if bothnew:
504 509 repo.ui.debug(" unmatched files new in both:\n %s\n"
505 510 % "\n ".join(bothnew))
506 511 bothdiverge = {}
507 512 bothincompletediverge = {}
508 513 remainder = {}
509 514 both1 = {'copy': {},
510 515 'fullcopy': {},
511 516 'incomplete': {},
512 517 'diverge': bothdiverge,
513 518 'incompletediverge': bothincompletediverge
514 519 }
515 520 both2 = {'copy': {},
516 521 'fullcopy': {},
517 522 'incomplete': {},
518 523 'diverge': bothdiverge,
519 524 'incompletediverge': bothincompletediverge
520 525 }
521 526 for f in bothnew:
522 527 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
523 528 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
524 529 if dirtyc1:
525 530 # incomplete copies may only be found on the "dirty" side for bothnew
526 531 assert not both2['incomplete']
527 532 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
528 533 bothincompletediverge)
529 534 elif dirtyc2:
530 535 assert not both1['incomplete']
531 536 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
532 537 bothincompletediverge)
533 538 else:
534 539 # incomplete copies and divergences can't happen outside grafts
535 540 assert not both1['incomplete']
536 541 assert not both2['incomplete']
537 542 assert not bothincompletediverge
538 543 for f in remainder:
539 544 assert f not in bothdiverge
540 545 ic = remainder[f]
541 546 if ic[0] in (m1 if dirtyc1 else m2):
542 547 # backed-out rename on one side, but watch out for deleted files
543 548 bothdiverge[f] = ic
544 549 for of, fl in bothdiverge.items():
545 550 if len(fl) == 2 and fl[0] == fl[1]:
546 551 copy[fl[0]] = of # not actually divergent, just matching renames
547 552
548 553 if fullcopy and repo.ui.debugflag:
549 554 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
550 555 "% = renamed and deleted):\n")
551 556 for f in sorted(fullcopy):
552 557 note = ""
553 558 if f in copy:
554 559 note += "*"
555 560 if f in divergeset:
556 561 note += "!"
557 562 if f in renamedeleteset:
558 563 note += "%"
559 564 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
560 565 note))
561 566 del divergeset
562 567
563 568 if not fullcopy:
564 569 return copy, {}, diverge, renamedelete, {}
565 570
566 571 repo.ui.debug(" checking for directory renames\n")
567 572
568 573 # generate a directory move map
569 574 d1, d2 = c1.dirs(), c2.dirs()
570 575 # Hack for adding '', which is not otherwise added, to d1 and d2
571 576 d1.addpath('/')
572 577 d2.addpath('/')
573 578 invalid = set()
574 579 dirmove = {}
575 580
576 581 # examine each file copy for a potential directory move, which is
577 582 # when all the files in a directory are moved to a new directory
578 583 for dst, src in fullcopy.iteritems():
579 584 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
580 585 if dsrc in invalid:
581 586 # already seen to be uninteresting
582 587 continue
583 588 elif dsrc in d1 and ddst in d1:
584 589 # directory wasn't entirely moved locally
585 590 invalid.add(dsrc + "/")
586 591 elif dsrc in d2 and ddst in d2:
587 592 # directory wasn't entirely moved remotely
588 593 invalid.add(dsrc + "/")
589 594 elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/":
590 595 # files from the same directory moved to two different places
591 596 invalid.add(dsrc + "/")
592 597 else:
593 598 # looks good so far
594 599 dirmove[dsrc + "/"] = ddst + "/"
595 600
596 601 for i in invalid:
597 602 if i in dirmove:
598 603 del dirmove[i]
599 604 del d1, d2, invalid
600 605
601 606 if not dirmove:
602 607 return copy, {}, diverge, renamedelete, {}
603 608
604 609 for d in dirmove:
605 610 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
606 611 (d, dirmove[d]))
607 612
608 613 movewithdir = {}
609 614 # check unaccounted nonoverlapping files against directory moves
610 615 for f in u1r + u2r:
611 616 if f not in fullcopy:
612 617 for d in dirmove:
613 618 if f.startswith(d):
614 619 # new file added in a directory that was moved, move it
615 620 df = dirmove[d] + f[len(d):]
616 621 if df not in copy:
617 622 movewithdir[f] = df
618 623 repo.ui.debug((" pending file src: '%s' -> "
619 624 "dst: '%s'\n") % (f, df))
620 625 break
621 626
622 627 return copy, movewithdir, diverge, renamedelete, dirmove
623 628
624 629 def _heuristicscopytracing(repo, c1, c2, base):
625 630 """ Fast copytracing using filename heuristics
626 631
627 632 Assumes that moves or renames are of following two types:
628 633
629 634 1) Inside a directory only (same directory name but different filenames)
630 635 2) Move from one directory to another
631 636 (same filenames but different directory names)
632 637
633 638 Works only when there are no merge commits in the "source branch".
634 639 Source branch is commits from base up to c2 not including base.
635 640
636 641 If merge is involved it fallbacks to _fullcopytracing().
637 642
638 643 Can be used by setting the following config:
639 644
640 645 [experimental]
641 646 copytrace = heuristics
642 647 """
643 648
644 649 if c1.rev() is None:
645 650 c1 = c1.p1()
646 651 if c2.rev() is None:
647 652 c2 = c2.p1()
648 653
649 654 copies = {}
650 655
651 656 changedfiles = set()
652 657 m1 = c1.manifest()
653 658 if not repo.revs('%d::%d', base.rev(), c2.rev()):
654 659 # If base is not in c2 branch, we switch to fullcopytracing
655 660 repo.ui.debug("switching to full copytracing as base is not "
656 661 "an ancestor of c2\n")
657 662 return _fullcopytracing(repo, c1, c2, base)
658 663
659 664 ctx = c2
660 665 while ctx != base:
661 666 if len(ctx.parents()) == 2:
662 667 # To keep things simple let's not handle merges
663 668 repo.ui.debug("switching to full copytracing because of merges\n")
664 669 return _fullcopytracing(repo, c1, c2, base)
665 670 changedfiles.update(ctx.files())
666 671 ctx = ctx.p1()
667 672
668 673 cp = _forwardcopies(base, c2)
669 674 for dst, src in cp.iteritems():
670 675 if src in m1:
671 676 copies[dst] = src
672 677
673 678 # file is missing if it isn't present in the destination, but is present in
674 679 # the base and present in the source.
675 680 # Presence in the base is important to exclude added files, presence in the
676 681 # source is important to exclude removed files.
677 682 missingfiles = filter(lambda f: f not in m1 and f in base and f in c2,
678 683 changedfiles)
679 684
680 685 if missingfiles:
681 686 basenametofilename = collections.defaultdict(list)
682 687 dirnametofilename = collections.defaultdict(list)
683 688
684 689 for f in m1.filesnotin(base.manifest()):
685 690 basename = os.path.basename(f)
686 691 dirname = os.path.dirname(f)
687 692 basenametofilename[basename].append(f)
688 693 dirnametofilename[dirname].append(f)
689 694
690 695 # in case of a rebase/graft, base may not be a common ancestor
691 696 anc = c1.ancestor(c2)
692 697
693 698 for f in missingfiles:
694 699 basename = os.path.basename(f)
695 700 dirname = os.path.dirname(f)
696 701 samebasename = basenametofilename[basename]
697 702 samedirname = dirnametofilename[dirname]
698 703 movecandidates = samebasename + samedirname
699 704 # f is guaranteed to be present in c2, that's why
700 705 # c2.filectx(f) won't fail
701 706 f2 = c2.filectx(f)
702 707 for candidate in movecandidates:
703 708 f1 = c1.filectx(candidate)
704 709 if _related(f1, f2, anc.rev()):
705 710 # if there are a few related copies then we'll merge
706 711 # changes into all of them. This matches the behaviour
707 712 # of upstream copytracing
708 713 copies[candidate] = f
709 714
710 715 return copies, {}, {}, {}, {}
711 716
712 717 def _related(f1, f2, limit):
713 718 """return True if f1 and f2 filectx have a common ancestor
714 719
715 720 Walk back to common ancestor to see if the two files originate
716 721 from the same file. Since workingfilectx's rev() is None it messes
717 722 up the integer comparison logic, hence the pre-step check for
718 723 None (f1 and f2 can only be workingfilectx's initially).
719 724 """
720 725
721 726 if f1 == f2:
722 727 return f1 # a match
723 728
724 729 g1, g2 = f1.ancestors(), f2.ancestors()
725 730 try:
726 731 f1r, f2r = f1.linkrev(), f2.linkrev()
727 732
728 733 if f1r is None:
729 734 f1 = next(g1)
730 735 if f2r is None:
731 736 f2 = next(g2)
732 737
733 738 while True:
734 739 f1r, f2r = f1.linkrev(), f2.linkrev()
735 740 if f1r > f2r:
736 741 f1 = next(g1)
737 742 elif f2r > f1r:
738 743 f2 = next(g2)
739 744 elif f1 == f2:
740 745 return f1 # a match
741 746 elif f1r == f2r or f1r < limit or f2r < limit:
742 747 return False # copy no longer relevant
743 748 except StopIteration:
744 749 return False
745 750
746 751 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
747 752 """
748 753 check possible copies of f from msrc to mdst
749 754
750 755 srcctx = starting context for f in msrc
751 756 dstctx = destination context for f in mdst
752 757 f = the filename to check (as in msrc)
753 758 base = the changectx used as a merge base
754 759 tca = topological common ancestor for graft-like scenarios
755 760 remotebase = True if base is outside tca::srcctx, False otherwise
756 761 limit = the rev number to not search beyond
757 762 data = dictionary of dictionary to store copy data. (see mergecopies)
758 763
759 764 note: limit is only an optimization, and provides no guarantee that
760 765 irrelevant revisions will not be visited
761 766 there is no easy way to make this algorithm stop in a guaranteed way
762 767 once it "goes behind a certain revision".
763 768 """
764 769
765 770 msrc = srcctx.manifest()
766 771 mdst = dstctx.manifest()
767 772 mb = base.manifest()
768 773 mta = tca.manifest()
769 774 # Might be true if this call is about finding backward renames,
770 775 # This happens in the case of grafts because the DAG is then rotated.
771 776 # If the file exists in both the base and the source, we are not looking
772 777 # for a rename on the source side, but on the part of the DAG that is
773 778 # traversed backwards.
774 779 #
775 780 # In the case there is both backward and forward renames (before and after
776 781 # the base) this is more complicated as we must detect a divergence.
777 782 # We use 'backwards = False' in that case.
778 783 backwards = not remotebase and base != tca and f in mb
779 784 getsrcfctx = _makegetfctx(srcctx)
780 785 getdstfctx = _makegetfctx(dstctx)
781 786
782 787 if msrc[f] == mb.get(f) and not remotebase:
783 788 # Nothing to merge
784 789 return
785 790
786 791 of = None
787 792 seen = {f}
788 793 for oc in getsrcfctx(f, msrc[f]).ancestors():
789 794 ocr = oc.linkrev()
790 795 of = oc.path()
791 796 if of in seen:
792 797 # check limit late - grab last rename before
793 798 if ocr < limit:
794 799 break
795 800 continue
796 801 seen.add(of)
797 802
798 803 # remember for dir rename detection
799 804 if backwards:
800 805 data['fullcopy'][of] = f # grafting backwards through renames
801 806 else:
802 807 data['fullcopy'][f] = of
803 808 if of not in mdst:
804 809 continue # no match, keep looking
805 810 if mdst[of] == mb.get(of):
806 811 return # no merge needed, quit early
807 812 c2 = getdstfctx(of, mdst[of])
808 813 # c2 might be a plain new file on added on destination side that is
809 814 # unrelated to the droids we are looking for.
810 815 cr = _related(oc, c2, tca.rev())
811 816 if cr and (of == f or of == c2.path()): # non-divergent
812 817 if backwards:
813 818 data['copy'][of] = f
814 819 elif of in mb:
815 820 data['copy'][f] = of
816 821 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
817 822 data['copy'][of] = f
818 823 del data['fullcopy'][f]
819 824 data['fullcopy'][of] = f
820 825 else: # divergence w.r.t. graft CA on one side of topological CA
821 826 for sf in seen:
822 827 if sf in mb:
823 828 assert sf not in data['diverge']
824 829 data['diverge'][sf] = [f, of]
825 830 break
826 831 return
827 832
828 833 if of in mta:
829 834 if backwards or remotebase:
830 835 data['incomplete'][of] = f
831 836 else:
832 837 for sf in seen:
833 838 if sf in mb:
834 839 if tca == base:
835 840 data['diverge'].setdefault(sf, []).append(f)
836 841 else:
837 842 data['incompletediverge'][sf] = [of, f]
838 843 return
839 844
840 845 def duplicatecopies(repo, rev, fromrev, skiprev=None):
841 846 '''reproduce copies from fromrev to rev in the dirstate
842 847
843 848 If skiprev is specified, it's a revision that should be used to
844 849 filter copy records. Any copies that occur between fromrev and
845 850 skiprev will not be duplicated, even if they appear in the set of
846 851 copies between fromrev and rev.
847 852 '''
848 853 exclude = {}
849 854 if (skiprev is not None and
850 855 repo.ui.config('experimental', 'copytrace') != 'off'):
851 856 # copytrace='off' skips this line, but not the entire function because
852 857 # the line below is O(size of the repo) during a rebase, while the rest
853 858 # of the function is much faster (and is required for carrying copy
854 859 # metadata across the rebase anyway).
855 860 exclude = pathcopies(repo[fromrev], repo[skiprev])
856 861 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
857 862 # copies.pathcopies returns backward renames, so dst might not
858 863 # actually be in the dirstate
859 864 if dst in exclude:
860 865 continue
861 866 if repo.dirstate[dst] in "nma":
862 867 repo.dirstate.copy(src, dst)
@@ -1,667 +1,714 b''
1 1 Test for the heuristic copytracing algorithm
2 2 ============================================
3 3
4 4 $ cat >> $TESTTMP/copytrace.sh << '__EOF__'
5 5 > initclient() {
6 6 > cat >> $1/.hg/hgrc <<EOF
7 7 > [experimental]
8 8 > copytrace = heuristics
9 9 > EOF
10 10 > }
11 11 > __EOF__
12 12 $ . "$TESTTMP/copytrace.sh"
13 13
14 14 $ cat >> $HGRCPATH << EOF
15 15 > [extensions]
16 16 > rebase=
17 17 > shelve=
18 18 > EOF
19 19
20 20 Check filename heuristics (same dirname and same basename)
21 21 $ hg init server
22 22 $ cd server
23 23 $ echo a > a
24 24 $ mkdir dir
25 25 $ echo a > dir/file.txt
26 26 $ hg addremove
27 27 adding a
28 28 adding dir/file.txt
29 29 $ hg ci -m initial
30 30 $ hg mv a b
31 31 $ hg mv -q dir dir2
32 32 $ hg ci -m 'mv a b, mv dir/ dir2/'
33 33 $ cd ..
34 34 $ hg clone -q server repo
35 35 $ initclient repo
36 36 $ cd repo
37 37 $ hg up -q 0
38 38 $ echo b > a
39 39 $ echo b > dir/file.txt
40 40 $ hg ci -qm 'mod a, mod dir/file.txt'
41 41
42 42 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
43 43 @ changeset: 557f403c0afd2a3cf15d7e2fb1f1001a8b85e081
44 44 | desc: mod a, mod dir/file.txt, phase: draft
45 45 | o changeset: 928d74bc9110681920854d845c06959f6dfc9547
46 46 |/ desc: mv a b, mv dir/ dir2/, phase: public
47 47 o changeset: 3c482b16e54596fed340d05ffaf155f156cda7ee
48 48 desc: initial, phase: public
49 49
50 50 $ hg rebase -s . -d 1
51 51 rebasing 2:557f403c0afd "mod a, mod dir/file.txt" (tip)
52 52 merging b and a to b
53 53 merging dir2/file.txt and dir/file.txt to dir2/file.txt
54 54 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg (glob)
55 55 $ cd ..
56 56 $ rm -rf server
57 57 $ rm -rf repo
58 58
59 59 Make sure filename heuristics do not when they are not related
60 60 $ hg init server
61 61 $ cd server
62 62 $ echo 'somecontent' > a
63 63 $ hg add a
64 64 $ hg ci -m initial
65 65 $ hg rm a
66 66 $ echo 'completelydifferentcontext' > b
67 67 $ hg add b
68 68 $ hg ci -m 'rm a, add b'
69 69 $ cd ..
70 70 $ hg clone -q server repo
71 71 $ initclient repo
72 72 $ cd repo
73 73 $ hg up -q 0
74 74 $ printf 'somecontent\nmoarcontent' > a
75 75 $ hg ci -qm 'mode a'
76 76
77 77 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
78 78 @ changeset: d526312210b9e8f795d576a77dc643796384d86e
79 79 | desc: mode a, phase: draft
80 80 | o changeset: 46985f76c7e5e5123433527f5c8526806145650b
81 81 |/ desc: rm a, add b, phase: public
82 82 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
83 83 desc: initial, phase: public
84 84
85 85 $ hg rebase -s . -d 1
86 86 rebasing 2:d526312210b9 "mode a" (tip)
87 87 other [source] changed a which local [dest] deleted
88 88 use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
89 89 unresolved conflicts (see hg resolve, then hg rebase --continue)
90 90 [1]
91 91
92 92 $ cd ..
93 93 $ rm -rf server
94 94 $ rm -rf repo
95 95
96 96 Test when lca didn't modified the file that was moved
97 97 $ hg init server
98 98 $ cd server
99 99 $ echo 'somecontent' > a
100 100 $ hg add a
101 101 $ hg ci -m initial
102 102 $ echo c > c
103 103 $ hg add c
104 104 $ hg ci -m randomcommit
105 105 $ hg mv a b
106 106 $ hg ci -m 'mv a b'
107 107 $ cd ..
108 108 $ hg clone -q server repo
109 109 $ initclient repo
110 110 $ cd repo
111 111 $ hg up -q 1
112 112 $ echo b > a
113 113 $ hg ci -qm 'mod a'
114 114
115 115 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
116 116 @ changeset: 9d5cf99c3d9f8e8b05ba55421f7f56530cfcf3bc
117 117 | desc: mod a, phase: draft
118 118 | o changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
119 119 |/ desc: mv a b, phase: public
120 120 o changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
121 121 | desc: randomcommit, phase: public
122 122 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
123 123 desc: initial, phase: public
124 124
125 125 $ hg rebase -s . -d 2
126 126 rebasing 3:9d5cf99c3d9f "mod a" (tip)
127 127 merging b and a to b
128 128 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg (glob)
129 129 $ cd ..
130 130 $ rm -rf server
131 131 $ rm -rf repo
132 132
133 133 Rebase "backwards"
134 134 $ hg init server
135 135 $ cd server
136 136 $ echo 'somecontent' > a
137 137 $ hg add a
138 138 $ hg ci -m initial
139 139 $ echo c > c
140 140 $ hg add c
141 141 $ hg ci -m randomcommit
142 142 $ hg mv a b
143 143 $ hg ci -m 'mv a b'
144 144 $ cd ..
145 145 $ hg clone -q server repo
146 146 $ initclient repo
147 147 $ cd repo
148 148 $ hg up -q 2
149 149 $ echo b > b
150 150 $ hg ci -qm 'mod b'
151 151
152 152 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
153 153 @ changeset: fbe97126b3969056795c462a67d93faf13e4d298
154 154 | desc: mod b, phase: draft
155 155 o changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
156 156 | desc: mv a b, phase: public
157 157 o changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
158 158 | desc: randomcommit, phase: public
159 159 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
160 160 desc: initial, phase: public
161 161
162 162 $ hg rebase -s . -d 0
163 163 rebasing 3:fbe97126b396 "mod b" (tip)
164 164 merging a and b to a
165 165 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg (glob)
166 166 $ cd ..
167 167 $ rm -rf server
168 168 $ rm -rf repo
169 169
170 170 Rebase draft commit on top of draft commit
171 171 $ hg init repo
172 172 $ initclient repo
173 173 $ cd repo
174 174 $ echo 'somecontent' > a
175 175 $ hg add a
176 176 $ hg ci -m initial
177 177 $ hg mv a b
178 178 $ hg ci -m 'mv a b'
179 179 $ hg up -q ".^"
180 180 $ echo b > a
181 181 $ hg ci -qm 'mod a'
182 182
183 183 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
184 184 @ changeset: 5268f05aa1684cfb5741e9eb05eddcc1c5ee7508
185 185 | desc: mod a, phase: draft
186 186 | o changeset: 542cb58df733ee48fa74729bd2cdb94c9310d362
187 187 |/ desc: mv a b, phase: draft
188 188 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
189 189 desc: initial, phase: draft
190 190
191 191 $ hg rebase -s . -d 1
192 192 rebasing 2:5268f05aa168 "mod a" (tip)
193 193 merging b and a to b
194 194 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5268f05aa168-284f6515-rebase.hg (glob)
195 195 $ cd ..
196 196 $ rm -rf server
197 197 $ rm -rf repo
198 198
199 199 Check a few potential move candidates
200 200 $ hg init server
201 201 $ initclient server
202 202 $ cd server
203 203 $ mkdir dir
204 204 $ echo a > dir/a
205 205 $ hg add dir/a
206 206 $ hg ci -qm initial
207 207 $ hg mv dir/a dir/b
208 208 $ hg ci -qm 'mv dir/a dir/b'
209 209 $ mkdir dir2
210 210 $ echo b > dir2/a
211 211 $ hg add dir2/a
212 212 $ hg ci -qm 'create dir2/a'
213 213 $ cd ..
214 214 $ hg clone -q server repo
215 215 $ initclient repo
216 216 $ cd repo
217 217 $ hg up -q 0
218 218 $ echo b > dir/a
219 219 $ hg ci -qm 'mod dir/a'
220 220
221 221 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
222 222 @ changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
223 223 | desc: mod dir/a, phase: draft
224 224 | o changeset: 4494bf7efd2e0dfdd388e767fb913a8a3731e3fa
225 225 | | desc: create dir2/a, phase: public
226 226 | o changeset: b1784dfab6ea6bfafeb11c0ac50a2981b0fe6ade
227 227 |/ desc: mv dir/a dir/b, phase: public
228 228 o changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
229 229 desc: initial, phase: public
230 230
231 231 $ hg rebase -s . -d 2
232 232 rebasing 3:6b2f4cece40f "mod dir/a" (tip)
233 233 merging dir/b and dir/a to dir/b
234 234 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg (glob)
235 235 $ cd ..
236 236 $ rm -rf server
237 237 $ rm -rf repo
238 238
239 239 Move file in one branch and delete it in another
240 240 $ hg init server
241 241 $ initclient server
242 242 $ cd server
243 243 $ echo a > a
244 244 $ hg add a
245 245 $ hg ci -m initial
246 246 $ cd ..
247 247 $ hg clone -q server repo
248 248 $ initclient repo
249 249 $ cd repo
250 250 $ hg mv a b
251 251 $ hg ci -m 'mv a b'
252 252 $ hg up -q ".^"
253 253 $ hg rm a
254 254 $ hg ci -m 'del a'
255 255 created new head
256 256
257 257 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
258 258 @ changeset: 7d61ee3b1e48577891a072024968428ba465c47b
259 259 | desc: del a, phase: draft
260 260 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
261 261 |/ desc: mv a b, phase: draft
262 262 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
263 263 desc: initial, phase: public
264 264
265 265 $ hg rebase -s 1 -d 2
266 266 rebasing 1:472e38d57782 "mv a b"
267 267 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg (glob)
268 268 $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062
269 269 $ ls
270 270 b
271 271 $ cd ..
272 272 $ rm -rf server
273 273 $ rm -rf repo
274 274
275 275 Move a directory in draft branch
276 276 $ hg init server
277 277 $ initclient server
278 278 $ cd server
279 279 $ mkdir dir
280 280 $ echo a > dir/a
281 281 $ hg add dir/a
282 282 $ hg ci -qm initial
283 283 $ cd ..
284 284 $ hg clone -q server repo
285 285 $ initclient repo
286 286 $ cd repo
287 287 $ echo b > dir/a
288 288 $ hg ci -qm 'mod dir/a'
289 289 $ hg up -q ".^"
290 290 $ hg mv -q dir/ dir2
291 291 $ hg ci -qm 'mv dir/ dir2/'
292 292
293 293 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
294 294 @ changeset: a33d80b6e352591dfd82784e1ad6cdd86b25a239
295 295 | desc: mv dir/ dir2/, phase: draft
296 296 | o changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
297 297 |/ desc: mod dir/a, phase: draft
298 298 o changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
299 299 desc: initial, phase: public
300 300
301 301 $ hg rebase -s . -d 1
302 302 rebasing 2:a33d80b6e352 "mv dir/ dir2/" (tip)
303 303 merging dir/a and dir2/a to dir2/a
304 304 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg (glob)
305 305 $ cd ..
306 306 $ rm -rf server
307 307 $ rm -rf repo
308 308
309 309 Move file twice and rebase mod on top of moves
310 310 $ hg init server
311 311 $ initclient server
312 312 $ cd server
313 313 $ echo a > a
314 314 $ hg add a
315 315 $ hg ci -m initial
316 316 $ hg mv a b
317 317 $ hg ci -m 'mv a b'
318 318 $ hg mv b c
319 319 $ hg ci -m 'mv b c'
320 320 $ cd ..
321 321 $ hg clone -q server repo
322 322 $ initclient repo
323 323 $ cd repo
324 324 $ hg up -q 0
325 325 $ echo c > a
326 326 $ hg ci -m 'mod a'
327 327 created new head
328 328 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
329 329 @ changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
330 330 | desc: mod a, phase: draft
331 331 | o changeset: d3efd280421d24f9f229997c19e654761c942a71
332 332 | | desc: mv b c, phase: public
333 333 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
334 334 |/ desc: mv a b, phase: public
335 335 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
336 336 desc: initial, phase: public
337 337 $ hg rebase -s . -d 2
338 338 rebasing 3:d41316942216 "mod a" (tip)
339 339 merging c and a to c
340 340 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg (glob)
341 341
342 342 $ cd ..
343 343 $ rm -rf server
344 344 $ rm -rf repo
345 345
346 346 Move file twice and rebase moves on top of mods
347 347 $ hg init server
348 348 $ initclient server
349 349 $ cd server
350 350 $ echo a > a
351 351 $ hg add a
352 352 $ hg ci -m initial
353 353 $ cd ..
354 354 $ hg clone -q server repo
355 355 $ initclient repo
356 356 $ cd repo
357 357 $ hg mv a b
358 358 $ hg ci -m 'mv a b'
359 359 $ hg mv b c
360 360 $ hg ci -m 'mv b c'
361 361 $ hg up -q 0
362 362 $ echo c > a
363 363 $ hg ci -m 'mod a'
364 364 created new head
365 365 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
366 366 @ changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
367 367 | desc: mod a, phase: draft
368 368 | o changeset: d3efd280421d24f9f229997c19e654761c942a71
369 369 | | desc: mv b c, phase: draft
370 370 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
371 371 |/ desc: mv a b, phase: draft
372 372 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
373 373 desc: initial, phase: public
374 374 $ hg rebase -s 1 -d .
375 375 rebasing 1:472e38d57782 "mv a b"
376 376 merging a and b to b
377 377 rebasing 2:d3efd280421d "mv b c"
378 378 merging b and c to c
379 379 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg (glob)
380 380
381 381 $ cd ..
382 382 $ rm -rf server
383 383 $ rm -rf repo
384 384
385 385 Move one file and add another file in the same folder in one branch, modify file in another branch
386 386 $ hg init server
387 387 $ initclient server
388 388 $ cd server
389 389 $ echo a > a
390 390 $ hg add a
391 391 $ hg ci -m initial
392 392 $ hg mv a b
393 393 $ hg ci -m 'mv a b'
394 394 $ echo c > c
395 395 $ hg add c
396 396 $ hg ci -m 'add c'
397 397 $ cd ..
398 398 $ hg clone -q server repo
399 399 $ initclient repo
400 400 $ cd repo
401 401 $ hg up -q 0
402 402 $ echo b > a
403 403 $ hg ci -m 'mod a'
404 404 created new head
405 405
406 406 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
407 407 @ changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
408 408 | desc: mod a, phase: draft
409 409 | o changeset: b1a6187e79fbce851bb584eadcb0cc4a80290fd9
410 410 | | desc: add c, phase: public
411 411 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
412 412 |/ desc: mv a b, phase: public
413 413 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
414 414 desc: initial, phase: public
415 415
416 416 $ hg rebase -s . -d 2
417 417 rebasing 3:ef716627c70b "mod a" (tip)
418 418 merging b and a to b
419 419 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
420 420 $ ls
421 421 b
422 422 c
423 423 $ cat b
424 424 b
425 425
426 426 Merge test
427 427 $ hg init server
428 428 $ cd server
429 429 $ echo a > a
430 430 $ hg add a
431 431 $ hg ci -m initial
432 432 $ echo b > a
433 433 $ hg ci -m 'modify a'
434 434 $ hg up -q 0
435 435 $ hg mv a b
436 436 $ hg ci -m 'mv a b'
437 437 created new head
438 438 $ cd ..
439 439 $ hg clone -q server repo
440 440 $ initclient repo
441 441 $ cd repo
442 442 $ hg up -q 2
443 443
444 444 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
445 445 @ changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
446 446 | desc: mv a b, phase: public
447 447 | o changeset: b0357b07f79129a3d08a68621271ca1352ae8a09
448 448 |/ desc: modify a, phase: public
449 449 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
450 450 desc: initial, phase: public
451 451
452 452 $ hg merge 1
453 453 merging b and a to b
454 454 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
455 455 (branch merge, don't forget to commit)
456 456 $ hg ci -m merge
457 457 $ ls
458 458 b
459 459 $ cd ..
460 460 $ rm -rf server
461 461 $ rm -rf repo
462 462
463 463 Copy and move file
464 464 $ hg init server
465 465 $ initclient server
466 466 $ cd server
467 467 $ echo a > a
468 468 $ hg add a
469 469 $ hg ci -m initial
470 470 $ hg cp a c
471 471 $ hg mv a b
472 472 $ hg ci -m 'cp a c, mv a b'
473 473 $ cd ..
474 474 $ hg clone -q server repo
475 475 $ initclient repo
476 476 $ cd repo
477 477 $ hg up -q 0
478 478 $ echo b > a
479 479 $ hg ci -m 'mod a'
480 480 created new head
481 481
482 482 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
483 483 @ changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
484 484 | desc: mod a, phase: draft
485 485 | o changeset: 4fc3fd13fbdb89ada6b75bfcef3911a689a0dde8
486 486 |/ desc: cp a c, mv a b, phase: public
487 487 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
488 488 desc: initial, phase: public
489 489
490 490 $ hg rebase -s . -d 1
491 491 rebasing 2:ef716627c70b "mod a" (tip)
492 492 merging b and a to b
493 493 merging c and a to c
494 494 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
495 495 $ ls
496 496 b
497 497 c
498 498 $ cat b
499 499 b
500 500 $ cat c
501 501 b
502 502 $ cd ..
503 503 $ rm -rf server
504 504 $ rm -rf repo
505 505
506 506 Do a merge commit with many consequent moves in one branch
507 507 $ hg init server
508 508 $ initclient server
509 509 $ cd server
510 510 $ echo a > a
511 511 $ hg add a
512 512 $ hg ci -m initial
513 513 $ echo b > a
514 514 $ hg ci -qm 'mod a'
515 515 $ cd ..
516 516 $ hg clone -q server repo
517 517 $ initclient repo
518 518 $ cd repo
519 519 $ hg up -q ".^"
520 520 $ hg mv a b
521 521 $ hg ci -qm 'mv a b'
522 522 $ hg mv b c
523 523 $ hg ci -qm 'mv b c'
524 524 $ hg up -q 1
525 525 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
526 526 o changeset: d3efd280421d24f9f229997c19e654761c942a71
527 527 | desc: mv b c, phase: draft
528 528 o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
529 529 | desc: mv a b, phase: draft
530 530 | @ changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
531 531 |/ desc: mod a, phase: public
532 532 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
533 533 desc: initial, phase: public
534 534
535 535 $ hg merge 3
536 536 merging a and c to c
537 537 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
538 538 (branch merge, don't forget to commit)
539 539 $ hg ci -qm 'merge'
540 540 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
541 541 @ changeset: cd29b0d08c0f39bfed4cde1b40e30f419db0c825
542 542 |\ desc: merge, phase: draft
543 543 | o changeset: d3efd280421d24f9f229997c19e654761c942a71
544 544 | | desc: mv b c, phase: draft
545 545 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
546 546 | | desc: mv a b, phase: draft
547 547 o | changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
548 548 |/ desc: mod a, phase: public
549 549 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
550 550 desc: initial, phase: public
551 551 $ ls
552 552 c
553 553 $ cd ..
554 554 $ rm -rf server
555 555 $ rm -rf repo
556 556
557 557 Test shelve/unshelve
558 558 $ hg init server
559 559 $ initclient server
560 560 $ cd server
561 561 $ echo a > a
562 562 $ hg add a
563 563 $ hg ci -m initial
564 564 $ cd ..
565 565 $ hg clone -q server repo
566 566 $ initclient repo
567 567 $ cd repo
568 568 $ echo b > a
569 569 $ hg shelve
570 570 shelved as default
571 571 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
572 572 $ hg mv a b
573 573 $ hg ci -m 'mv a b'
574 574
575 575 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
576 576 @ changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
577 577 | desc: mv a b, phase: draft
578 578 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
579 579 desc: initial, phase: public
580 580 $ hg unshelve
581 581 unshelving change 'default'
582 582 rebasing shelved changes
583 583 rebasing 2:45f63161acea "changes to: initial" (tip)
584 584 merging b and a to b
585 585 $ ls
586 586 b
587 587 $ cat b
588 588 b
589 589 $ cd ..
590 590 $ rm -rf server
591 591 $ rm -rf repo
592 592
593 593 Test full copytrace ability on draft branch
594 594 -------------------------------------------
595 595
596 596 File directory and base name changed in same move
597 597 $ hg init repo
598 598 $ initclient repo
599 599 $ mkdir repo/dir1
600 600 $ cd repo/dir1
601 601 $ echo a > a
602 602 $ hg add a
603 603 $ hg ci -qm initial
604 604 $ cd ..
605 605 $ hg mv -q dir1 dir2
606 606 $ hg mv dir2/a dir2/b
607 607 $ hg ci -qm 'mv a b; mv dir1 dir2'
608 608 $ hg up -q '.^'
609 609 $ cd dir1
610 610 $ echo b >> a
611 611 $ cd ..
612 612 $ hg ci -qm 'mod a'
613 613
614 614 $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
615 615 @ changeset 6207d2d318e710b882e3d5ada2a89770efc42c96
616 616 | desc mod a, phase: draft
617 617 | o changeset abffdd4e3dfc04bc375034b970299b2a309a1cce
618 618 |/ desc mv a b; mv dir1 dir2, phase: draft
619 619 o changeset 81973cd24b58db2fdf18ce3d64fb2cc3284e9ab3
620 620 desc initial, phase: draft
621 621
622 622 $ hg rebase -s . -d 1
623 623 rebasing 2:6207d2d318e7 "mod a" (tip)
624 624 merging dir2/b and dir1/a to dir2/b
625 625 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg (glob)
626 626 $ cat dir2/b
627 627 a
628 628 b
629 629 $ cd ..
630 630 $ rm -rf server
631 631 $ rm -rf repo
632 632
633 633 Move directory in one merge parent, while adding file to original directory
634 634 in other merge parent. File moved on rebase.
635 635 $ hg init repo
636 636 $ initclient repo
637 637 $ mkdir repo/dir1
638 638 $ cd repo/dir1
639 639 $ echo dummy > dummy
640 640 $ hg add dummy
641 641 $ cd ..
642 642 $ hg ci -qm initial
643 643 $ cd dir1
644 644 $ echo a > a
645 645 $ hg add a
646 646 $ cd ..
647 647 $ hg ci -qm 'hg add dir1/a'
648 648 $ hg up -q '.^'
649 649 $ hg mv -q dir1 dir2
650 650 $ hg ci -qm 'mv dir1 dir2'
651 651
652 652 $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
653 653 @ changeset e8919e7df8d036e07b906045eddcd4a42ff1915f
654 654 | desc mv dir1 dir2, phase: draft
655 655 | o changeset 7c7c6f339be00f849c3cb2df738ca91db78b32c8
656 656 |/ desc hg add dir1/a, phase: draft
657 657 o changeset a235dcce55dcf42034c4e374cb200662d0bb4a13
658 658 desc initial, phase: draft
659 659
660 660 $ hg rebase -s . -d 1
661 661 rebasing 2:e8919e7df8d0 "mv dir1 dir2" (tip)
662 662 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg (glob)
663 663 $ ls dir2
664 664 a
665 665 dummy
666 666 $ rm -rf server
667 667 $ rm -rf repo
668
669 Testing the sourcecommitlimit config
670
671 $ hg init repo
672 $ initclient repo
673 $ cd repo
674 $ echo a > a
675 $ hg ci -Aqm "added a"
676 $ echo "more things" >> a
677 $ hg ci -qm "added more things to a"
678 $ hg up 0
679 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
680 $ echo b > b
681 $ hg ci -Aqm "added b"
682 $ mkdir foo
683 $ hg mv a foo/bar
684 $ hg ci -m "Moved a to foo/bar"
685 $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
686 @ changeset b4b0f7880e500b5c364a5f07b4a2b167de7a6fb0
687 | desc Moved a to foo/bar, phase: draft
688 o changeset 5f6d8a4bf34ab274ccc9f631c2536964b8a3666d
689 | desc added b, phase: draft
690 | o changeset 8b6e13696c38e8445a759516474640c2f8dddef6
691 |/ desc added more things to a, phase: draft
692 o changeset 9092f1db7931481f93b37d5c9fbcfc341bcd7318
693 desc added a, phase: draft
694
695 When the sourcecommitlimit is small and we have more drafts, we use heuristics only
696
697 $ hg rebase -s 8b6e13696 -d . --config experimental.copytrace.sourcecommitlimit=0
698 rebasing 1:8b6e13696c38 "added more things to a"
699 other [source] changed a which local [dest] deleted
700 use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
701 unresolved conflicts (see hg resolve, then hg rebase --continue)
702 [1]
703
704 But when we have "sourcecommitlimit > (no. of drafts from base to c1)", we do
705 fullcopytracing
706
707 $ hg rebase --abort
708 rebase aborted
709 $ hg rebase -s 8b6e13696 -d .
710 rebasing 1:8b6e13696c38 "added more things to a"
711 merging foo/bar and a to foo/bar
712 saved backup bundle to $TESTTMP/repo/repo/repo/.hg/strip-backup/8b6e13696c38-fc14ac83-rebase.hg (glob)
713 $ cd ..
714 $ rm -rf repo
General Comments 0
You need to be logged in to leave comments. Login now