##// END OF EJS Templates
copies: properly match result during changeset centric copy tracing...
marmoute -
r46774:92905484 default
parent child Browse files
Show More
@@ -1,1221 +1,1217 b''
1 1 # coding: utf8
2 2 # copies.py - copy detection for Mercurial
3 3 #
4 4 # Copyright 2008 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import collections
12 12 import os
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 nullid,
17 17 nullrev,
18 18 )
19 19
20 20 from . import (
21 21 match as matchmod,
22 22 pathutil,
23 23 policy,
24 24 pycompat,
25 25 util,
26 26 )
27 27
28 28
29 29 from .utils import stringutil
30 30
31 31 from .revlogutils import (
32 32 flagutil,
33 33 sidedata as sidedatamod,
34 34 )
35 35
36 36 rustmod = policy.importrust("copy_tracing")
37 37
38 38
39 39 def _filter(src, dst, t):
40 40 """filters out invalid copies after chaining"""
41 41
42 42 # When _chain()'ing copies in 'a' (from 'src' via some other commit 'mid')
43 43 # with copies in 'b' (from 'mid' to 'dst'), we can get the different cases
44 44 # in the following table (not including trivial cases). For example, case 2
45 45 # is where a file existed in 'src' and remained under that name in 'mid' and
46 46 # then was renamed between 'mid' and 'dst'.
47 47 #
48 48 # case src mid dst result
49 49 # 1 x y - -
50 50 # 2 x y y x->y
51 51 # 3 x y x -
52 52 # 4 x y z x->z
53 53 # 5 - x y -
54 54 # 6 x x y x->y
55 55 #
56 56 # _chain() takes care of chaining the copies in 'a' and 'b', but it
57 57 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
58 58 # between 5 and 6, so it includes all cases in its result.
59 59 # Cases 1, 3, and 5 are then removed by _filter().
60 60
61 61 for k, v in list(t.items()):
62 62 # remove copies from files that didn't exist
63 63 if v not in src:
64 64 del t[k]
65 65 # remove criss-crossed copies
66 66 elif k in src and v in dst:
67 67 del t[k]
68 68 # remove copies to files that were then removed
69 69 elif k not in dst:
70 70 del t[k]
71 71
72 72
73 73 def _chain(prefix, suffix):
74 74 """chain two sets of copies 'prefix' and 'suffix'"""
75 75 result = prefix.copy()
76 76 for key, value in pycompat.iteritems(suffix):
77 77 result[key] = prefix.get(value, value)
78 78 return result
79 79
80 80
81 81 def _tracefile(fctx, am, basemf):
82 82 """return file context that is the ancestor of fctx present in ancestor
83 83 manifest am
84 84
85 85 Note: we used to try and stop after a given limit, however checking if that
86 86 limit is reached turned out to be very expensive. we are better off
87 87 disabling that feature."""
88 88
89 89 for f in fctx.ancestors():
90 90 path = f.path()
91 91 if am.get(path, None) == f.filenode():
92 92 return path
93 93 if basemf and basemf.get(path, None) == f.filenode():
94 94 return path
95 95
96 96
97 97 def _dirstatecopies(repo, match=None):
98 98 ds = repo.dirstate
99 99 c = ds.copies().copy()
100 100 for k in list(c):
101 101 if ds[k] not in b'anm' or (match and not match(k)):
102 102 del c[k]
103 103 return c
104 104
105 105
106 106 def _computeforwardmissing(a, b, match=None):
107 107 """Computes which files are in b but not a.
108 108 This is its own function so extensions can easily wrap this call to see what
109 109 files _forwardcopies is about to process.
110 110 """
111 111 ma = a.manifest()
112 112 mb = b.manifest()
113 113 return mb.filesnotin(ma, match=match)
114 114
115 115
116 116 def usechangesetcentricalgo(repo):
117 117 """Checks if we should use changeset-centric copy algorithms"""
118 118 if repo.filecopiesmode == b'changeset-sidedata':
119 119 return True
120 120 readfrom = repo.ui.config(b'experimental', b'copies.read-from')
121 121 changesetsource = (b'changeset-only', b'compatibility')
122 122 return readfrom in changesetsource
123 123
124 124
125 125 def _committedforwardcopies(a, b, base, match):
126 126 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
127 127 # files might have to be traced back to the fctx parent of the last
128 128 # one-side-only changeset, but not further back than that
129 129 repo = a._repo
130 130
131 131 if usechangesetcentricalgo(repo):
132 132 return _changesetforwardcopies(a, b, match)
133 133
134 134 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
135 135 dbg = repo.ui.debug
136 136 if debug:
137 137 dbg(b'debug.copies: looking into rename from %s to %s\n' % (a, b))
138 138 am = a.manifest()
139 139 basemf = None if base is None else base.manifest()
140 140
141 141 # find where new files came from
142 142 # we currently don't try to find where old files went, too expensive
143 143 # this means we can miss a case like 'hg rm b; hg cp a b'
144 144 cm = {}
145 145
146 146 # Computing the forward missing is quite expensive on large manifests, since
147 147 # it compares the entire manifests. We can optimize it in the common use
148 148 # case of computing what copies are in a commit versus its parent (like
149 149 # during a rebase or histedit). Note, we exclude merge commits from this
150 150 # optimization, since the ctx.files() for a merge commit is not correct for
151 151 # this comparison.
152 152 forwardmissingmatch = match
153 153 if b.p1() == a and b.p2().node() == nullid:
154 154 filesmatcher = matchmod.exact(b.files())
155 155 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
156 156 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
157 157
158 158 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
159 159
160 160 if debug:
161 161 dbg(b'debug.copies: missing files to search: %d\n' % len(missing))
162 162
163 163 for f in sorted(missing):
164 164 if debug:
165 165 dbg(b'debug.copies: tracing file: %s\n' % f)
166 166 fctx = b[f]
167 167 fctx._ancestrycontext = ancestrycontext
168 168
169 169 if debug:
170 170 start = util.timer()
171 171 opath = _tracefile(fctx, am, basemf)
172 172 if opath:
173 173 if debug:
174 174 dbg(b'debug.copies: rename of: %s\n' % opath)
175 175 cm[f] = opath
176 176 if debug:
177 177 dbg(
178 178 b'debug.copies: time: %f seconds\n'
179 179 % (util.timer() - start)
180 180 )
181 181 return cm
182 182
183 183
184 184 def _revinfo_getter(repo, match):
185 185 """returns a function that returns the following data given a <rev>"
186 186
187 187 * p1: revision number of first parent
188 188 * p2: revision number of first parent
189 189 * changes: a ChangingFiles object
190 190 """
191 191 cl = repo.changelog
192 192 parents = cl.parentrevs
193 193 flags = cl.flags
194 194
195 195 HASCOPIESINFO = flagutil.REVIDX_HASCOPIESINFO
196 196
197 197 changelogrevision = cl.changelogrevision
198 198
199 alwaysmatch = match.always()
200
201 if rustmod is not None and alwaysmatch:
199 if rustmod is not None:
202 200
203 201 def revinfo(rev):
204 202 p1, p2 = parents(rev)
205 203 if flags(rev) & HASCOPIESINFO:
206 204 raw = changelogrevision(rev)._sidedata.get(sidedatamod.SD_FILES)
207 205 else:
208 206 raw = None
209 207 return (p1, p2, raw)
210 208
211 209 else:
212 210
213 211 def revinfo(rev):
214 212 p1, p2 = parents(rev)
215 213 if flags(rev) & HASCOPIESINFO:
216 214 changes = changelogrevision(rev).changes
217 215 else:
218 216 changes = None
219 217 return (p1, p2, changes)
220 218
221 219 return revinfo
222 220
223 221
224 222 def cached_is_ancestor(is_ancestor):
225 223 """return a cached version of is_ancestor"""
226 224 cache = {}
227 225
228 226 def _is_ancestor(anc, desc):
229 227 if anc > desc:
230 228 return False
231 229 elif anc == desc:
232 230 return True
233 231 key = (anc, desc)
234 232 ret = cache.get(key)
235 233 if ret is None:
236 234 ret = cache[key] = is_ancestor(anc, desc)
237 235 return ret
238 236
239 237 return _is_ancestor
240 238
241 239
242 240 def _changesetforwardcopies(a, b, match):
243 241 if a.rev() in (nullrev, b.rev()):
244 242 return {}
245 243
246 244 repo = a.repo().unfiltered()
247 245 children = {}
248 246
249 247 cl = repo.changelog
250 248 isancestor = cl.isancestorrev
251 249
252 250 # To track rename from "A" to B, we need to gather all parent β†’ children
253 251 # edges that are contains in `::B` but not in `::A`.
254 252 #
255 253 #
256 254 # To do so, we need to gather all revisions exclusiveΒΉ to "B" (ieΒΉ: `::b -
257 255 # ::a`) and also all the "roots point", ie the parents of the exclusive set
258 256 # that belong to ::a. These are exactly all the revisions needed to express
259 257 # the parent β†’ children we need to combine.
260 258 #
261 259 # [1] actually, we need to gather all the edges within `(::a)::b`, ie:
262 260 # excluding paths that leads to roots that are not ancestors of `a`. We
263 261 # keep this out of the explanation because it is hard enough without this special case..
264 262
265 263 parents = cl._uncheckedparentrevs
266 264 graph_roots = (nullrev, nullrev)
267 265
268 266 ancestors = cl.ancestors([a.rev()], inclusive=True)
269 267 revs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
270 268 roots = set()
271 269 has_graph_roots = False
272 270
273 271 # iterate over `only(B, A)`
274 272 for r in revs:
275 273 ps = parents(r)
276 274 if ps == graph_roots:
277 275 has_graph_roots = True
278 276 else:
279 277 p1, p2 = ps
280 278
281 279 # find all the "root points" (see larger comment above)
282 280 if p1 != nullrev and p1 in ancestors:
283 281 roots.add(p1)
284 282 if p2 != nullrev and p2 in ancestors:
285 283 roots.add(p2)
286 284 if not roots:
287 285 # no common revision to track copies from
288 286 return {}
289 287 if has_graph_roots:
290 288 # this deal with the special case mentionned in the [1] footnotes. We
291 289 # must filter out revisions that leads to non-common graphroots.
292 290 roots = list(roots)
293 291 m = min(roots)
294 292 h = [b.rev()]
295 293 roots_to_head = cl.reachableroots(m, h, roots, includepath=True)
296 294 roots_to_head = set(roots_to_head)
297 295 revs = [r for r in revs if r in roots_to_head]
298 296
299 297 if repo.filecopiesmode == b'changeset-sidedata':
300 298 # When using side-data, we will process the edges "from" the children.
301 299 # We iterate over the childre, gathering previous collected data for
302 300 # the parents. Do know when the parents data is no longer necessary, we
303 301 # keep a counter of how many children each revision has.
304 302 #
305 303 # An interresting property of `children_count` is that it only contains
306 304 # revision that will be relevant for a edge of the graph. So if a
307 305 # children has parent not in `children_count`, that edges should not be
308 306 # processed.
309 307 children_count = dict((r, 0) for r in roots)
310 308 for r in revs:
311 309 for p in cl.parentrevs(r):
312 310 if p == nullrev:
313 311 continue
314 312 children_count[r] = 0
315 313 if p in children_count:
316 314 children_count[p] += 1
317 315 revinfo = _revinfo_getter(repo, match)
318 316 return _combine_changeset_copies(
319 317 revs, children_count, b.rev(), revinfo, match, isancestor
320 318 )
321 319 else:
322 320 # When not using side-data, we will process the edges "from" the parent.
323 321 # so we need a full mapping of the parent -> children relation.
324 322 children = dict((r, []) for r in roots)
325 323 for r in revs:
326 324 for p in cl.parentrevs(r):
327 325 if p == nullrev:
328 326 continue
329 327 children[r] = []
330 328 if p in children:
331 329 children[p].append(r)
332 330 x = revs.pop()
333 331 assert x == b.rev()
334 332 revs.extend(roots)
335 333 revs.sort()
336 334
337 335 revinfo = _revinfo_getter_extra(repo)
338 336 return _combine_changeset_copies_extra(
339 337 revs, children, b.rev(), revinfo, match, isancestor
340 338 )
341 339
342 340
343 341 def _combine_changeset_copies(
344 342 revs, children_count, targetrev, revinfo, match, isancestor
345 343 ):
346 344 """combine the copies information for each item of iterrevs
347 345
348 346 revs: sorted iterable of revision to visit
349 347 children_count: a {parent: <number-of-relevant-children>} mapping.
350 348 targetrev: the final copies destination revision (not in iterrevs)
351 349 revinfo(rev): a function that return (p1, p2, p1copies, p2copies, removed)
352 350 match: a matcher
353 351
354 352 It returns the aggregated copies information for `targetrev`.
355 353 """
356 354
357 355 alwaysmatch = match.always()
358 356
359 if rustmod is not None and alwaysmatch:
357 if rustmod is not None:
360 358 final_copies = rustmod.combine_changeset_copies(
361 359 list(revs), children_count, targetrev, revinfo, isancestor
362 360 )
363 361 else:
364 362 isancestor = cached_is_ancestor(isancestor)
365 363
366 364 all_copies = {}
367 365 # iterate over all the "children" side of copy tracing "edge"
368 366 for current_rev in revs:
369 367 p1, p2, changes = revinfo(current_rev)
370 368 current_copies = None
371 369 # iterate over all parents to chain the existing data with the
372 370 # data from the parent β†’ child edge.
373 371 for parent, parent_rev in ((1, p1), (2, p2)):
374 372 if parent_rev == nullrev:
375 373 continue
376 374 remaining_children = children_count.get(parent_rev)
377 375 if remaining_children is None:
378 376 continue
379 377 remaining_children -= 1
380 378 children_count[parent_rev] = remaining_children
381 379 if remaining_children:
382 380 copies = all_copies.get(parent_rev, None)
383 381 else:
384 382 copies = all_copies.pop(parent_rev, None)
385 383
386 384 if copies is None:
387 385 # this is a root
388 386 copies = {}
389 387
390 388 newcopies = copies
391 389 # chain the data in the edge with the existing data
392 390 if changes is not None:
393 391 childcopies = {}
394 392 if parent == 1:
395 393 childcopies = changes.copied_from_p1
396 394 elif parent == 2:
397 395 childcopies = changes.copied_from_p2
398 396
399 if not alwaysmatch:
400 childcopies = {
401 dst: src
402 for dst, src in childcopies.items()
403 if match(dst)
404 }
405 397 if childcopies:
406 398 newcopies = copies.copy()
407 399 for dest, source in pycompat.iteritems(childcopies):
408 400 prev = copies.get(source)
409 401 if prev is not None and prev[1] is not None:
410 402 source = prev[1]
411 403 newcopies[dest] = (current_rev, source)
412 404 assert newcopies is not copies
413 405 if changes.removed:
414 406 if newcopies is copies:
415 407 newcopies = copies.copy()
416 408 for f in changes.removed:
417 409 if f in newcopies:
418 410 if newcopies is copies:
419 411 # copy on write to avoid affecting potential other
420 412 # branches. when there are no other branches, this
421 413 # could be avoided.
422 414 newcopies = copies.copy()
423 415 newcopies[f] = (current_rev, None)
424 416 # check potential need to combine the data from another parent (for
425 417 # that child). See comment below for details.
426 418 if current_copies is None:
427 419 current_copies = newcopies
428 420 elif current_copies is newcopies:
429 421 # nothing to merge:
430 422 pass
431 423 else:
432 424 # we are the second parent to work on c, we need to merge our
433 425 # work with the other.
434 426 #
435 427 # In case of conflict, parent 1 take precedence over parent 2.
436 428 # This is an arbitrary choice made anew when implementing
437 429 # changeset based copies. It was made without regards with
438 430 # potential filelog related behavior.
439 431 assert parent == 2
440 432 current_copies = _merge_copies_dict(
441 433 newcopies, current_copies, isancestor, changes
442 434 )
443 435 all_copies[current_rev] = current_copies
444 436
445 437 # filter out internal details and return a {dest: source mapping}
446 438 final_copies = {}
447 439 for dest, (tt, source) in all_copies[targetrev].items():
448 440 if source is not None:
449 441 final_copies[dest] = source
442 if not alwaysmatch:
443 for filename in list(final_copies.keys()):
444 if not match(filename):
445 del final_copies[filename]
450 446 return final_copies
451 447
452 448
453 449 def _merge_copies_dict(minor, major, isancestor, changes):
454 450 """merge two copies-mapping together, minor and major
455 451
456 452 In case of conflict, value from "major" will be picked.
457 453
458 454 - `isancestors(low_rev, high_rev)`: callable return True if `low_rev` is an
459 455 ancestors of `high_rev`,
460 456
461 457 - `ismerged(path)`: callable return True if `path` have been merged in the
462 458 current revision,
463 459
464 460 return the resulting dict (in practice, the "minor" object, updated)
465 461 """
466 462 for dest, value in major.items():
467 463 other = minor.get(dest)
468 464 if other is None:
469 465 minor[dest] = value
470 466 else:
471 467 new_tt = value[0]
472 468 other_tt = other[0]
473 469 if value[1] == other[1]:
474 470 continue
475 471 # content from "major" wins, unless it is older
476 472 # than the branch point or there is a merge
477 473 if new_tt == other_tt:
478 474 minor[dest] = value
479 475 elif (
480 476 changes is not None
481 477 and value[1] is None
482 478 and dest in changes.salvaged
483 479 ):
484 480 pass
485 481 elif (
486 482 changes is not None
487 483 and other[1] is None
488 484 and dest in changes.salvaged
489 485 ):
490 486 minor[dest] = value
491 487 elif changes is not None and dest in changes.merged:
492 488 minor[dest] = value
493 489 elif not isancestor(new_tt, other_tt):
494 490 if value[1] is not None:
495 491 minor[dest] = value
496 492 elif isancestor(other_tt, new_tt):
497 493 minor[dest] = value
498 494 return minor
499 495
500 496
501 497 def _revinfo_getter_extra(repo):
502 498 """return a function that return multiple data given a <rev>"i
503 499
504 500 * p1: revision number of first parent
505 501 * p2: revision number of first parent
506 502 * p1copies: mapping of copies from p1
507 503 * p2copies: mapping of copies from p2
508 504 * removed: a list of removed files
509 505 * ismerged: a callback to know if file was merged in that revision
510 506 """
511 507 cl = repo.changelog
512 508 parents = cl.parentrevs
513 509
514 510 def get_ismerged(rev):
515 511 ctx = repo[rev]
516 512
517 513 def ismerged(path):
518 514 if path not in ctx.files():
519 515 return False
520 516 fctx = ctx[path]
521 517 parents = fctx._filelog.parents(fctx._filenode)
522 518 nb_parents = 0
523 519 for n in parents:
524 520 if n != nullid:
525 521 nb_parents += 1
526 522 return nb_parents >= 2
527 523
528 524 return ismerged
529 525
530 526 def revinfo(rev):
531 527 p1, p2 = parents(rev)
532 528 ctx = repo[rev]
533 529 p1copies, p2copies = ctx._copies
534 530 removed = ctx.filesremoved()
535 531 return p1, p2, p1copies, p2copies, removed, get_ismerged(rev)
536 532
537 533 return revinfo
538 534
539 535
540 536 def _combine_changeset_copies_extra(
541 537 revs, children, targetrev, revinfo, match, isancestor
542 538 ):
543 539 """version of `_combine_changeset_copies` that works with the Google
544 540 specific "extra" based storage for copy information"""
545 541 all_copies = {}
546 542 alwaysmatch = match.always()
547 543 for r in revs:
548 544 copies = all_copies.pop(r, None)
549 545 if copies is None:
550 546 # this is a root
551 547 copies = {}
552 548 for i, c in enumerate(children[r]):
553 549 p1, p2, p1copies, p2copies, removed, ismerged = revinfo(c)
554 550 if r == p1:
555 551 parent = 1
556 552 childcopies = p1copies
557 553 else:
558 554 assert r == p2
559 555 parent = 2
560 556 childcopies = p2copies
561 557 if not alwaysmatch:
562 558 childcopies = {
563 559 dst: src for dst, src in childcopies.items() if match(dst)
564 560 }
565 561 newcopies = copies
566 562 if childcopies:
567 563 newcopies = copies.copy()
568 564 for dest, source in pycompat.iteritems(childcopies):
569 565 prev = copies.get(source)
570 566 if prev is not None and prev[1] is not None:
571 567 source = prev[1]
572 568 newcopies[dest] = (c, source)
573 569 assert newcopies is not copies
574 570 for f in removed:
575 571 if f in newcopies:
576 572 if newcopies is copies:
577 573 # copy on write to avoid affecting potential other
578 574 # branches. when there are no other branches, this
579 575 # could be avoided.
580 576 newcopies = copies.copy()
581 577 newcopies[f] = (c, None)
582 578 othercopies = all_copies.get(c)
583 579 if othercopies is None:
584 580 all_copies[c] = newcopies
585 581 else:
586 582 # we are the second parent to work on c, we need to merge our
587 583 # work with the other.
588 584 #
589 585 # In case of conflict, parent 1 take precedence over parent 2.
590 586 # This is an arbitrary choice made anew when implementing
591 587 # changeset based copies. It was made without regards with
592 588 # potential filelog related behavior.
593 589 if parent == 1:
594 590 _merge_copies_dict_extra(
595 591 othercopies, newcopies, isancestor, ismerged
596 592 )
597 593 else:
598 594 _merge_copies_dict_extra(
599 595 newcopies, othercopies, isancestor, ismerged
600 596 )
601 597 all_copies[c] = newcopies
602 598
603 599 final_copies = {}
604 600 for dest, (tt, source) in all_copies[targetrev].items():
605 601 if source is not None:
606 602 final_copies[dest] = source
607 603 return final_copies
608 604
609 605
610 606 def _merge_copies_dict_extra(minor, major, isancestor, ismerged):
611 607 """version of `_merge_copies_dict` that works with the Google
612 608 specific "extra" based storage for copy information"""
613 609 for dest, value in major.items():
614 610 other = minor.get(dest)
615 611 if other is None:
616 612 minor[dest] = value
617 613 else:
618 614 new_tt = value[0]
619 615 other_tt = other[0]
620 616 if value[1] == other[1]:
621 617 continue
622 618 # content from "major" wins, unless it is older
623 619 # than the branch point or there is a merge
624 620 if (
625 621 new_tt == other_tt
626 622 or not isancestor(new_tt, other_tt)
627 623 or ismerged(dest)
628 624 ):
629 625 minor[dest] = value
630 626
631 627
632 628 def _forwardcopies(a, b, base=None, match=None):
633 629 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
634 630
635 631 if base is None:
636 632 base = a
637 633 match = a.repo().narrowmatch(match)
638 634 # check for working copy
639 635 if b.rev() is None:
640 636 cm = _committedforwardcopies(a, b.p1(), base, match)
641 637 # combine copies from dirstate if necessary
642 638 copies = _chain(cm, _dirstatecopies(b._repo, match))
643 639 else:
644 640 copies = _committedforwardcopies(a, b, base, match)
645 641 return copies
646 642
647 643
648 644 def _backwardrenames(a, b, match):
649 645 if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
650 646 return {}
651 647
652 648 # Even though we're not taking copies into account, 1:n rename situations
653 649 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
654 650 # arbitrarily pick one of the renames.
655 651 # We don't want to pass in "match" here, since that would filter
656 652 # the destination by it. Since we're reversing the copies, we want
657 653 # to filter the source instead.
658 654 f = _forwardcopies(b, a)
659 655 r = {}
660 656 for k, v in sorted(pycompat.iteritems(f)):
661 657 if match and not match(v):
662 658 continue
663 659 # remove copies
664 660 if v in a:
665 661 continue
666 662 r[v] = k
667 663 return r
668 664
669 665
670 666 def pathcopies(x, y, match=None):
671 667 """find {dst@y: src@x} copy mapping for directed compare"""
672 668 repo = x._repo
673 669 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
674 670 if debug:
675 671 repo.ui.debug(
676 672 b'debug.copies: searching copies from %s to %s\n' % (x, y)
677 673 )
678 674 if x == y or not x or not y:
679 675 return {}
680 676 if y.rev() is None and x == y.p1():
681 677 if debug:
682 678 repo.ui.debug(b'debug.copies: search mode: dirstate\n')
683 679 # short-circuit to avoid issues with merge states
684 680 return _dirstatecopies(repo, match)
685 681 a = y.ancestor(x)
686 682 if a == x:
687 683 if debug:
688 684 repo.ui.debug(b'debug.copies: search mode: forward\n')
689 685 copies = _forwardcopies(x, y, match=match)
690 686 elif a == y:
691 687 if debug:
692 688 repo.ui.debug(b'debug.copies: search mode: backward\n')
693 689 copies = _backwardrenames(x, y, match=match)
694 690 else:
695 691 if debug:
696 692 repo.ui.debug(b'debug.copies: search mode: combined\n')
697 693 base = None
698 694 if a.rev() != nullrev:
699 695 base = x
700 696 copies = _chain(
701 697 _backwardrenames(x, a, match=match),
702 698 _forwardcopies(a, y, base, match=match),
703 699 )
704 700 _filter(x, y, copies)
705 701 return copies
706 702
707 703
708 704 def mergecopies(repo, c1, c2, base):
709 705 """
710 706 Finds moves and copies between context c1 and c2 that are relevant for
711 707 merging. 'base' will be used as the merge base.
712 708
713 709 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
714 710 files that were moved/ copied in one merge parent and modified in another.
715 711 For example:
716 712
717 713 o ---> 4 another commit
718 714 |
719 715 | o ---> 3 commit that modifies a.txt
720 716 | /
721 717 o / ---> 2 commit that moves a.txt to b.txt
722 718 |/
723 719 o ---> 1 merge base
724 720
725 721 If we try to rebase revision 3 on revision 4, since there is no a.txt in
726 722 revision 4, and if user have copytrace disabled, we prints the following
727 723 message:
728 724
729 725 ```other changed <file> which local deleted```
730 726
731 727 Returns a tuple where:
732 728
733 729 "branch_copies" an instance of branch_copies.
734 730
735 731 "diverge" is a mapping of source name -> list of destination names
736 732 for divergent renames.
737 733
738 734 This function calls different copytracing algorithms based on config.
739 735 """
740 736 # avoid silly behavior for update from empty dir
741 737 if not c1 or not c2 or c1 == c2:
742 738 return branch_copies(), branch_copies(), {}
743 739
744 740 narrowmatch = c1.repo().narrowmatch()
745 741
746 742 # avoid silly behavior for parent -> working dir
747 743 if c2.node() is None and c1.node() == repo.dirstate.p1():
748 744 return (
749 745 branch_copies(_dirstatecopies(repo, narrowmatch)),
750 746 branch_copies(),
751 747 {},
752 748 )
753 749
754 750 copytracing = repo.ui.config(b'experimental', b'copytrace')
755 751 if stringutil.parsebool(copytracing) is False:
756 752 # stringutil.parsebool() returns None when it is unable to parse the
757 753 # value, so we should rely on making sure copytracing is on such cases
758 754 return branch_copies(), branch_copies(), {}
759 755
760 756 if usechangesetcentricalgo(repo):
761 757 # The heuristics don't make sense when we need changeset-centric algos
762 758 return _fullcopytracing(repo, c1, c2, base)
763 759
764 760 # Copy trace disabling is explicitly below the node == p1 logic above
765 761 # because the logic above is required for a simple copy to be kept across a
766 762 # rebase.
767 763 if copytracing == b'heuristics':
768 764 # Do full copytracing if only non-public revisions are involved as
769 765 # that will be fast enough and will also cover the copies which could
770 766 # be missed by heuristics
771 767 if _isfullcopytraceable(repo, c1, base):
772 768 return _fullcopytracing(repo, c1, c2, base)
773 769 return _heuristicscopytracing(repo, c1, c2, base)
774 770 else:
775 771 return _fullcopytracing(repo, c1, c2, base)
776 772
777 773
778 774 def _isfullcopytraceable(repo, c1, base):
779 775 """Checks that if base, source and destination are all no-public branches,
780 776 if yes let's use the full copytrace algorithm for increased capabilities
781 777 since it will be fast enough.
782 778
783 779 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
784 780 number of changesets from c1 to base such that if number of changesets are
785 781 more than the limit, full copytracing algorithm won't be used.
786 782 """
787 783 if c1.rev() is None:
788 784 c1 = c1.p1()
789 785 if c1.mutable() and base.mutable():
790 786 sourcecommitlimit = repo.ui.configint(
791 787 b'experimental', b'copytrace.sourcecommitlimit'
792 788 )
793 789 commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
794 790 return commits < sourcecommitlimit
795 791 return False
796 792
797 793
798 794 def _checksinglesidecopies(
799 795 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
800 796 ):
801 797 if src not in m2:
802 798 # deleted on side 2
803 799 if src not in m1:
804 800 # renamed on side 1, deleted on side 2
805 801 renamedelete[src] = dsts1
806 802 elif src not in mb:
807 803 # Work around the "short-circuit to avoid issues with merge states"
808 804 # thing in pathcopies(): pathcopies(x, y) can return a copy where the
809 805 # destination doesn't exist in y.
810 806 pass
811 807 elif mb[src] != m2[src] and not _related(c2[src], base[src]):
812 808 return
813 809 elif mb[src] != m2[src] or mb.flags(src) != m2.flags(src):
814 810 # modified on side 2
815 811 for dst in dsts1:
816 812 copy[dst] = src
817 813
818 814
819 815 class branch_copies(object):
820 816 """Information about copies made on one side of a merge/graft.
821 817
822 818 "copy" is a mapping from destination name -> source name,
823 819 where source is in c1 and destination is in c2 or vice-versa.
824 820
825 821 "movewithdir" is a mapping from source name -> destination name,
826 822 where the file at source present in one context but not the other
827 823 needs to be moved to destination by the merge process, because the
828 824 other context moved the directory it is in.
829 825
830 826 "renamedelete" is a mapping of source name -> list of destination
831 827 names for files deleted in c1 that were renamed in c2 or vice-versa.
832 828
833 829 "dirmove" is a mapping of detected source dir -> destination dir renames.
834 830 This is needed for handling changes to new files previously grafted into
835 831 renamed directories.
836 832 """
837 833
838 834 def __init__(
839 835 self, copy=None, renamedelete=None, dirmove=None, movewithdir=None
840 836 ):
841 837 self.copy = {} if copy is None else copy
842 838 self.renamedelete = {} if renamedelete is None else renamedelete
843 839 self.dirmove = {} if dirmove is None else dirmove
844 840 self.movewithdir = {} if movewithdir is None else movewithdir
845 841
846 842 def __repr__(self):
847 843 return '<branch_copies\n copy=%r\n renamedelete=%r\n dirmove=%r\n movewithdir=%r\n>' % (
848 844 self.copy,
849 845 self.renamedelete,
850 846 self.dirmove,
851 847 self.movewithdir,
852 848 )
853 849
854 850
855 851 def _fullcopytracing(repo, c1, c2, base):
856 852 """The full copytracing algorithm which finds all the new files that were
857 853 added from merge base up to the top commit and for each file it checks if
858 854 this file was copied from another file.
859 855
860 856 This is pretty slow when a lot of changesets are involved but will track all
861 857 the copies.
862 858 """
863 859 m1 = c1.manifest()
864 860 m2 = c2.manifest()
865 861 mb = base.manifest()
866 862
867 863 copies1 = pathcopies(base, c1)
868 864 copies2 = pathcopies(base, c2)
869 865
870 866 if not (copies1 or copies2):
871 867 return branch_copies(), branch_copies(), {}
872 868
873 869 inversecopies1 = {}
874 870 inversecopies2 = {}
875 871 for dst, src in copies1.items():
876 872 inversecopies1.setdefault(src, []).append(dst)
877 873 for dst, src in copies2.items():
878 874 inversecopies2.setdefault(src, []).append(dst)
879 875
880 876 copy1 = {}
881 877 copy2 = {}
882 878 diverge = {}
883 879 renamedelete1 = {}
884 880 renamedelete2 = {}
885 881 allsources = set(inversecopies1) | set(inversecopies2)
886 882 for src in allsources:
887 883 dsts1 = inversecopies1.get(src)
888 884 dsts2 = inversecopies2.get(src)
889 885 if dsts1 and dsts2:
890 886 # copied/renamed on both sides
891 887 if src not in m1 and src not in m2:
892 888 # renamed on both sides
893 889 dsts1 = set(dsts1)
894 890 dsts2 = set(dsts2)
895 891 # If there's some overlap in the rename destinations, we
896 892 # consider it not divergent. For example, if side 1 copies 'a'
897 893 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
898 894 # and 'd' and deletes 'a'.
899 895 if dsts1 & dsts2:
900 896 for dst in dsts1 & dsts2:
901 897 copy1[dst] = src
902 898 copy2[dst] = src
903 899 else:
904 900 diverge[src] = sorted(dsts1 | dsts2)
905 901 elif src in m1 and src in m2:
906 902 # copied on both sides
907 903 dsts1 = set(dsts1)
908 904 dsts2 = set(dsts2)
909 905 for dst in dsts1 & dsts2:
910 906 copy1[dst] = src
911 907 copy2[dst] = src
912 908 # TODO: Handle cases where it was renamed on one side and copied
913 909 # on the other side
914 910 elif dsts1:
915 911 # copied/renamed only on side 1
916 912 _checksinglesidecopies(
917 913 src, dsts1, m1, m2, mb, c2, base, copy1, renamedelete1
918 914 )
919 915 elif dsts2:
920 916 # copied/renamed only on side 2
921 917 _checksinglesidecopies(
922 918 src, dsts2, m2, m1, mb, c1, base, copy2, renamedelete2
923 919 )
924 920
925 921 # find interesting file sets from manifests
926 922 cache = []
927 923
928 924 def _get_addedfiles(idx):
929 925 if not cache:
930 926 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
931 927 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
932 928 u1 = sorted(addedinm1 - addedinm2)
933 929 u2 = sorted(addedinm2 - addedinm1)
934 930 cache.extend((u1, u2))
935 931 return cache[idx]
936 932
937 933 u1fn = lambda: _get_addedfiles(0)
938 934 u2fn = lambda: _get_addedfiles(1)
939 935 if repo.ui.debugflag:
940 936 u1 = u1fn()
941 937 u2 = u2fn()
942 938
943 939 header = b" unmatched files in %s"
944 940 if u1:
945 941 repo.ui.debug(
946 942 b"%s:\n %s\n" % (header % b'local', b"\n ".join(u1))
947 943 )
948 944 if u2:
949 945 repo.ui.debug(
950 946 b"%s:\n %s\n" % (header % b'other', b"\n ".join(u2))
951 947 )
952 948
953 949 renamedeleteset = set()
954 950 divergeset = set()
955 951 for dsts in diverge.values():
956 952 divergeset.update(dsts)
957 953 for dsts in renamedelete1.values():
958 954 renamedeleteset.update(dsts)
959 955 for dsts in renamedelete2.values():
960 956 renamedeleteset.update(dsts)
961 957
962 958 repo.ui.debug(
963 959 b" all copies found (* = to merge, ! = divergent, "
964 960 b"% = renamed and deleted):\n"
965 961 )
966 962 for side, copies in ((b"local", copies1), (b"remote", copies2)):
967 963 if not copies:
968 964 continue
969 965 repo.ui.debug(b" on %s side:\n" % side)
970 966 for f in sorted(copies):
971 967 note = b""
972 968 if f in copy1 or f in copy2:
973 969 note += b"*"
974 970 if f in divergeset:
975 971 note += b"!"
976 972 if f in renamedeleteset:
977 973 note += b"%"
978 974 repo.ui.debug(
979 975 b" src: '%s' -> dst: '%s' %s\n" % (copies[f], f, note)
980 976 )
981 977 del renamedeleteset
982 978 del divergeset
983 979
984 980 repo.ui.debug(b" checking for directory renames\n")
985 981
986 982 dirmove1, movewithdir2 = _dir_renames(repo, c1, copy1, copies1, u2fn)
987 983 dirmove2, movewithdir1 = _dir_renames(repo, c2, copy2, copies2, u1fn)
988 984
989 985 branch_copies1 = branch_copies(copy1, renamedelete1, dirmove1, movewithdir1)
990 986 branch_copies2 = branch_copies(copy2, renamedelete2, dirmove2, movewithdir2)
991 987
992 988 return branch_copies1, branch_copies2, diverge
993 989
994 990
995 991 def _dir_renames(repo, ctx, copy, fullcopy, addedfilesfn):
996 992 """Finds moved directories and files that should move with them.
997 993
998 994 ctx: the context for one of the sides
999 995 copy: files copied on the same side (as ctx)
1000 996 fullcopy: files copied on the same side (as ctx), including those that
1001 997 merge.manifestmerge() won't care about
1002 998 addedfilesfn: function returning added files on the other side (compared to
1003 999 ctx)
1004 1000 """
1005 1001 # generate a directory move map
1006 1002 invalid = set()
1007 1003 dirmove = {}
1008 1004
1009 1005 # examine each file copy for a potential directory move, which is
1010 1006 # when all the files in a directory are moved to a new directory
1011 1007 for dst, src in pycompat.iteritems(fullcopy):
1012 1008 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
1013 1009 if dsrc in invalid:
1014 1010 # already seen to be uninteresting
1015 1011 continue
1016 1012 elif ctx.hasdir(dsrc) and ctx.hasdir(ddst):
1017 1013 # directory wasn't entirely moved locally
1018 1014 invalid.add(dsrc)
1019 1015 elif dsrc in dirmove and dirmove[dsrc] != ddst:
1020 1016 # files from the same directory moved to two different places
1021 1017 invalid.add(dsrc)
1022 1018 else:
1023 1019 # looks good so far
1024 1020 dirmove[dsrc] = ddst
1025 1021
1026 1022 for i in invalid:
1027 1023 if i in dirmove:
1028 1024 del dirmove[i]
1029 1025 del invalid
1030 1026
1031 1027 if not dirmove:
1032 1028 return {}, {}
1033 1029
1034 1030 dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
1035 1031
1036 1032 for d in dirmove:
1037 1033 repo.ui.debug(
1038 1034 b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
1039 1035 )
1040 1036
1041 1037 movewithdir = {}
1042 1038 # check unaccounted nonoverlapping files against directory moves
1043 1039 for f in addedfilesfn():
1044 1040 if f not in fullcopy:
1045 1041 for d in dirmove:
1046 1042 if f.startswith(d):
1047 1043 # new file added in a directory that was moved, move it
1048 1044 df = dirmove[d] + f[len(d) :]
1049 1045 if df not in copy:
1050 1046 movewithdir[f] = df
1051 1047 repo.ui.debug(
1052 1048 b" pending file src: '%s' -> dst: '%s'\n"
1053 1049 % (f, df)
1054 1050 )
1055 1051 break
1056 1052
1057 1053 return dirmove, movewithdir
1058 1054
1059 1055
1060 1056 def _heuristicscopytracing(repo, c1, c2, base):
1061 1057 """Fast copytracing using filename heuristics
1062 1058
1063 1059 Assumes that moves or renames are of following two types:
1064 1060
1065 1061 1) Inside a directory only (same directory name but different filenames)
1066 1062 2) Move from one directory to another
1067 1063 (same filenames but different directory names)
1068 1064
1069 1065 Works only when there are no merge commits in the "source branch".
1070 1066 Source branch is commits from base up to c2 not including base.
1071 1067
1072 1068 If merge is involved it fallbacks to _fullcopytracing().
1073 1069
1074 1070 Can be used by setting the following config:
1075 1071
1076 1072 [experimental]
1077 1073 copytrace = heuristics
1078 1074
1079 1075 In some cases the copy/move candidates found by heuristics can be very large
1080 1076 in number and that will make the algorithm slow. The number of possible
1081 1077 candidates to check can be limited by using the config
1082 1078 `experimental.copytrace.movecandidateslimit` which defaults to 100.
1083 1079 """
1084 1080
1085 1081 if c1.rev() is None:
1086 1082 c1 = c1.p1()
1087 1083 if c2.rev() is None:
1088 1084 c2 = c2.p1()
1089 1085
1090 1086 changedfiles = set()
1091 1087 m1 = c1.manifest()
1092 1088 if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
1093 1089 # If base is not in c2 branch, we switch to fullcopytracing
1094 1090 repo.ui.debug(
1095 1091 b"switching to full copytracing as base is not "
1096 1092 b"an ancestor of c2\n"
1097 1093 )
1098 1094 return _fullcopytracing(repo, c1, c2, base)
1099 1095
1100 1096 ctx = c2
1101 1097 while ctx != base:
1102 1098 if len(ctx.parents()) == 2:
1103 1099 # To keep things simple let's not handle merges
1104 1100 repo.ui.debug(b"switching to full copytracing because of merges\n")
1105 1101 return _fullcopytracing(repo, c1, c2, base)
1106 1102 changedfiles.update(ctx.files())
1107 1103 ctx = ctx.p1()
1108 1104
1109 1105 copies2 = {}
1110 1106 cp = _forwardcopies(base, c2)
1111 1107 for dst, src in pycompat.iteritems(cp):
1112 1108 if src in m1:
1113 1109 copies2[dst] = src
1114 1110
1115 1111 # file is missing if it isn't present in the destination, but is present in
1116 1112 # the base and present in the source.
1117 1113 # Presence in the base is important to exclude added files, presence in the
1118 1114 # source is important to exclude removed files.
1119 1115 filt = lambda f: f not in m1 and f in base and f in c2
1120 1116 missingfiles = [f for f in changedfiles if filt(f)]
1121 1117
1122 1118 copies1 = {}
1123 1119 if missingfiles:
1124 1120 basenametofilename = collections.defaultdict(list)
1125 1121 dirnametofilename = collections.defaultdict(list)
1126 1122
1127 1123 for f in m1.filesnotin(base.manifest()):
1128 1124 basename = os.path.basename(f)
1129 1125 dirname = os.path.dirname(f)
1130 1126 basenametofilename[basename].append(f)
1131 1127 dirnametofilename[dirname].append(f)
1132 1128
1133 1129 for f in missingfiles:
1134 1130 basename = os.path.basename(f)
1135 1131 dirname = os.path.dirname(f)
1136 1132 samebasename = basenametofilename[basename]
1137 1133 samedirname = dirnametofilename[dirname]
1138 1134 movecandidates = samebasename + samedirname
1139 1135 # f is guaranteed to be present in c2, that's why
1140 1136 # c2.filectx(f) won't fail
1141 1137 f2 = c2.filectx(f)
1142 1138 # we can have a lot of candidates which can slow down the heuristics
1143 1139 # config value to limit the number of candidates moves to check
1144 1140 maxcandidates = repo.ui.configint(
1145 1141 b'experimental', b'copytrace.movecandidateslimit'
1146 1142 )
1147 1143
1148 1144 if len(movecandidates) > maxcandidates:
1149 1145 repo.ui.status(
1150 1146 _(
1151 1147 b"skipping copytracing for '%s', more "
1152 1148 b"candidates than the limit: %d\n"
1153 1149 )
1154 1150 % (f, len(movecandidates))
1155 1151 )
1156 1152 continue
1157 1153
1158 1154 for candidate in movecandidates:
1159 1155 f1 = c1.filectx(candidate)
1160 1156 if _related(f1, f2):
1161 1157 # if there are a few related copies then we'll merge
1162 1158 # changes into all of them. This matches the behaviour
1163 1159 # of upstream copytracing
1164 1160 copies1[candidate] = f
1165 1161
1166 1162 return branch_copies(copies1), branch_copies(copies2), {}
1167 1163
1168 1164
1169 1165 def _related(f1, f2):
1170 1166 """return True if f1 and f2 filectx have a common ancestor
1171 1167
1172 1168 Walk back to common ancestor to see if the two files originate
1173 1169 from the same file. Since workingfilectx's rev() is None it messes
1174 1170 up the integer comparison logic, hence the pre-step check for
1175 1171 None (f1 and f2 can only be workingfilectx's initially).
1176 1172 """
1177 1173
1178 1174 if f1 == f2:
1179 1175 return True # a match
1180 1176
1181 1177 g1, g2 = f1.ancestors(), f2.ancestors()
1182 1178 try:
1183 1179 f1r, f2r = f1.linkrev(), f2.linkrev()
1184 1180
1185 1181 if f1r is None:
1186 1182 f1 = next(g1)
1187 1183 if f2r is None:
1188 1184 f2 = next(g2)
1189 1185
1190 1186 while True:
1191 1187 f1r, f2r = f1.linkrev(), f2.linkrev()
1192 1188 if f1r > f2r:
1193 1189 f1 = next(g1)
1194 1190 elif f2r > f1r:
1195 1191 f2 = next(g2)
1196 1192 else: # f1 and f2 point to files in the same linkrev
1197 1193 return f1 == f2 # true if they point to the same file
1198 1194 except StopIteration:
1199 1195 return False
1200 1196
1201 1197
1202 1198 def graftcopies(wctx, ctx, base):
1203 1199 """reproduce copies between base and ctx in the wctx
1204 1200
1205 1201 Unlike mergecopies(), this function will only consider copies between base
1206 1202 and ctx; it will ignore copies between base and wctx. Also unlike
1207 1203 mergecopies(), this function will apply copies to the working copy (instead
1208 1204 of just returning information about the copies). That makes it cheaper
1209 1205 (especially in the common case of base==ctx.p1()) and useful also when
1210 1206 experimental.copytrace=off.
1211 1207
1212 1208 merge.update() will have already marked most copies, but it will only
1213 1209 mark copies if it thinks the source files are related (see
1214 1210 merge._related()). It will also not mark copies if the file wasn't modified
1215 1211 on the local side. This function adds the copies that were "missed"
1216 1212 by merge.update().
1217 1213 """
1218 1214 new_copies = pathcopies(base, ctx)
1219 1215 _filter(wctx.p1(), wctx, new_copies)
1220 1216 for dst, src in pycompat.iteritems(new_copies):
1221 1217 wctx[dst].markcopied(src)
@@ -1,1672 +1,1685 b''
1 1 #testcases filelog compatibility changeset sidedata upgraded
2 2
3 3 =====================================================
4 4 Test Copy tracing for chain of copies involving merge
5 5 =====================================================
6 6
7 7 This test files covers copies/rename case for a chains of commit where merges
8 8 are involved. It cheks we do not have unwanted update of behavior and that the
9 9 different options to retrieve copies behave correctly.
10 10
11 11
12 12 Setup
13 13 =====
14 14
15 15 use git diff to see rename
16 16
17 17 $ cat << EOF >> $HGRCPATH
18 18 > [diff]
19 19 > git=yes
20 20 > [command-templates]
21 21 > log={rev} {desc}\n
22 22 > EOF
23 23
24 24 #if compatibility
25 25 $ cat >> $HGRCPATH << EOF
26 26 > [experimental]
27 27 > copies.read-from = compatibility
28 28 > EOF
29 29 #endif
30 30
31 31 #if changeset
32 32 $ cat >> $HGRCPATH << EOF
33 33 > [experimental]
34 34 > copies.read-from = changeset-only
35 35 > copies.write-to = changeset-only
36 36 > EOF
37 37 #endif
38 38
39 39 #if sidedata
40 40 $ cat >> $HGRCPATH << EOF
41 41 > [format]
42 42 > exp-use-side-data = yes
43 43 > exp-use-copies-side-data-changeset = yes
44 44 > EOF
45 45 #endif
46 46
47 47
48 48 $ hg init repo-chain
49 49 $ cd repo-chain
50 50
51 51 Add some linear rename initialy
52 52
53 53 $ echo a > a
54 54 $ echo b > b
55 55 $ echo h > h
56 56 $ hg ci -Am 'i-0 initial commit: a b h'
57 57 adding a
58 58 adding b
59 59 adding h
60 60 $ hg mv a c
61 61 $ hg ci -Am 'i-1: a -move-> c'
62 62 $ hg mv c d
63 63 $ hg ci -Am 'i-2: c -move-> d'
64 64 $ hg log -G
65 65 @ 2 i-2: c -move-> d
66 66 |
67 67 o 1 i-1: a -move-> c
68 68 |
69 69 o 0 i-0 initial commit: a b h
70 70
71 71
72 72 And having another branch with renames on the other side
73 73
74 74 $ hg mv d e
75 75 $ hg ci -Am 'a-1: d -move-> e'
76 76 $ hg mv e f
77 77 $ hg ci -Am 'a-2: e -move-> f'
78 78 $ hg log -G --rev '::.'
79 79 @ 4 a-2: e -move-> f
80 80 |
81 81 o 3 a-1: d -move-> e
82 82 |
83 83 o 2 i-2: c -move-> d
84 84 |
85 85 o 1 i-1: a -move-> c
86 86 |
87 87 o 0 i-0 initial commit: a b h
88 88
89 89
90 90 Have a branching with nothing on one side
91 91
92 92 $ hg up 'desc("i-2")'
93 93 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
94 94 $ echo foo > b
95 95 $ hg ci -m 'b-1: b update'
96 96 created new head
97 97 $ hg log -G --rev '::.'
98 98 @ 5 b-1: b update
99 99 |
100 100 o 2 i-2: c -move-> d
101 101 |
102 102 o 1 i-1: a -move-> c
103 103 |
104 104 o 0 i-0 initial commit: a b h
105 105
106 106
107 107 Create a branch that delete a file previous renamed
108 108
109 109 $ hg up 'desc("i-2")'
110 110 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 111 $ hg rm d
112 112 $ hg ci -m 'c-1 delete d'
113 113 created new head
114 114 $ hg log -G --rev '::.'
115 115 @ 6 c-1 delete d
116 116 |
117 117 o 2 i-2: c -move-> d
118 118 |
119 119 o 1 i-1: a -move-> c
120 120 |
121 121 o 0 i-0 initial commit: a b h
122 122
123 123
124 124 Create a branch that delete a file previous renamed and recreate it
125 125
126 126 $ hg up 'desc("i-2")'
127 127 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 128 $ hg rm d
129 129 $ hg ci -m 'd-1 delete d'
130 130 created new head
131 131 $ echo bar > d
132 132 $ hg add d
133 133 $ hg ci -m 'd-2 re-add d'
134 134 $ hg log -G --rev '::.'
135 135 @ 8 d-2 re-add d
136 136 |
137 137 o 7 d-1 delete d
138 138 |
139 139 o 2 i-2: c -move-> d
140 140 |
141 141 o 1 i-1: a -move-> c
142 142 |
143 143 o 0 i-0 initial commit: a b h
144 144
145 145
146 146 Having another branch renaming a different file to the same filename as another
147 147
148 148 $ hg up 'desc("i-2")'
149 149 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 150 $ hg mv b g
151 151 $ hg ci -m 'e-1 b -move-> g'
152 152 created new head
153 153 $ hg mv g f
154 154 $ hg ci -m 'e-2 g -move-> f'
155 155 $ hg log -G --rev '::.'
156 156 @ 10 e-2 g -move-> f
157 157 |
158 158 o 9 e-1 b -move-> g
159 159 |
160 160 o 2 i-2: c -move-> d
161 161 |
162 162 o 1 i-1: a -move-> c
163 163 |
164 164 o 0 i-0 initial commit: a b h
165 165
166 166
167 167 Setup all merge
168 168 ===============
169 169
170 170 This is done beforehand to validate that the upgrade process creates valid copy
171 171 information.
172 172
173 173 merging with unrelated change does not interfere with the renames
174 174 ---------------------------------------------------------------
175 175
176 176 - rename on one side
177 177 - unrelated change on the other side
178 178
179 179 $ hg up 'desc("b-1")'
180 180 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
181 181 $ hg merge 'desc("a-2")'
182 182 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
183 183 (branch merge, don't forget to commit)
184 184 $ hg ci -m 'mBAm-0 simple merge - one way'
185 185 $ hg up 'desc("a-2")'
186 186 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 187 $ hg merge 'desc("b-1")'
188 188 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 189 (branch merge, don't forget to commit)
190 190 $ hg ci -m 'mABm-0 simple merge - the other way'
191 191 created new head
192 192 $ hg log -G --rev '::(desc("mABm")+desc("mBAm"))'
193 193 @ 12 mABm-0 simple merge - the other way
194 194 |\
195 195 +---o 11 mBAm-0 simple merge - one way
196 196 | |/
197 197 | o 5 b-1: b update
198 198 | |
199 199 o | 4 a-2: e -move-> f
200 200 | |
201 201 o | 3 a-1: d -move-> e
202 202 |/
203 203 o 2 i-2: c -move-> d
204 204 |
205 205 o 1 i-1: a -move-> c
206 206 |
207 207 o 0 i-0 initial commit: a b h
208 208
209 209
210 210
211 211 merging with the side having a delete
212 212 -------------------------------------
213 213
214 214 case summary:
215 215 - one with change to an unrelated file
216 216 - one deleting the change
217 217 and recreate an unrelated file after the merge
218 218
219 219 $ hg up 'desc("b-1")'
220 220 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
221 221 $ hg merge 'desc("c-1")'
222 222 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
223 223 (branch merge, don't forget to commit)
224 224 $ hg ci -m 'mBCm-0 simple merge - one way'
225 225 $ echo bar > d
226 226 $ hg add d
227 227 $ hg ci -m 'mBCm-1 re-add d'
228 228 $ hg up 'desc("c-1")'
229 229 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
230 230 $ hg merge 'desc("b-1")'
231 231 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
232 232 (branch merge, don't forget to commit)
233 233 $ hg ci -m 'mCBm-0 simple merge - the other way'
234 234 created new head
235 235 $ echo bar > d
236 236 $ hg add d
237 237 $ hg ci -m 'mCBm-1 re-add d'
238 238 $ hg log -G --rev '::(desc("mCBm")+desc("mBCm"))'
239 239 @ 16 mCBm-1 re-add d
240 240 |
241 241 o 15 mCBm-0 simple merge - the other way
242 242 |\
243 243 | | o 14 mBCm-1 re-add d
244 244 | | |
245 245 +---o 13 mBCm-0 simple merge - one way
246 246 | |/
247 247 | o 6 c-1 delete d
248 248 | |
249 249 o | 5 b-1: b update
250 250 |/
251 251 o 2 i-2: c -move-> d
252 252 |
253 253 o 1 i-1: a -move-> c
254 254 |
255 255 o 0 i-0 initial commit: a b h
256 256
257 257
258 258 Comparing with a merge re-adding the file afterward
259 259 ---------------------------------------------------
260 260
261 261 Merge:
262 262 - one with change to an unrelated file
263 263 - one deleting and recreating the change
264 264
265 265 $ hg up 'desc("b-1")'
266 266 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 267 $ hg merge 'desc("d-2")'
268 268 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
269 269 (branch merge, don't forget to commit)
270 270 $ hg ci -m 'mBDm-0 simple merge - one way'
271 271 $ hg up 'desc("d-2")'
272 272 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
273 273 $ hg merge 'desc("b-1")'
274 274 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
275 275 (branch merge, don't forget to commit)
276 276 $ hg ci -m 'mDBm-0 simple merge - the other way'
277 277 created new head
278 278 $ hg log -G --rev '::(desc("mDBm")+desc("mBDm"))'
279 279 @ 18 mDBm-0 simple merge - the other way
280 280 |\
281 281 +---o 17 mBDm-0 simple merge - one way
282 282 | |/
283 283 | o 8 d-2 re-add d
284 284 | |
285 285 | o 7 d-1 delete d
286 286 | |
287 287 o | 5 b-1: b update
288 288 |/
289 289 o 2 i-2: c -move-> d
290 290 |
291 291 o 1 i-1: a -move-> c
292 292 |
293 293 o 0 i-0 initial commit: a b h
294 294
295 295
296 296
297 297 Comparing with a merge with colliding rename
298 298 --------------------------------------------
299 299
300 300 - the "e-" branch renaming b to f (through 'g')
301 301 - the "a-" branch renaming d to f (through e)
302 302
303 303 $ hg up 'desc("a-2")'
304 304 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
305 305 $ hg merge 'desc("e-2")' --tool :union
306 306 merging f
307 307 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
308 308 (branch merge, don't forget to commit)
309 309 $ hg ci -m 'mAEm-0 simple merge - one way'
310 310 $ hg up 'desc("e-2")'
311 311 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
312 312 $ hg merge 'desc("a-2")' --tool :union
313 313 merging f
314 314 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
315 315 (branch merge, don't forget to commit)
316 316 $ hg ci -m 'mEAm-0 simple merge - the other way'
317 317 created new head
318 318 $ hg log -G --rev '::(desc("mAEm")+desc("mEAm"))'
319 319 @ 20 mEAm-0 simple merge - the other way
320 320 |\
321 321 +---o 19 mAEm-0 simple merge - one way
322 322 | |/
323 323 | o 10 e-2 g -move-> f
324 324 | |
325 325 | o 9 e-1 b -move-> g
326 326 | |
327 327 o | 4 a-2: e -move-> f
328 328 | |
329 329 o | 3 a-1: d -move-> e
330 330 |/
331 331 o 2 i-2: c -move-> d
332 332 |
333 333 o 1 i-1: a -move-> c
334 334 |
335 335 o 0 i-0 initial commit: a b h
336 336
337 337
338 338
339 339 Merge:
340 340 - one with change to an unrelated file (b)
341 341 - one overwriting a file (d) with a rename (from h to i to d)
342 342
343 343 $ hg up 'desc("i-2")'
344 344 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
345 345 $ hg mv h i
346 346 $ hg commit -m "f-1: rename h -> i"
347 347 created new head
348 348 $ hg mv --force i d
349 349 $ hg commit -m "f-2: rename i -> d"
350 350 $ hg debugindex d
351 351 rev linkrev nodeid p1 p2
352 352 0 2 169be882533b 000000000000 000000000000 (no-changeset !)
353 353 0 2 b789fdd96dc2 000000000000 000000000000 (changeset !)
354 354 1 8 b004912a8510 000000000000 000000000000
355 355 2 22 4a067cf8965d 000000000000 000000000000 (no-changeset !)
356 356 2 22 fe6f8b4f507f 000000000000 000000000000 (changeset !)
357 357 $ hg up 'desc("b-1")'
358 358 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
359 359 $ hg merge 'desc("f-2")'
360 360 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
361 361 (branch merge, don't forget to commit)
362 362 $ hg ci -m 'mBFm-0 simple merge - one way'
363 363 $ hg up 'desc("f-2")'
364 364 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
365 365 $ hg merge 'desc("b-1")'
366 366 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
367 367 (branch merge, don't forget to commit)
368 368 $ hg ci -m 'mFBm-0 simple merge - the other way'
369 369 created new head
370 370 $ hg log -G --rev '::(desc("mBFm")+desc("mFBm"))'
371 371 @ 24 mFBm-0 simple merge - the other way
372 372 |\
373 373 +---o 23 mBFm-0 simple merge - one way
374 374 | |/
375 375 | o 22 f-2: rename i -> d
376 376 | |
377 377 | o 21 f-1: rename h -> i
378 378 | |
379 379 o | 5 b-1: b update
380 380 |/
381 381 o 2 i-2: c -move-> d
382 382 |
383 383 o 1 i-1: a -move-> c
384 384 |
385 385 o 0 i-0 initial commit: a b h
386 386
387 387
388 388
389 389 Merge:
390 390 - one with change to a file
391 391 - one deleting and recreating the file
392 392
393 393 Unlike in the 'BD/DB' cases, an actual merge happened here. So we should
394 394 consider history and rename on both branch of the merge.
395 395
396 396 $ hg up 'desc("i-2")'
397 397 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
398 398 $ echo "some update" >> d
399 399 $ hg commit -m "g-1: update d"
400 400 created new head
401 401 $ hg up 'desc("d-2")'
402 402 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
403 403 $ hg merge 'desc("g-1")' --tool :union
404 404 merging d
405 405 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
406 406 (branch merge, don't forget to commit)
407 407 $ hg ci -m 'mDGm-0 simple merge - one way'
408 408 $ hg up 'desc("g-1")'
409 409 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
410 410 $ hg merge 'desc("d-2")' --tool :union
411 411 merging d
412 412 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
413 413 (branch merge, don't forget to commit)
414 414 $ hg ci -m 'mGDm-0 simple merge - the other way'
415 415 created new head
416 416 $ hg log -G --rev '::(desc("mDGm")+desc("mGDm"))'
417 417 @ 27 mGDm-0 simple merge - the other way
418 418 |\
419 419 +---o 26 mDGm-0 simple merge - one way
420 420 | |/
421 421 | o 25 g-1: update d
422 422 | |
423 423 o | 8 d-2 re-add d
424 424 | |
425 425 o | 7 d-1 delete d
426 426 |/
427 427 o 2 i-2: c -move-> d
428 428 |
429 429 o 1 i-1: a -move-> c
430 430 |
431 431 o 0 i-0 initial commit: a b h
432 432
433 433
434 434
435 435 Merge:
436 436 - one with change to a file (d)
437 437 - one overwriting that file with a rename (from h to i, to d)
438 438
439 439 This case is similar to BF/FB, but an actual merge happens, so both side of the
440 440 history are relevant.
441 441
442 442 Note:
443 443 | In this case, the merge get conflicting information since on one side we have
444 444 | "a -> c -> d". and one the other one we have "h -> i -> d".
445 445 |
446 446 | The current code arbitrarily pick one side
447 447
448 448 $ hg up 'desc("f-2")'
449 449 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
450 450 $ hg merge 'desc("g-1")' --tool :union
451 451 merging d
452 452 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
453 453 (branch merge, don't forget to commit)
454 454 $ hg ci -m 'mFGm-0 simple merge - one way'
455 455 created new head
456 456 $ hg up 'desc("g-1")'
457 457 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
458 458 $ hg merge 'desc("f-2")' --tool :union
459 459 merging d
460 460 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
461 461 (branch merge, don't forget to commit)
462 462 $ hg ci -m 'mGFm-0 simple merge - the other way'
463 463 created new head
464 464 $ hg log -G --rev '::(desc("mGFm")+desc("mFGm"))'
465 465 @ 29 mGFm-0 simple merge - the other way
466 466 |\
467 467 +---o 28 mFGm-0 simple merge - one way
468 468 | |/
469 469 | o 25 g-1: update d
470 470 | |
471 471 o | 22 f-2: rename i -> d
472 472 | |
473 473 o | 21 f-1: rename h -> i
474 474 |/
475 475 o 2 i-2: c -move-> d
476 476 |
477 477 o 1 i-1: a -move-> c
478 478 |
479 479 o 0 i-0 initial commit: a b h
480 480
481 481
482 482
483 483 Comparing with merging with a deletion (and keeping the file)
484 484 -------------------------------------------------------------
485 485
486 486 Merge:
487 487 - one removing a file (d)
488 488 - one updating that file
489 489 - the merge keep the modified version of the file (canceling the delete)
490 490
491 491 In this case, the file keep on living after the merge. So we should not drop its
492 492 copy tracing chain.
493 493
494 494 $ hg up 'desc("c-1")'
495 495 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
496 496 $ hg merge 'desc("g-1")'
497 497 file 'd' was deleted in local [working copy] but was modified in other [merge rev].
498 498 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
499 499 What do you want to do? u
500 500 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
501 501 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
502 502 [1]
503 503 $ hg resolve -t :other d
504 504 (no more unresolved files)
505 505 $ hg ci -m "mCGm-0"
506 506 created new head
507 507
508 508 $ hg up 'desc("g-1")'
509 509 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
510 510 $ hg merge 'desc("c-1")'
511 511 file 'd' was deleted in other [merge rev] but was modified in local [working copy].
512 512 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
513 513 What do you want to do? u
514 514 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
515 515 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
516 516 [1]
517 517 $ hg resolve -t :local d
518 518 (no more unresolved files)
519 519 $ hg ci -m "mGCm-0"
520 520 created new head
521 521
522 522 $ hg log -G --rev '::(desc("mCGm")+desc("mGCm"))'
523 523 @ 31 mGCm-0
524 524 |\
525 525 +---o 30 mCGm-0
526 526 | |/
527 527 | o 25 g-1: update d
528 528 | |
529 529 o | 6 c-1 delete d
530 530 |/
531 531 o 2 i-2: c -move-> d
532 532 |
533 533 o 1 i-1: a -move-> c
534 534 |
535 535 o 0 i-0 initial commit: a b h
536 536
537 537
538 538
539 539
540 540 Comparing with merge restoring an untouched deleted file
541 541 --------------------------------------------------------
542 542
543 543 Merge:
544 544 - one removing a file (d)
545 545 - one leaving the file untouched
546 546 - the merge actively restore the file to the same content.
547 547
548 548 In this case, the file keep on living after the merge. So we should not drop its
549 549 copy tracing chain.
550 550
551 551 $ hg up 'desc("c-1")'
552 552 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
553 553 $ hg merge 'desc("b-1")'
554 554 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
555 555 (branch merge, don't forget to commit)
556 556 $ hg revert --rev 'desc("b-1")' d
557 557 $ hg ci -m "mCB-revert-m-0"
558 558 created new head
559 559
560 560 $ hg up 'desc("b-1")'
561 561 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
562 562 $ hg merge 'desc("c-1")'
563 563 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
564 564 (branch merge, don't forget to commit)
565 565 $ hg revert --rev 'desc("b-1")' d
566 566 $ hg ci -m "mBC-revert-m-0"
567 567 created new head
568 568
569 569 $ hg log -G --rev '::(desc("mCB-revert-m")+desc("mBC-revert-m"))'
570 570 @ 33 mBC-revert-m-0
571 571 |\
572 572 +---o 32 mCB-revert-m-0
573 573 | |/
574 574 | o 6 c-1 delete d
575 575 | |
576 576 o | 5 b-1: b update
577 577 |/
578 578 o 2 i-2: c -move-> d
579 579 |
580 580 o 1 i-1: a -move-> c
581 581 |
582 582 o 0 i-0 initial commit: a b h
583 583
584 584
585 585
586 586 $ hg up null --quiet
587 587
588 588 Merging a branch where a rename was deleted with a branch where the same file was renamed
589 589 ------------------------------------------------------------------------------------------
590 590
591 591 Create a "conflicting" merge where `d` get removed on one branch before its
592 592 rename information actually conflict with the other branch.
593 593
594 594 (the copy information from the branch that was not deleted should win).
595 595
596 596 $ hg up 'desc("i-0")'
597 597 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
598 598 $ hg mv b d
599 599 $ hg ci -m "h-1: b -(move)-> d"
600 600 created new head
601 601
602 602 $ hg up 'desc("c-1")'
603 603 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
604 604 $ hg merge 'desc("h-1")'
605 605 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
606 606 (branch merge, don't forget to commit)
607 607 $ hg ci -m "mCH-delete-before-conflict-m-0"
608 608
609 609 $ hg up 'desc("h-1")'
610 610 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
611 611 $ hg merge 'desc("c-1")'
612 612 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
613 613 (branch merge, don't forget to commit)
614 614 $ hg ci -m "mHC-delete-before-conflict-m-0"
615 615 created new head
616 616 $ hg log -G --rev '::(desc("mCH-delete-before-conflict-m")+desc("mHC-delete-before-conflict-m"))'
617 617 @ 36 mHC-delete-before-conflict-m-0
618 618 |\
619 619 +---o 35 mCH-delete-before-conflict-m-0
620 620 | |/
621 621 | o 34 h-1: b -(move)-> d
622 622 | |
623 623 o | 6 c-1 delete d
624 624 | |
625 625 o | 2 i-2: c -move-> d
626 626 | |
627 627 o | 1 i-1: a -move-> c
628 628 |/
629 629 o 0 i-0 initial commit: a b h
630 630
631 631
632 632
633 633 Test that sidedata computations during upgrades are correct
634 634 ===========================================================
635 635
636 636 We upgrade a repository that is not using sidedata (the filelog case) and
637 637 check that the same side data have been generated as if they were computed at
638 638 commit time.
639 639
640 640
641 641 #if upgraded
642 642 $ cat >> $HGRCPATH << EOF
643 643 > [format]
644 644 > exp-use-side-data = yes
645 645 > exp-use-copies-side-data-changeset = yes
646 646 > EOF
647 647 $ hg debugformat -v
648 648 format-variant repo config default
649 649 fncache: yes yes yes
650 650 dotencode: yes yes yes
651 651 generaldelta: yes yes yes
652 652 exp-sharesafe: no no no
653 653 sparserevlog: yes yes yes
654 654 sidedata: no yes no
655 655 persistent-nodemap: no no no
656 656 copies-sdc: no yes no
657 657 plain-cl-delta: yes yes yes
658 658 compression: * (glob)
659 659 compression-level: default default default
660 660 $ hg debugupgraderepo --run --quiet
661 661 upgrade will perform the following actions:
662 662
663 663 requirements
664 664 preserved: * (glob)
665 665 added: exp-copies-sidedata-changeset, exp-sidedata-flag
666 666
667 667 processed revlogs:
668 668 - all-filelogs
669 669 - changelog
670 670 - manifest
671 671
672 672 #endif
673 673
674 674
675 675 #if no-compatibility no-filelog no-changeset
676 676
677 677 $ for rev in `hg log --rev 'all()' -T '{rev}\n'`; do
678 678 > echo "##### revision $rev #####"
679 679 > hg debugsidedata -c -v -- $rev
680 680 > hg debugchangedfiles $rev
681 681 > done
682 682 ##### revision 0 #####
683 683 1 sidedata entries
684 684 entry-0014 size 34
685 685 '\x00\x00\x00\x03\x04\x00\x00\x00\x01\x00\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00abh'
686 686 added : a, ;
687 687 added : b, ;
688 688 added : h, ;
689 689 ##### revision 1 #####
690 690 1 sidedata entries
691 691 entry-0014 size 24
692 692 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00ac'
693 693 removed : a, ;
694 694 added p1: c, a;
695 695 ##### revision 2 #####
696 696 1 sidedata entries
697 697 entry-0014 size 24
698 698 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00cd'
699 699 removed : c, ;
700 700 added p1: d, c;
701 701 ##### revision 3 #####
702 702 1 sidedata entries
703 703 entry-0014 size 24
704 704 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00de'
705 705 removed : d, ;
706 706 added p1: e, d;
707 707 ##### revision 4 #####
708 708 1 sidedata entries
709 709 entry-0014 size 24
710 710 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00ef'
711 711 removed : e, ;
712 712 added p1: f, e;
713 713 ##### revision 5 #####
714 714 1 sidedata entries
715 715 entry-0014 size 14
716 716 '\x00\x00\x00\x01\x14\x00\x00\x00\x01\x00\x00\x00\x00b'
717 717 touched : b, ;
718 718 ##### revision 6 #####
719 719 1 sidedata entries
720 720 entry-0014 size 14
721 721 '\x00\x00\x00\x01\x0c\x00\x00\x00\x01\x00\x00\x00\x00d'
722 722 removed : d, ;
723 723 ##### revision 7 #####
724 724 1 sidedata entries
725 725 entry-0014 size 14
726 726 '\x00\x00\x00\x01\x0c\x00\x00\x00\x01\x00\x00\x00\x00d'
727 727 removed : d, ;
728 728 ##### revision 8 #####
729 729 1 sidedata entries
730 730 entry-0014 size 14
731 731 '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x00\x00\x00\x00d'
732 732 added : d, ;
733 733 ##### revision 9 #####
734 734 1 sidedata entries
735 735 entry-0014 size 24
736 736 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00bg'
737 737 removed : b, ;
738 738 added p1: g, b;
739 739 ##### revision 10 #####
740 740 1 sidedata entries
741 741 entry-0014 size 24
742 742 '\x00\x00\x00\x02\x06\x00\x00\x00\x01\x00\x00\x00\x01\x0c\x00\x00\x00\x02\x00\x00\x00\x00fg'
743 743 added p1: f, g;
744 744 removed : g, ;
745 745 ##### revision 11 #####
746 746 1 sidedata entries
747 747 entry-0014 size 4
748 748 '\x00\x00\x00\x00'
749 749 ##### revision 12 #####
750 750 1 sidedata entries
751 751 entry-0014 size 4
752 752 '\x00\x00\x00\x00'
753 753 ##### revision 13 #####
754 754 1 sidedata entries
755 755 entry-0014 size 4
756 756 '\x00\x00\x00\x00'
757 757 ##### revision 14 #####
758 758 1 sidedata entries
759 759 entry-0014 size 14
760 760 '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x00\x00\x00\x00d'
761 761 added : d, ;
762 762 ##### revision 15 #####
763 763 1 sidedata entries
764 764 entry-0014 size 4
765 765 '\x00\x00\x00\x00'
766 766 ##### revision 16 #####
767 767 1 sidedata entries
768 768 entry-0014 size 14
769 769 '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x00\x00\x00\x00d'
770 770 added : d, ;
771 771 ##### revision 17 #####
772 772 1 sidedata entries
773 773 entry-0014 size 4
774 774 '\x00\x00\x00\x00'
775 775 ##### revision 18 #####
776 776 1 sidedata entries
777 777 entry-0014 size 4
778 778 '\x00\x00\x00\x00'
779 779 ##### revision 19 #####
780 780 1 sidedata entries
781 781 entry-0014 size 14
782 782 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00f'
783 783 merged : f, ;
784 784 ##### revision 20 #####
785 785 1 sidedata entries
786 786 entry-0014 size 14
787 787 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00f'
788 788 merged : f, ;
789 789 ##### revision 21 #####
790 790 1 sidedata entries
791 791 entry-0014 size 24
792 792 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00hi'
793 793 removed : h, ;
794 794 added p1: i, h;
795 795 ##### revision 22 #####
796 796 1 sidedata entries
797 797 entry-0014 size 24
798 798 '\x00\x00\x00\x02\x16\x00\x00\x00\x01\x00\x00\x00\x01\x0c\x00\x00\x00\x02\x00\x00\x00\x00di'
799 799 touched p1: d, i;
800 800 removed : i, ;
801 801 ##### revision 23 #####
802 802 1 sidedata entries
803 803 entry-0014 size 4
804 804 '\x00\x00\x00\x00'
805 805 ##### revision 24 #####
806 806 1 sidedata entries
807 807 entry-0014 size 4
808 808 '\x00\x00\x00\x00'
809 809 ##### revision 25 #####
810 810 1 sidedata entries
811 811 entry-0014 size 14
812 812 '\x00\x00\x00\x01\x14\x00\x00\x00\x01\x00\x00\x00\x00d'
813 813 touched : d, ;
814 814 ##### revision 26 #####
815 815 1 sidedata entries
816 816 entry-0014 size 14
817 817 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00d'
818 818 merged : d, ;
819 819 ##### revision 27 #####
820 820 1 sidedata entries
821 821 entry-0014 size 14
822 822 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00d'
823 823 merged : d, ;
824 824 ##### revision 28 #####
825 825 1 sidedata entries
826 826 entry-0014 size 14
827 827 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00d'
828 828 merged : d, ;
829 829 ##### revision 29 #####
830 830 1 sidedata entries
831 831 entry-0014 size 14
832 832 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00d'
833 833 merged : d, ;
834 834 ##### revision 30 #####
835 835 1 sidedata entries
836 836 entry-0014 size 14
837 837 '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d'
838 838 salvaged : d, ;
839 839 ##### revision 31 #####
840 840 1 sidedata entries
841 841 entry-0014 size 14
842 842 '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d'
843 843 salvaged : d, ;
844 844 ##### revision 32 #####
845 845 1 sidedata entries
846 846 entry-0014 size 14
847 847 '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d'
848 848 salvaged : d, ;
849 849 ##### revision 33 #####
850 850 1 sidedata entries
851 851 entry-0014 size 14
852 852 '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d'
853 853 salvaged : d, ;
854 854 ##### revision 34 #####
855 855 1 sidedata entries
856 856 entry-0014 size 24
857 857 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00bd'
858 858 removed : b, ;
859 859 added p1: d, b;
860 860 ##### revision 35 #####
861 861 1 sidedata entries
862 862 entry-0014 size 4
863 863 '\x00\x00\x00\x00'
864 864 ##### revision 36 #####
865 865 1 sidedata entries
866 866 entry-0014 size 4
867 867 '\x00\x00\x00\x00'
868 868
869 869 #endif
870 870
871 871
872 872 Test copy information chaining
873 873 ==============================
874 874
875 Check that matching only affect the destination and not intermediate path
876 -------------------------------------------------------------------------
877
878 The two status call should give the same value for f
879
880 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("a-2")'
881 A f
882 a
883 R a
884 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("a-2")' f
885 A f
886 a (no-changeset no-compatibility !)
887
875 888 merging with unrelated change does not interfere with the renames
876 889 ---------------------------------------------------------------
877 890
878 891 - rename on one side
879 892 - unrelated change on the other side
880 893
881 894 $ hg log -G --rev '::(desc("mABm")+desc("mBAm"))'
882 895 o 12 mABm-0 simple merge - the other way
883 896 |\
884 897 +---o 11 mBAm-0 simple merge - one way
885 898 | |/
886 899 | o 5 b-1: b update
887 900 | |
888 901 o | 4 a-2: e -move-> f
889 902 | |
890 903 o | 3 a-1: d -move-> e
891 904 |/
892 905 o 2 i-2: c -move-> d
893 906 |
894 907 o 1 i-1: a -move-> c
895 908 |
896 909 o 0 i-0 initial commit: a b h
897 910
898 911
899 912 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mABm")'
900 913 A f
901 914 d
902 915 R d
903 916 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBAm")'
904 917 A f
905 918 d
906 919 R d
907 920 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mABm")'
908 921 M b
909 922 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mBAm")'
910 923 M b
911 924 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mABm")'
912 925 M b
913 926 A f
914 927 d
915 928 R d
916 929 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBAm")'
917 930 M b
918 931 A f
919 932 d
920 933 R d
921 934 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mABm")'
922 935 M b
923 936 A f
924 937 a
925 938 R a
926 939 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBAm")'
927 940 M b
928 941 A f
929 942 a
930 943 R a
931 944
932 945 merging with the side having a delete
933 946 -------------------------------------
934 947
935 948 case summary:
936 949 - one with change to an unrelated file
937 950 - one deleting the change
938 951 and recreate an unrelated file after the merge
939 952
940 953 $ hg log -G --rev '::(desc("mCBm")+desc("mBCm"))'
941 954 o 16 mCBm-1 re-add d
942 955 |
943 956 o 15 mCBm-0 simple merge - the other way
944 957 |\
945 958 | | o 14 mBCm-1 re-add d
946 959 | | |
947 960 +---o 13 mBCm-0 simple merge - one way
948 961 | |/
949 962 | o 6 c-1 delete d
950 963 | |
951 964 o | 5 b-1: b update
952 965 |/
953 966 o 2 i-2: c -move-> d
954 967 |
955 968 o 1 i-1: a -move-> c
956 969 |
957 970 o 0 i-0 initial commit: a b h
958 971
959 972 - comparing from the merge
960 973
961 974 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBCm-0")'
962 975 R d
963 976 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mCBm-0")'
964 977 R d
965 978 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mBCm-0")'
966 979 M b
967 980 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCBm-0")'
968 981 M b
969 982 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBCm-0")'
970 983 M b
971 984 R d
972 985 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mCBm-0")'
973 986 M b
974 987 R d
975 988 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBCm-0")'
976 989 M b
977 990 R a
978 991 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCBm-0")'
979 992 M b
980 993 R a
981 994
982 995 - comparing with the merge children re-adding the file
983 996
984 997 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBCm-1")'
985 998 M d
986 999 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mCBm-1")'
987 1000 M d
988 1001 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mBCm-1")'
989 1002 M b
990 1003 A d
991 1004 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCBm-1")'
992 1005 M b
993 1006 A d
994 1007 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBCm-1")'
995 1008 M b
996 1009 M d
997 1010 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mCBm-1")'
998 1011 M b
999 1012 M d
1000 1013 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBCm-1")'
1001 1014 M b
1002 1015 A d
1003 1016 R a
1004 1017 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCBm-1")'
1005 1018 M b
1006 1019 A d
1007 1020 R a
1008 1021
1009 1022 Comparing with a merge re-adding the file afterward
1010 1023 ---------------------------------------------------
1011 1024
1012 1025 Merge:
1013 1026 - one with change to an unrelated file
1014 1027 - one deleting and recreating the change
1015 1028
1016 1029 $ hg log -G --rev '::(desc("mDBm")+desc("mBDm"))'
1017 1030 o 18 mDBm-0 simple merge - the other way
1018 1031 |\
1019 1032 +---o 17 mBDm-0 simple merge - one way
1020 1033 | |/
1021 1034 | o 8 d-2 re-add d
1022 1035 | |
1023 1036 | o 7 d-1 delete d
1024 1037 | |
1025 1038 o | 5 b-1: b update
1026 1039 |/
1027 1040 o 2 i-2: c -move-> d
1028 1041 |
1029 1042 o 1 i-1: a -move-> c
1030 1043 |
1031 1044 o 0 i-0 initial commit: a b h
1032 1045
1033 1046 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBDm-0")'
1034 1047 M d
1035 1048 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mDBm-0")'
1036 1049 M d
1037 1050 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mBDm-0")'
1038 1051 M b
1039 1052 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mDBm-0")'
1040 1053 M b
1041 1054 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBDm-0")'
1042 1055 M b
1043 1056 M d
1044 1057 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mDBm-0")'
1045 1058 M b
1046 1059 M d
1047 1060
1048 1061 The bugs makes recorded copy is different depending of where we started the merge from since
1049 1062
1050 1063 $ hg manifest --debug --rev 'desc("mBDm-0")' | grep '644 d'
1051 1064 b004912a8510032a0350a74daa2803dadfb00e12 644 d
1052 1065 $ hg manifest --debug --rev 'desc("mDBm-0")' | grep '644 d'
1053 1066 b004912a8510032a0350a74daa2803dadfb00e12 644 d
1054 1067
1055 1068 $ hg manifest --debug --rev 'desc("d-2")' | grep '644 d'
1056 1069 b004912a8510032a0350a74daa2803dadfb00e12 644 d
1057 1070 $ hg manifest --debug --rev 'desc("b-1")' | grep '644 d'
1058 1071 169be882533bc917905d46c0c951aa9a1e288dcf 644 d (no-changeset !)
1059 1072 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 d (changeset !)
1060 1073 $ hg debugindex d | head -n 4
1061 1074 rev linkrev nodeid p1 p2
1062 1075 0 2 169be882533b 000000000000 000000000000 (no-changeset !)
1063 1076 0 2 b789fdd96dc2 000000000000 000000000000 (changeset !)
1064 1077 1 8 b004912a8510 000000000000 000000000000
1065 1078 2 22 4a067cf8965d 000000000000 000000000000 (no-changeset !)
1066 1079 2 22 fe6f8b4f507f 000000000000 000000000000 (changeset !)
1067 1080
1068 1081 Log output should not include a merge commit as it did not happen
1069 1082
1070 1083 $ hg log -Gfr 'desc("mBDm-0")' d
1071 1084 o 8 d-2 re-add d
1072 1085 |
1073 1086 ~
1074 1087
1075 1088 $ hg log -Gfr 'desc("mDBm-0")' d
1076 1089 o 8 d-2 re-add d
1077 1090 |
1078 1091 ~
1079 1092
1080 1093 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBDm-0")'
1081 1094 M b
1082 1095 A d
1083 1096 R a
1084 1097 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDBm-0")'
1085 1098 M b
1086 1099 A d
1087 1100 R a
1088 1101
1089 1102
1090 1103 Comparing with a merge with colliding rename
1091 1104 --------------------------------------------
1092 1105
1093 1106 - the "e-" branch renaming b to f (through 'g')
1094 1107 - the "a-" branch renaming d to f (through e)
1095 1108
1096 1109 $ hg log -G --rev '::(desc("mAEm")+desc("mEAm"))'
1097 1110 o 20 mEAm-0 simple merge - the other way
1098 1111 |\
1099 1112 +---o 19 mAEm-0 simple merge - one way
1100 1113 | |/
1101 1114 | o 10 e-2 g -move-> f
1102 1115 | |
1103 1116 | o 9 e-1 b -move-> g
1104 1117 | |
1105 1118 o | 4 a-2: e -move-> f
1106 1119 | |
1107 1120 o | 3 a-1: d -move-> e
1108 1121 |/
1109 1122 o 2 i-2: c -move-> d
1110 1123 |
1111 1124 o 1 i-1: a -move-> c
1112 1125 |
1113 1126 o 0 i-0 initial commit: a b h
1114 1127
1115 1128 #if no-changeset
1116 1129 $ hg manifest --debug --rev 'desc("mAEm-0")' | grep '644 f'
1117 1130 c39c6083dad048d5138618a46f123e2f397f4f18 644 f
1118 1131 $ hg manifest --debug --rev 'desc("mEAm-0")' | grep '644 f'
1119 1132 a9a8bc3860c9d8fa5f2f7e6ea8d40498322737fd 644 f
1120 1133 $ hg manifest --debug --rev 'desc("a-2")' | grep '644 f'
1121 1134 263ea25e220aaeb7b9bac551c702037849aa75e8 644 f
1122 1135 $ hg manifest --debug --rev 'desc("e-2")' | grep '644 f'
1123 1136 71b9b7e73d973572ade6dd765477fcee6890e8b1 644 f
1124 1137 $ hg debugindex f
1125 1138 rev linkrev nodeid p1 p2
1126 1139 0 4 263ea25e220a 000000000000 000000000000
1127 1140 1 10 71b9b7e73d97 000000000000 000000000000
1128 1141 2 19 c39c6083dad0 263ea25e220a 71b9b7e73d97
1129 1142 3 20 a9a8bc3860c9 71b9b7e73d97 263ea25e220a
1130 1143 #else
1131 1144 $ hg manifest --debug --rev 'desc("mAEm-0")' | grep '644 f'
1132 1145 498e8799f49f9da1ca06bb2d6d4accf165c5b572 644 f
1133 1146 $ hg manifest --debug --rev 'desc("mEAm-0")' | grep '644 f'
1134 1147 c5b506a7118667a38a9c9348a1f63b679e382f57 644 f
1135 1148 $ hg manifest --debug --rev 'desc("a-2")' | grep '644 f'
1136 1149 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 f
1137 1150 $ hg manifest --debug --rev 'desc("e-2")' | grep '644 f'
1138 1151 1e88685f5ddec574a34c70af492f95b6debc8741 644 f
1139 1152 $ hg debugindex f
1140 1153 rev linkrev nodeid p1 p2
1141 1154 0 4 b789fdd96dc2 000000000000 000000000000
1142 1155 1 10 1e88685f5dde 000000000000 000000000000
1143 1156 2 19 498e8799f49f b789fdd96dc2 1e88685f5dde
1144 1157 3 20 c5b506a71186 1e88685f5dde b789fdd96dc2
1145 1158 #endif
1146 1159
1147 1160 # Here the filelog based implementation is not looking at the rename
1148 1161 # information (because the file exist on both side). However the changelog
1149 1162 # based on works fine. We have different output.
1150 1163
1151 1164 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mAEm-0")'
1152 1165 M f
1153 1166 b (no-filelog !)
1154 1167 R b
1155 1168 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mEAm-0")'
1156 1169 M f
1157 1170 b (no-filelog !)
1158 1171 R b
1159 1172 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mAEm-0")'
1160 1173 M f
1161 1174 d (no-filelog !)
1162 1175 R d
1163 1176 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mEAm-0")'
1164 1177 M f
1165 1178 d (no-filelog !)
1166 1179 R d
1167 1180 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("a-2")'
1168 1181 A f
1169 1182 d
1170 1183 R d
1171 1184 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("e-2")'
1172 1185 A f
1173 1186 b
1174 1187 R b
1175 1188
1176 1189 # From here, we run status against revision where both source file exists.
1177 1190 #
1178 1191 # The filelog based implementation picks an arbitrary side based on revision
1179 1192 # numbers. So the same side "wins" whatever the parents order is. This is
1180 1193 # sub-optimal because depending on revision numbers means the result can be
1181 1194 # different from one repository to the next.
1182 1195 #
1183 1196 # The changeset based algorithm use the parent order to break tie on conflicting
1184 1197 # information and will have a different order depending on who is p1 and p2.
1185 1198 # That order is stable accross repositories. (data from p1 prevails)
1186 1199
1187 1200 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mAEm-0")'
1188 1201 A f
1189 1202 d
1190 1203 R b
1191 1204 R d
1192 1205 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mEAm-0")'
1193 1206 A f
1194 1207 d (filelog !)
1195 1208 b (no-filelog !)
1196 1209 R b
1197 1210 R d
1198 1211 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAEm-0")'
1199 1212 A f
1200 1213 a
1201 1214 R a
1202 1215 R b
1203 1216 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEAm-0")'
1204 1217 A f
1205 1218 a (filelog !)
1206 1219 b (no-filelog !)
1207 1220 R a
1208 1221 R b
1209 1222
1210 1223
1211 1224 Note:
1212 1225 | In this case, one of the merge wrongly record a merge while there is none.
1213 1226 | This lead to bad copy tracing information to be dug up.
1214 1227
1215 1228
1216 1229 Merge:
1217 1230 - one with change to an unrelated file (b)
1218 1231 - one overwriting a file (d) with a rename (from h to i to d)
1219 1232
1220 1233 $ hg log -G --rev '::(desc("mBFm")+desc("mFBm"))'
1221 1234 o 24 mFBm-0 simple merge - the other way
1222 1235 |\
1223 1236 +---o 23 mBFm-0 simple merge - one way
1224 1237 | |/
1225 1238 | o 22 f-2: rename i -> d
1226 1239 | |
1227 1240 | o 21 f-1: rename h -> i
1228 1241 | |
1229 1242 o | 5 b-1: b update
1230 1243 |/
1231 1244 o 2 i-2: c -move-> d
1232 1245 |
1233 1246 o 1 i-1: a -move-> c
1234 1247 |
1235 1248 o 0 i-0 initial commit: a b h
1236 1249
1237 1250 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBFm-0")'
1238 1251 M b
1239 1252 A d
1240 1253 h
1241 1254 R a
1242 1255 R h
1243 1256 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFBm-0")'
1244 1257 M b
1245 1258 A d
1246 1259 h
1247 1260 R a
1248 1261 R h
1249 1262 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBFm-0")'
1250 1263 M d
1251 1264 h (no-filelog !)
1252 1265 R h
1253 1266 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mBFm-0")'
1254 1267 M b
1255 1268 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mBFm-0")'
1256 1269 M b
1257 1270 M d
1258 1271 i (no-filelog !)
1259 1272 R i
1260 1273 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mFBm-0")'
1261 1274 M d
1262 1275 h (no-filelog !)
1263 1276 R h
1264 1277 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFBm-0")'
1265 1278 M b
1266 1279 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFBm-0")'
1267 1280 M b
1268 1281 M d
1269 1282 i (no-filelog !)
1270 1283 R i
1271 1284
1272 1285 #if no-changeset
1273 1286 $ hg log -Gfr 'desc("mBFm-0")' d
1274 1287 o 22 f-2: rename i -> d
1275 1288 |
1276 1289 o 21 f-1: rename h -> i
1277 1290 :
1278 1291 o 0 i-0 initial commit: a b h
1279 1292
1280 1293 #else
1281 1294 BROKEN: `hg log --follow <file>` relies on filelog metadata to work
1282 1295 $ hg log -Gfr 'desc("mBFm-0")' d
1283 1296 o 22 f-2: rename i -> d
1284 1297 |
1285 1298 ~
1286 1299 #endif
1287 1300
1288 1301 #if no-changeset
1289 1302 $ hg log -Gfr 'desc("mFBm-0")' d
1290 1303 o 22 f-2: rename i -> d
1291 1304 |
1292 1305 o 21 f-1: rename h -> i
1293 1306 :
1294 1307 o 0 i-0 initial commit: a b h
1295 1308
1296 1309 #else
1297 1310 BROKEN: `hg log --follow <file>` relies on filelog metadata to work
1298 1311 $ hg log -Gfr 'desc("mFBm-0")' d
1299 1312 o 22 f-2: rename i -> d
1300 1313 |
1301 1314 ~
1302 1315 #endif
1303 1316
1304 1317
1305 1318 Merge:
1306 1319 - one with change to a file
1307 1320 - one deleting and recreating the file
1308 1321
1309 1322 Unlike in the 'BD/DB' cases, an actual merge happened here. So we should
1310 1323 consider history and rename on both branch of the merge.
1311 1324
1312 1325 $ hg log -G --rev '::(desc("mDGm")+desc("mGDm"))'
1313 1326 o 27 mGDm-0 simple merge - the other way
1314 1327 |\
1315 1328 +---o 26 mDGm-0 simple merge - one way
1316 1329 | |/
1317 1330 | o 25 g-1: update d
1318 1331 | |
1319 1332 o | 8 d-2 re-add d
1320 1333 | |
1321 1334 o | 7 d-1 delete d
1322 1335 |/
1323 1336 o 2 i-2: c -move-> d
1324 1337 |
1325 1338 o 1 i-1: a -move-> c
1326 1339 |
1327 1340 o 0 i-0 initial commit: a b h
1328 1341
1329 1342 One side of the merge have a long history with rename. The other side of the
1330 1343 merge point to a new file with a smaller history. Each side is "valid".
1331 1344
1332 1345 (and again the filelog based algorithm only explore one, with a pick based on
1333 1346 revision numbers)
1334 1347
1335 1348 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDGm-0")'
1336 1349 A d
1337 1350 a (filelog !)
1338 1351 R a
1339 1352 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGDm-0")'
1340 1353 A d
1341 1354 a
1342 1355 R a
1343 1356 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mDGm-0")'
1344 1357 M d
1345 1358 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mGDm-0")'
1346 1359 M d
1347 1360 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mDGm-0")'
1348 1361 M d
1349 1362 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGDm-0")'
1350 1363 M d
1351 1364
1352 1365 #if no-changeset
1353 1366 $ hg log -Gfr 'desc("mDGm-0")' d
1354 1367 o 26 mDGm-0 simple merge - one way
1355 1368 |\
1356 1369 | o 25 g-1: update d
1357 1370 | |
1358 1371 o | 8 d-2 re-add d
1359 1372 |/
1360 1373 o 2 i-2: c -move-> d
1361 1374 |
1362 1375 o 1 i-1: a -move-> c
1363 1376 |
1364 1377 o 0 i-0 initial commit: a b h
1365 1378
1366 1379 #else
1367 1380 BROKEN: `hg log --follow <file>` relies on filelog metadata to work
1368 1381 $ hg log -Gfr 'desc("mDGm-0")' d
1369 1382 o 26 mDGm-0 simple merge - one way
1370 1383 |\
1371 1384 | o 25 g-1: update d
1372 1385 | |
1373 1386 o | 8 d-2 re-add d
1374 1387 |/
1375 1388 o 2 i-2: c -move-> d
1376 1389 |
1377 1390 ~
1378 1391 #endif
1379 1392
1380 1393
1381 1394 #if no-changeset
1382 1395 $ hg log -Gfr 'desc("mDGm-0")' d
1383 1396 o 26 mDGm-0 simple merge - one way
1384 1397 |\
1385 1398 | o 25 g-1: update d
1386 1399 | |
1387 1400 o | 8 d-2 re-add d
1388 1401 |/
1389 1402 o 2 i-2: c -move-> d
1390 1403 |
1391 1404 o 1 i-1: a -move-> c
1392 1405 |
1393 1406 o 0 i-0 initial commit: a b h
1394 1407
1395 1408 #else
1396 1409 BROKEN: `hg log --follow <file>` relies on filelog metadata to work
1397 1410 $ hg log -Gfr 'desc("mDGm-0")' d
1398 1411 o 26 mDGm-0 simple merge - one way
1399 1412 |\
1400 1413 | o 25 g-1: update d
1401 1414 | |
1402 1415 o | 8 d-2 re-add d
1403 1416 |/
1404 1417 o 2 i-2: c -move-> d
1405 1418 |
1406 1419 ~
1407 1420 #endif
1408 1421
1409 1422
1410 1423 Merge:
1411 1424 - one with change to a file (d)
1412 1425 - one overwriting that file with a rename (from h to i, to d)
1413 1426
1414 1427 This case is similar to BF/FB, but an actual merge happens, so both side of the
1415 1428 history are relevant.
1416 1429
1417 1430 Note:
1418 1431 | In this case, the merge get conflicting information since on one side we have
1419 1432 | "a -> c -> d". and one the other one we have "h -> i -> d".
1420 1433 |
1421 1434 | The current code arbitrarily pick one side
1422 1435
1423 1436 $ hg log -G --rev '::(desc("mGFm")+desc("mFGm"))'
1424 1437 o 29 mGFm-0 simple merge - the other way
1425 1438 |\
1426 1439 +---o 28 mFGm-0 simple merge - one way
1427 1440 | |/
1428 1441 | o 25 g-1: update d
1429 1442 | |
1430 1443 o | 22 f-2: rename i -> d
1431 1444 | |
1432 1445 o | 21 f-1: rename h -> i
1433 1446 |/
1434 1447 o 2 i-2: c -move-> d
1435 1448 |
1436 1449 o 1 i-1: a -move-> c
1437 1450 |
1438 1451 o 0 i-0 initial commit: a b h
1439 1452
1440 1453 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFGm-0")'
1441 1454 A d
1442 1455 h
1443 1456 R a
1444 1457 R h
1445 1458 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGFm-0")'
1446 1459 A d
1447 1460 a (no-filelog !)
1448 1461 h (filelog !)
1449 1462 R a
1450 1463 R h
1451 1464 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFGm-0")'
1452 1465 M d
1453 1466 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mGFm-0")'
1454 1467 M d
1455 1468 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFGm-0")'
1456 1469 M d
1457 1470 i (no-filelog !)
1458 1471 R i
1459 1472 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mGFm-0")'
1460 1473 M d
1461 1474 i (no-filelog !)
1462 1475 R i
1463 1476 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mFGm-0")'
1464 1477 M d
1465 1478 h (no-filelog !)
1466 1479 R h
1467 1480 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGFm-0")'
1468 1481 M d
1469 1482 h (no-filelog !)
1470 1483 R h
1471 1484
1472 1485 #if no-changeset
1473 1486 $ hg log -Gfr 'desc("mFGm-0")' d
1474 1487 o 28 mFGm-0 simple merge - one way
1475 1488 |\
1476 1489 | o 25 g-1: update d
1477 1490 | |
1478 1491 o | 22 f-2: rename i -> d
1479 1492 | |
1480 1493 o | 21 f-1: rename h -> i
1481 1494 |/
1482 1495 o 2 i-2: c -move-> d
1483 1496 |
1484 1497 o 1 i-1: a -move-> c
1485 1498 |
1486 1499 o 0 i-0 initial commit: a b h
1487 1500
1488 1501 #else
1489 1502 BROKEN: `hg log --follow <file>` relies on filelog metadata to work
1490 1503 $ hg log -Gfr 'desc("mFGm-0")' d
1491 1504 o 28 mFGm-0 simple merge - one way
1492 1505 |\
1493 1506 | o 25 g-1: update d
1494 1507 | |
1495 1508 o | 22 f-2: rename i -> d
1496 1509 |/
1497 1510 o 2 i-2: c -move-> d
1498 1511 |
1499 1512 ~
1500 1513 #endif
1501 1514
1502 1515 #if no-changeset
1503 1516 $ hg log -Gfr 'desc("mGFm-0")' d
1504 1517 o 29 mGFm-0 simple merge - the other way
1505 1518 |\
1506 1519 | o 25 g-1: update d
1507 1520 | |
1508 1521 o | 22 f-2: rename i -> d
1509 1522 | |
1510 1523 o | 21 f-1: rename h -> i
1511 1524 |/
1512 1525 o 2 i-2: c -move-> d
1513 1526 |
1514 1527 o 1 i-1: a -move-> c
1515 1528 |
1516 1529 o 0 i-0 initial commit: a b h
1517 1530
1518 1531 #else
1519 1532 BROKEN: `hg log --follow <file>` relies on filelog metadata to work
1520 1533 $ hg log -Gfr 'desc("mGFm-0")' d
1521 1534 o 29 mGFm-0 simple merge - the other way
1522 1535 |\
1523 1536 | o 25 g-1: update d
1524 1537 | |
1525 1538 o | 22 f-2: rename i -> d
1526 1539 |/
1527 1540 o 2 i-2: c -move-> d
1528 1541 |
1529 1542 ~
1530 1543 #endif
1531 1544
1532 1545
1533 1546 Comparing with merging with a deletion (and keeping the file)
1534 1547 -------------------------------------------------------------
1535 1548
1536 1549 Merge:
1537 1550 - one removing a file (d)
1538 1551 - one updating that file
1539 1552 - the merge keep the modified version of the file (canceling the delete)
1540 1553
1541 1554 In this case, the file keep on living after the merge. So we should not drop its
1542 1555 copy tracing chain.
1543 1556
1544 1557 $ hg log -G --rev '::(desc("mCGm")+desc("mGCm"))'
1545 1558 o 31 mGCm-0
1546 1559 |\
1547 1560 +---o 30 mCGm-0
1548 1561 | |/
1549 1562 | o 25 g-1: update d
1550 1563 | |
1551 1564 o | 6 c-1 delete d
1552 1565 |/
1553 1566 o 2 i-2: c -move-> d
1554 1567 |
1555 1568 o 1 i-1: a -move-> c
1556 1569 |
1557 1570 o 0 i-0 initial commit: a b h
1558 1571
1559 1572
1560 1573 'a' is the copy source of 'd'
1561 1574
1562 1575 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCGm-0")'
1563 1576 A d
1564 1577 a (no-compatibility no-changeset !)
1565 1578 R a
1566 1579 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGCm-0")'
1567 1580 A d
1568 1581 a (no-compatibility no-changeset !)
1569 1582 R a
1570 1583 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCGm-0")'
1571 1584 A d
1572 1585 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mGCm-0")'
1573 1586 A d
1574 1587 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mCGm-0")'
1575 1588 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGCm-0")'
1576 1589
1577 1590
1578 1591 Comparing with merge restoring an untouched deleted file
1579 1592 --------------------------------------------------------
1580 1593
1581 1594 Merge:
1582 1595 - one removing a file (d)
1583 1596 - one leaving the file untouched
1584 1597 - the merge actively restore the file to the same content.
1585 1598
1586 1599 In this case, the file keep on living after the merge. So we should not drop its
1587 1600 copy tracing chain.
1588 1601
1589 1602 $ hg log -G --rev '::(desc("mCB-revert-m")+desc("mBC-revert-m"))'
1590 1603 o 33 mBC-revert-m-0
1591 1604 |\
1592 1605 +---o 32 mCB-revert-m-0
1593 1606 | |/
1594 1607 | o 6 c-1 delete d
1595 1608 | |
1596 1609 o | 5 b-1: b update
1597 1610 |/
1598 1611 o 2 i-2: c -move-> d
1599 1612 |
1600 1613 o 1 i-1: a -move-> c
1601 1614 |
1602 1615 o 0 i-0 initial commit: a b h
1603 1616
1604 1617
1605 1618 'a' is the the copy source of 'd'
1606 1619
1607 1620 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCB-revert-m-0")'
1608 1621 M b
1609 1622 A d
1610 1623 a (no-compatibility no-changeset !)
1611 1624 R a
1612 1625 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBC-revert-m-0")'
1613 1626 M b
1614 1627 A d
1615 1628 a (no-compatibility no-changeset !)
1616 1629 R a
1617 1630 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCB-revert-m-0")'
1618 1631 M b
1619 1632 A d
1620 1633 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mBC-revert-m-0")'
1621 1634 M b
1622 1635 A d
1623 1636 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mCB-revert-m-0")'
1624 1637 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBC-revert-m-0")'
1625 1638
1626 1639
1627 1640 Merging a branch where a rename was deleted with a branch where the same file was renamed
1628 1641 ------------------------------------------------------------------------------------------
1629 1642
1630 1643 Create a "conflicting" merge where `d` get removed on one branch before its
1631 1644 rename information actually conflict with the other branch.
1632 1645
1633 1646 (the copy information from the branch that was not deleted should win).
1634 1647
1635 1648 $ hg log -G --rev '::(desc("mCH-delete-before-conflict-m")+desc("mHC-delete-before-conflict-m"))'
1636 1649 @ 36 mHC-delete-before-conflict-m-0
1637 1650 |\
1638 1651 +---o 35 mCH-delete-before-conflict-m-0
1639 1652 | |/
1640 1653 | o 34 h-1: b -(move)-> d
1641 1654 | |
1642 1655 o | 6 c-1 delete d
1643 1656 | |
1644 1657 o | 2 i-2: c -move-> d
1645 1658 | |
1646 1659 o | 1 i-1: a -move-> c
1647 1660 |/
1648 1661 o 0 i-0 initial commit: a b h
1649 1662
1650 1663
1651 1664 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCH-delete-before-conflict-m")'
1652 1665 A d
1653 1666 b (no-compatibility no-changeset !)
1654 1667 R a
1655 1668 R b
1656 1669 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mHC-delete-before-conflict-m")'
1657 1670 A d
1658 1671 b
1659 1672 R a
1660 1673 R b
1661 1674 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCH-delete-before-conflict-m")'
1662 1675 A d
1663 1676 b
1664 1677 R b
1665 1678 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mHC-delete-before-conflict-m")'
1666 1679 A d
1667 1680 b
1668 1681 R b
1669 1682 $ hg status --copies --rev 'desc("h-1")' --rev 'desc("mCH-delete-before-conflict-m")'
1670 1683 R a
1671 1684 $ hg status --copies --rev 'desc("h-1")' --rev 'desc("mHC-delete-before-conflict-m")'
1672 1685 R a
General Comments 0
You need to be logged in to leave comments. Login now