##// END OF EJS Templates
copies: handle a case when both merging csets are not descendant of merge base...
Sushil khanchi -
r42098:7694b685 default
parent child Browse files
Show More
@@ -1,1001 +1,1007 b''
1 1 # copies.py - copy detection for Mercurial
2 2 #
3 3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import heapq
12 12 import os
13 13
14 14 from .i18n import _
15 15
16 16 from . import (
17 17 match as matchmod,
18 18 node,
19 19 pathutil,
20 20 scmutil,
21 21 util,
22 22 )
23 23 from .utils import (
24 24 stringutil,
25 25 )
26 26
27 27 def _findlimit(repo, ctxa, ctxb):
28 28 """
29 29 Find the last revision that needs to be checked to ensure that a full
30 30 transitive closure for file copies can be properly calculated.
31 31 Generally, this means finding the earliest revision number that's an
32 32 ancestor of a or b but not both, except when a or b is a direct descendent
33 33 of the other, in which case we can return the minimum revnum of a and b.
34 34 """
35 35
36 36 # basic idea:
37 37 # - mark a and b with different sides
38 38 # - if a parent's children are all on the same side, the parent is
39 39 # on that side, otherwise it is on no side
40 40 # - walk the graph in topological order with the help of a heap;
41 41 # - add unseen parents to side map
42 42 # - clear side of any parent that has children on different sides
43 43 # - track number of interesting revs that might still be on a side
44 44 # - track the lowest interesting rev seen
45 45 # - quit when interesting revs is zero
46 46
47 47 cl = repo.changelog
48 48 wdirparents = None
49 49 a = ctxa.rev()
50 50 b = ctxb.rev()
51 51 if a is None:
52 52 wdirparents = (ctxa.p1(), ctxa.p2())
53 53 a = node.wdirrev
54 54 if b is None:
55 55 assert not wdirparents
56 56 wdirparents = (ctxb.p1(), ctxb.p2())
57 57 b = node.wdirrev
58 58
59 59 side = {a: -1, b: 1}
60 60 visit = [-a, -b]
61 61 heapq.heapify(visit)
62 62 interesting = len(visit)
63 63 limit = node.wdirrev
64 64
65 65 while interesting:
66 66 r = -heapq.heappop(visit)
67 67 if r == node.wdirrev:
68 68 parents = [pctx.rev() for pctx in wdirparents]
69 69 else:
70 70 parents = cl.parentrevs(r)
71 71 if parents[1] == node.nullrev:
72 72 parents = parents[:1]
73 73 for p in parents:
74 74 if p not in side:
75 75 # first time we see p; add it to visit
76 76 side[p] = side[r]
77 77 if side[p]:
78 78 interesting += 1
79 79 heapq.heappush(visit, -p)
80 80 elif side[p] and side[p] != side[r]:
81 81 # p was interesting but now we know better
82 82 side[p] = 0
83 83 interesting -= 1
84 84 if side[r]:
85 85 limit = r # lowest rev visited
86 86 interesting -= 1
87 87
88 88 # Consider the following flow (see test-commit-amend.t under issue4405):
89 89 # 1/ File 'a0' committed
90 90 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
91 91 # 3/ Move back to first commit
92 92 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
93 93 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
94 94 #
95 95 # During the amend in step five, we will be in this state:
96 96 #
97 97 # @ 3 temporary amend commit for a1-amend
98 98 # |
99 99 # o 2 a1-amend
100 100 # |
101 101 # | o 1 a1
102 102 # |/
103 103 # o 0 a0
104 104 #
105 105 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
106 106 # yet the filelog has the copy information in rev 1 and we will not look
107 107 # back far enough unless we also look at the a and b as candidates.
108 108 # This only occurs when a is a descendent of b or visa-versa.
109 109 return min(limit, a, b)
110 110
111 111 def _chain(src, dst, a, b):
112 112 """chain two sets of copies a->b"""
113 113 t = a.copy()
114 114 for k, v in b.iteritems():
115 115 if v in t:
116 116 # found a chain
117 117 if t[v] != k:
118 118 # file wasn't renamed back to itself
119 119 t[k] = t[v]
120 120 if v not in dst:
121 121 # chain was a rename, not a copy
122 122 del t[v]
123 123 if v in src:
124 124 # file is a copy of an existing file
125 125 t[k] = v
126 126
127 127 for k, v in list(t.items()):
128 128 # remove criss-crossed copies
129 129 if k in src and v in dst:
130 130 del t[k]
131 131 # remove copies to files that were then removed
132 132 elif k not in dst:
133 133 del t[k]
134 134
135 135 return t
136 136
137 137 def _tracefile(fctx, am, limit=node.nullrev):
138 138 """return file context that is the ancestor of fctx present in ancestor
139 139 manifest am, stopping after the first ancestor lower than limit"""
140 140
141 141 for f in fctx.ancestors():
142 142 if am.get(f.path(), None) == f.filenode():
143 143 return f
144 144 if limit >= 0 and not f.isintroducedafter(limit):
145 145 return None
146 146
147 147 def _dirstatecopies(repo, match=None):
148 148 ds = repo.dirstate
149 149 c = ds.copies().copy()
150 150 for k in list(c):
151 151 if ds[k] not in 'anm' or (match and not match(k)):
152 152 del c[k]
153 153 return c
154 154
155 155 def _computeforwardmissing(a, b, match=None):
156 156 """Computes which files are in b but not a.
157 157 This is its own function so extensions can easily wrap this call to see what
158 158 files _forwardcopies is about to process.
159 159 """
160 160 ma = a.manifest()
161 161 mb = b.manifest()
162 162 return mb.filesnotin(ma, match=match)
163 163
164 164 def _committedforwardcopies(a, b, match):
165 165 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
166 166 # files might have to be traced back to the fctx parent of the last
167 167 # one-side-only changeset, but not further back than that
168 168 repo = a._repo
169 169
170 170 if repo.ui.config('experimental', 'copies.read-from') == 'compatibility':
171 171 return _changesetforwardcopies(a, b, match)
172 172
173 173 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
174 174 dbg = repo.ui.debug
175 175 if debug:
176 176 dbg('debug.copies: looking into rename from %s to %s\n'
177 177 % (a, b))
178 178 limit = _findlimit(repo, a, b)
179 179 if debug:
180 180 dbg('debug.copies: search limit: %d\n' % limit)
181 181 am = a.manifest()
182 182
183 183 # find where new files came from
184 184 # we currently don't try to find where old files went, too expensive
185 185 # this means we can miss a case like 'hg rm b; hg cp a b'
186 186 cm = {}
187 187
188 188 # Computing the forward missing is quite expensive on large manifests, since
189 189 # it compares the entire manifests. We can optimize it in the common use
190 190 # case of computing what copies are in a commit versus its parent (like
191 191 # during a rebase or histedit). Note, we exclude merge commits from this
192 192 # optimization, since the ctx.files() for a merge commit is not correct for
193 193 # this comparison.
194 194 forwardmissingmatch = match
195 195 if b.p1() == a and b.p2().node() == node.nullid:
196 196 filesmatcher = scmutil.matchfiles(a._repo, b.files())
197 197 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
198 198 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
199 199
200 200 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
201 201
202 202 if debug:
203 203 dbg('debug.copies: missing file to search: %d\n' % len(missing))
204 204
205 205 for f in missing:
206 206 if debug:
207 207 dbg('debug.copies: tracing file: %s\n' % f)
208 208 fctx = b[f]
209 209 fctx._ancestrycontext = ancestrycontext
210 210
211 211 if debug:
212 212 start = util.timer()
213 213 ofctx = _tracefile(fctx, am, limit)
214 214 if ofctx:
215 215 if debug:
216 216 dbg('debug.copies: rename of: %s\n' % ofctx._path)
217 217 cm[f] = ofctx.path()
218 218 if debug:
219 219 dbg('debug.copies: time: %f seconds\n'
220 220 % (util.timer() - start))
221 221 return cm
222 222
223 223 def _changesetforwardcopies(a, b, match):
224 224 if a.rev() == node.nullrev:
225 225 return {}
226 226
227 227 repo = a.repo()
228 228 children = {}
229 229 cl = repo.changelog
230 230 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
231 231 for r in missingrevs:
232 232 for p in cl.parentrevs(r):
233 233 if p == node.nullrev:
234 234 continue
235 235 if p not in children:
236 236 children[p] = [r]
237 237 else:
238 238 children[p].append(r)
239 239
240 240 roots = set(children) - set(missingrevs)
241 241 # 'work' contains 3-tuples of a (revision number, parent number, copies).
242 242 # The parent number is only used for knowing which parent the copies dict
243 243 # came from.
244 244 work = [(r, 1, {}) for r in roots]
245 245 heapq.heapify(work)
246 246 while work:
247 247 r, i1, copies1 = heapq.heappop(work)
248 248 if work and work[0][0] == r:
249 249 # We are tracing copies from both parents
250 250 r, i2, copies2 = heapq.heappop(work)
251 251 copies = {}
252 252 ctx = repo[r]
253 253 p1man, p2man = ctx.p1().manifest(), ctx.p2().manifest()
254 254 allcopies = set(copies1) | set(copies2)
255 255 # TODO: perhaps this filtering should be done as long as ctx
256 256 # is merge, whether or not we're tracing from both parent.
257 257 for dst in allcopies:
258 258 if not match(dst):
259 259 continue
260 260 if dst not in copies2:
261 261 # Copied on p1 side: mark as copy from p1 side if it didn't
262 262 # already exist on p2 side
263 263 if dst not in p2man:
264 264 copies[dst] = copies1[dst]
265 265 elif dst not in copies1:
266 266 # Copied on p2 side: mark as copy from p2 side if it didn't
267 267 # already exist on p1 side
268 268 if dst not in p1man:
269 269 copies[dst] = copies2[dst]
270 270 else:
271 271 # Copied on both sides: mark as copy from p1 side
272 272 copies[dst] = copies1[dst]
273 273 else:
274 274 copies = copies1
275 275 if r == b.rev():
276 276 return copies
277 277 for c in children[r]:
278 278 childctx = repo[c]
279 279 if r == childctx.p1().rev():
280 280 parent = 1
281 281 childcopies = childctx.p1copies()
282 282 else:
283 283 assert r == childctx.p2().rev()
284 284 parent = 2
285 285 childcopies = childctx.p2copies()
286 286 if not match.always():
287 287 childcopies = {dst: src for dst, src in childcopies.items()
288 288 if match(dst)}
289 289 childcopies = _chain(a, childctx, copies, childcopies)
290 290 heapq.heappush(work, (c, parent, childcopies))
291 291 assert False
292 292
293 293 def _forwardcopies(a, b, match=None):
294 294 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
295 295
296 296 match = a.repo().narrowmatch(match)
297 297 # check for working copy
298 298 if b.rev() is None:
299 299 if a == b.p1():
300 300 # short-circuit to avoid issues with merge states
301 301 return _dirstatecopies(b._repo, match)
302 302
303 303 cm = _committedforwardcopies(a, b.p1(), match)
304 304 # combine copies from dirstate if necessary
305 305 return _chain(a, b, cm, _dirstatecopies(b._repo, match))
306 306 return _committedforwardcopies(a, b, match)
307 307
308 308 def _backwardrenames(a, b, match):
309 309 if a._repo.ui.config('experimental', 'copytrace') == 'off':
310 310 return {}
311 311
312 312 # Even though we're not taking copies into account, 1:n rename situations
313 313 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
314 314 # arbitrarily pick one of the renames.
315 315 # We don't want to pass in "match" here, since that would filter
316 316 # the destination by it. Since we're reversing the copies, we want
317 317 # to filter the source instead.
318 318 f = _forwardcopies(b, a)
319 319 r = {}
320 320 for k, v in sorted(f.iteritems()):
321 321 if match and not match(v):
322 322 continue
323 323 # remove copies
324 324 if v in a:
325 325 continue
326 326 r[v] = k
327 327 return r
328 328
329 329 def pathcopies(x, y, match=None):
330 330 """find {dst@y: src@x} copy mapping for directed compare"""
331 331 repo = x._repo
332 332 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
333 333 if debug:
334 334 repo.ui.debug('debug.copies: searching copies from %s to %s\n'
335 335 % (x, y))
336 336 if x == y or not x or not y:
337 337 return {}
338 338 a = y.ancestor(x)
339 339 if a == x:
340 340 if debug:
341 341 repo.ui.debug('debug.copies: search mode: forward\n')
342 342 return _forwardcopies(x, y, match=match)
343 343 if a == y:
344 344 if debug:
345 345 repo.ui.debug('debug.copies: search mode: backward\n')
346 346 return _backwardrenames(x, y, match=match)
347 347 if debug:
348 348 repo.ui.debug('debug.copies: search mode: combined\n')
349 349 return _chain(x, y, _backwardrenames(x, a, match=match),
350 350 _forwardcopies(a, y, match=match))
351 351
352 352 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
353 353 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
354 354 and c2. This is its own function so extensions can easily wrap this call
355 355 to see what files mergecopies is about to process.
356 356
357 357 Even though c1 and c2 are not used in this function, they are useful in
358 358 other extensions for being able to read the file nodes of the changed files.
359 359
360 360 "baselabel" can be passed to help distinguish the multiple computations
361 361 done in the graft case.
362 362 """
363 363 u1 = sorted(addedinm1 - addedinm2)
364 364 u2 = sorted(addedinm2 - addedinm1)
365 365
366 366 header = " unmatched files in %s"
367 367 if baselabel:
368 368 header += ' (from %s)' % baselabel
369 369 if u1:
370 370 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
371 371 if u2:
372 372 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
373 373
374 374 return u1, u2
375 375
376 376 def _makegetfctx(ctx):
377 377 """return a 'getfctx' function suitable for _checkcopies usage
378 378
379 379 We have to re-setup the function building 'filectx' for each
380 380 '_checkcopies' to ensure the linkrev adjustment is properly setup for
381 381 each. Linkrev adjustment is important to avoid bug in rename
382 382 detection. Moreover, having a proper '_ancestrycontext' setup ensures
383 383 the performance impact of this adjustment is kept limited. Without it,
384 384 each file could do a full dag traversal making the time complexity of
385 385 the operation explode (see issue4537).
386 386
387 387 This function exists here mostly to limit the impact on stable. Feel
388 388 free to refactor on default.
389 389 """
390 390 rev = ctx.rev()
391 391 repo = ctx._repo
392 392 ac = getattr(ctx, '_ancestrycontext', None)
393 393 if ac is None:
394 394 revs = [rev]
395 395 if rev is None:
396 396 revs = [p.rev() for p in ctx.parents()]
397 397 ac = repo.changelog.ancestors(revs, inclusive=True)
398 398 ctx._ancestrycontext = ac
399 399 def makectx(f, n):
400 400 if n in node.wdirfilenodeids: # in a working context?
401 401 if ctx.rev() is None:
402 402 return ctx.filectx(f)
403 403 return repo[None][f]
404 404 fctx = repo.filectx(f, fileid=n)
405 405 # setup only needed for filectx not create from a changectx
406 406 fctx._ancestrycontext = ac
407 407 fctx._descendantrev = rev
408 408 return fctx
409 409 return util.lrucachefunc(makectx)
410 410
411 411 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
412 412 """combine partial copy paths"""
413 413 remainder = {}
414 414 for f in copyfrom:
415 415 if f in copyto:
416 416 finalcopy[copyto[f]] = copyfrom[f]
417 417 del copyto[f]
418 418 for f in incompletediverge:
419 419 assert f not in diverge
420 420 ic = incompletediverge[f]
421 421 if ic[0] in copyto:
422 422 diverge[f] = [copyto[ic[0]], ic[1]]
423 423 else:
424 424 remainder[f] = ic
425 425 return remainder
426 426
427 427 def mergecopies(repo, c1, c2, base):
428 428 """
429 429 The function calling different copytracing algorithms on the basis of config
430 430 which find moves and copies between context c1 and c2 that are relevant for
431 431 merging. 'base' will be used as the merge base.
432 432
433 433 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
434 434 files that were moved/ copied in one merge parent and modified in another.
435 435 For example:
436 436
437 437 o ---> 4 another commit
438 438 |
439 439 | o ---> 3 commit that modifies a.txt
440 440 | /
441 441 o / ---> 2 commit that moves a.txt to b.txt
442 442 |/
443 443 o ---> 1 merge base
444 444
445 445 If we try to rebase revision 3 on revision 4, since there is no a.txt in
446 446 revision 4, and if user have copytrace disabled, we prints the following
447 447 message:
448 448
449 449 ```other changed <file> which local deleted```
450 450
451 451 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
452 452 "dirmove".
453 453
454 454 "copy" is a mapping from destination name -> source name,
455 455 where source is in c1 and destination is in c2 or vice-versa.
456 456
457 457 "movewithdir" is a mapping from source name -> destination name,
458 458 where the file at source present in one context but not the other
459 459 needs to be moved to destination by the merge process, because the
460 460 other context moved the directory it is in.
461 461
462 462 "diverge" is a mapping of source name -> list of destination names
463 463 for divergent renames.
464 464
465 465 "renamedelete" is a mapping of source name -> list of destination
466 466 names for files deleted in c1 that were renamed in c2 or vice-versa.
467 467
468 468 "dirmove" is a mapping of detected source dir -> destination dir renames.
469 469 This is needed for handling changes to new files previously grafted into
470 470 renamed directories.
471 471 """
472 472 # avoid silly behavior for update from empty dir
473 473 if not c1 or not c2 or c1 == c2:
474 474 return {}, {}, {}, {}, {}
475 475
476 476 narrowmatch = c1.repo().narrowmatch()
477 477
478 478 # avoid silly behavior for parent -> working dir
479 479 if c2.node() is None and c1.node() == repo.dirstate.p1():
480 480 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
481 481
482 482 copytracing = repo.ui.config('experimental', 'copytrace')
483 483 boolctrace = stringutil.parsebool(copytracing)
484 484
485 485 # Copy trace disabling is explicitly below the node == p1 logic above
486 486 # because the logic above is required for a simple copy to be kept across a
487 487 # rebase.
488 488 if copytracing == 'heuristics':
489 489 # Do full copytracing if only non-public revisions are involved as
490 490 # that will be fast enough and will also cover the copies which could
491 491 # be missed by heuristics
492 492 if _isfullcopytraceable(repo, c1, base):
493 493 return _fullcopytracing(repo, c1, c2, base)
494 494 return _heuristicscopytracing(repo, c1, c2, base)
495 495 elif boolctrace is False:
496 496 # stringutil.parsebool() returns None when it is unable to parse the
497 497 # value, so we should rely on making sure copytracing is on such cases
498 498 return {}, {}, {}, {}, {}
499 499 else:
500 500 return _fullcopytracing(repo, c1, c2, base)
501 501
502 502 def _isfullcopytraceable(repo, c1, base):
503 503 """ Checks that if base, source and destination are all no-public branches,
504 504 if yes let's use the full copytrace algorithm for increased capabilities
505 505 since it will be fast enough.
506 506
507 507 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
508 508 number of changesets from c1 to base such that if number of changesets are
509 509 more than the limit, full copytracing algorithm won't be used.
510 510 """
511 511 if c1.rev() is None:
512 512 c1 = c1.p1()
513 513 if c1.mutable() and base.mutable():
514 514 sourcecommitlimit = repo.ui.configint('experimental',
515 515 'copytrace.sourcecommitlimit')
516 516 commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
517 517 return commits < sourcecommitlimit
518 518 return False
519 519
520 520 def _fullcopytracing(repo, c1, c2, base):
521 521 """ The full copytracing algorithm which finds all the new files that were
522 522 added from merge base up to the top commit and for each file it checks if
523 523 this file was copied from another file.
524 524
525 525 This is pretty slow when a lot of changesets are involved but will track all
526 526 the copies.
527 527 """
528 528 # In certain scenarios (e.g. graft, update or rebase), base can be
529 529 # overridden We still need to know a real common ancestor in this case We
530 530 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
531 531 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
532 532 # caller may not know if the revision passed in lieu of the CA is a genuine
533 533 # common ancestor or not without explicitly checking it, it's better to
534 534 # determine that here.
535 535 #
536 536 # base.isancestorof(wc) is False, work around that
537 537 _c1 = c1.p1() if c1.rev() is None else c1
538 538 _c2 = c2.p1() if c2.rev() is None else c2
539 539 # an endpoint is "dirty" if it isn't a descendant of the merge base
540 540 # if we have a dirty endpoint, we need to trigger graft logic, and also
541 541 # keep track of which endpoint is dirty
542 542 dirtyc1 = not base.isancestorof(_c1)
543 543 dirtyc2 = not base.isancestorof(_c2)
544 544 graft = dirtyc1 or dirtyc2
545 545 tca = base
546 546 if graft:
547 547 tca = _c1.ancestor(_c2)
548 548
549 549 limit = _findlimit(repo, c1, c2)
550 550 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
551 551
552 552 m1 = c1.manifest()
553 553 m2 = c2.manifest()
554 554 mb = base.manifest()
555 555
556 556 # gather data from _checkcopies:
557 557 # - diverge = record all diverges in this dict
558 558 # - copy = record all non-divergent copies in this dict
559 559 # - fullcopy = record all copies in this dict
560 560 # - incomplete = record non-divergent partial copies here
561 561 # - incompletediverge = record divergent partial copies here
562 562 diverge = {} # divergence data is shared
563 563 incompletediverge = {}
564 564 data1 = {'copy': {},
565 565 'fullcopy': {},
566 566 'incomplete': {},
567 567 'diverge': diverge,
568 568 'incompletediverge': incompletediverge,
569 569 }
570 570 data2 = {'copy': {},
571 571 'fullcopy': {},
572 572 'incomplete': {},
573 573 'diverge': diverge,
574 574 'incompletediverge': incompletediverge,
575 575 }
576 576
577 577 # find interesting file sets from manifests
578 578 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
579 579 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
580 580 bothnew = sorted(addedinm1 & addedinm2)
581 581 if tca == base:
582 582 # unmatched file from base
583 583 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
584 584 u1u, u2u = u1r, u2r
585 585 else:
586 586 # unmatched file from base (DAG rotation in the graft case)
587 587 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
588 588 baselabel='base')
589 589 # unmatched file from topological common ancestors (no DAG rotation)
590 590 # need to recompute this for directory move handling when grafting
591 591 mta = tca.manifest()
592 592 u1u, u2u = _computenonoverlap(repo, c1, c2,
593 593 m1.filesnotin(mta, repo.narrowmatch()),
594 594 m2.filesnotin(mta, repo.narrowmatch()),
595 595 baselabel='topological common ancestor')
596 596
597 597 for f in u1u:
598 598 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
599 599
600 600 for f in u2u:
601 601 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
602 602
603 603 copy = dict(data1['copy'])
604 604 copy.update(data2['copy'])
605 605 fullcopy = dict(data1['fullcopy'])
606 606 fullcopy.update(data2['fullcopy'])
607 607
608 608 if dirtyc1:
609 609 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
610 610 incompletediverge)
611 else:
611 if dirtyc2:
612 612 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
613 613 incompletediverge)
614 614
615 615 renamedelete = {}
616 616 renamedeleteset = set()
617 617 divergeset = set()
618 618 for of, fl in list(diverge.items()):
619 619 if len(fl) == 1 or of in c1 or of in c2:
620 620 del diverge[of] # not actually divergent, or not a rename
621 621 if of not in c1 and of not in c2:
622 622 # renamed on one side, deleted on the other side, but filter
623 623 # out files that have been renamed and then deleted
624 624 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
625 625 renamedeleteset.update(fl) # reverse map for below
626 626 else:
627 627 divergeset.update(fl) # reverse map for below
628 628
629 629 if bothnew:
630 630 repo.ui.debug(" unmatched files new in both:\n %s\n"
631 631 % "\n ".join(bothnew))
632 632 bothdiverge = {}
633 633 bothincompletediverge = {}
634 634 remainder = {}
635 635 both1 = {'copy': {},
636 636 'fullcopy': {},
637 637 'incomplete': {},
638 638 'diverge': bothdiverge,
639 639 'incompletediverge': bothincompletediverge
640 640 }
641 641 both2 = {'copy': {},
642 642 'fullcopy': {},
643 643 'incomplete': {},
644 644 'diverge': bothdiverge,
645 645 'incompletediverge': bothincompletediverge
646 646 }
647 647 for f in bothnew:
648 648 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
649 649 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
650 if dirtyc1:
650 if dirtyc1 and dirtyc2:
651 remainder = _combinecopies(both2['incomplete'], both1['incomplete'],
652 copy, bothdiverge, bothincompletediverge)
653 remainder1 = _combinecopies(both1['incomplete'], both2['incomplete'],
654 copy, bothdiverge, bothincompletediverge)
655 remainder.update(remainder1)
656 elif dirtyc1:
651 657 # incomplete copies may only be found on the "dirty" side for bothnew
652 658 assert not both2['incomplete']
653 659 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
654 660 bothincompletediverge)
655 661 elif dirtyc2:
656 662 assert not both1['incomplete']
657 663 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
658 664 bothincompletediverge)
659 665 else:
660 666 # incomplete copies and divergences can't happen outside grafts
661 667 assert not both1['incomplete']
662 668 assert not both2['incomplete']
663 669 assert not bothincompletediverge
664 670 for f in remainder:
665 671 assert f not in bothdiverge
666 672 ic = remainder[f]
667 673 if ic[0] in (m1 if dirtyc1 else m2):
668 674 # backed-out rename on one side, but watch out for deleted files
669 675 bothdiverge[f] = ic
670 676 for of, fl in bothdiverge.items():
671 677 if len(fl) == 2 and fl[0] == fl[1]:
672 678 copy[fl[0]] = of # not actually divergent, just matching renames
673 679
674 680 if fullcopy and repo.ui.debugflag:
675 681 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
676 682 "% = renamed and deleted):\n")
677 683 for f in sorted(fullcopy):
678 684 note = ""
679 685 if f in copy:
680 686 note += "*"
681 687 if f in divergeset:
682 688 note += "!"
683 689 if f in renamedeleteset:
684 690 note += "%"
685 691 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
686 692 note))
687 693 del divergeset
688 694
689 695 if not fullcopy:
690 696 return copy, {}, diverge, renamedelete, {}
691 697
692 698 repo.ui.debug(" checking for directory renames\n")
693 699
694 700 # generate a directory move map
695 701 d1, d2 = c1.dirs(), c2.dirs()
696 702 # Hack for adding '', which is not otherwise added, to d1 and d2
697 703 d1.addpath('/')
698 704 d2.addpath('/')
699 705 invalid = set()
700 706 dirmove = {}
701 707
702 708 # examine each file copy for a potential directory move, which is
703 709 # when all the files in a directory are moved to a new directory
704 710 for dst, src in fullcopy.iteritems():
705 711 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
706 712 if dsrc in invalid:
707 713 # already seen to be uninteresting
708 714 continue
709 715 elif dsrc in d1 and ddst in d1:
710 716 # directory wasn't entirely moved locally
711 717 invalid.add(dsrc)
712 718 elif dsrc in d2 and ddst in d2:
713 719 # directory wasn't entirely moved remotely
714 720 invalid.add(dsrc)
715 721 elif dsrc in dirmove and dirmove[dsrc] != ddst:
716 722 # files from the same directory moved to two different places
717 723 invalid.add(dsrc)
718 724 else:
719 725 # looks good so far
720 726 dirmove[dsrc] = ddst
721 727
722 728 for i in invalid:
723 729 if i in dirmove:
724 730 del dirmove[i]
725 731 del d1, d2, invalid
726 732
727 733 if not dirmove:
728 734 return copy, {}, diverge, renamedelete, {}
729 735
730 736 dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
731 737
732 738 for d in dirmove:
733 739 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
734 740 (d, dirmove[d]))
735 741
736 742 movewithdir = {}
737 743 # check unaccounted nonoverlapping files against directory moves
738 744 for f in u1r + u2r:
739 745 if f not in fullcopy:
740 746 for d in dirmove:
741 747 if f.startswith(d):
742 748 # new file added in a directory that was moved, move it
743 749 df = dirmove[d] + f[len(d):]
744 750 if df not in copy:
745 751 movewithdir[f] = df
746 752 repo.ui.debug((" pending file src: '%s' -> "
747 753 "dst: '%s'\n") % (f, df))
748 754 break
749 755
750 756 return copy, movewithdir, diverge, renamedelete, dirmove
751 757
752 758 def _heuristicscopytracing(repo, c1, c2, base):
753 759 """ Fast copytracing using filename heuristics
754 760
755 761 Assumes that moves or renames are of following two types:
756 762
757 763 1) Inside a directory only (same directory name but different filenames)
758 764 2) Move from one directory to another
759 765 (same filenames but different directory names)
760 766
761 767 Works only when there are no merge commits in the "source branch".
762 768 Source branch is commits from base up to c2 not including base.
763 769
764 770 If merge is involved it fallbacks to _fullcopytracing().
765 771
766 772 Can be used by setting the following config:
767 773
768 774 [experimental]
769 775 copytrace = heuristics
770 776
771 777 In some cases the copy/move candidates found by heuristics can be very large
772 778 in number and that will make the algorithm slow. The number of possible
773 779 candidates to check can be limited by using the config
774 780 `experimental.copytrace.movecandidateslimit` which defaults to 100.
775 781 """
776 782
777 783 if c1.rev() is None:
778 784 c1 = c1.p1()
779 785 if c2.rev() is None:
780 786 c2 = c2.p1()
781 787
782 788 copies = {}
783 789
784 790 changedfiles = set()
785 791 m1 = c1.manifest()
786 792 if not repo.revs('%d::%d', base.rev(), c2.rev()):
787 793 # If base is not in c2 branch, we switch to fullcopytracing
788 794 repo.ui.debug("switching to full copytracing as base is not "
789 795 "an ancestor of c2\n")
790 796 return _fullcopytracing(repo, c1, c2, base)
791 797
792 798 ctx = c2
793 799 while ctx != base:
794 800 if len(ctx.parents()) == 2:
795 801 # To keep things simple let's not handle merges
796 802 repo.ui.debug("switching to full copytracing because of merges\n")
797 803 return _fullcopytracing(repo, c1, c2, base)
798 804 changedfiles.update(ctx.files())
799 805 ctx = ctx.p1()
800 806
801 807 cp = _forwardcopies(base, c2)
802 808 for dst, src in cp.iteritems():
803 809 if src in m1:
804 810 copies[dst] = src
805 811
806 812 # file is missing if it isn't present in the destination, but is present in
807 813 # the base and present in the source.
808 814 # Presence in the base is important to exclude added files, presence in the
809 815 # source is important to exclude removed files.
810 816 filt = lambda f: f not in m1 and f in base and f in c2
811 817 missingfiles = [f for f in changedfiles if filt(f)]
812 818
813 819 if missingfiles:
814 820 basenametofilename = collections.defaultdict(list)
815 821 dirnametofilename = collections.defaultdict(list)
816 822
817 823 for f in m1.filesnotin(base.manifest()):
818 824 basename = os.path.basename(f)
819 825 dirname = os.path.dirname(f)
820 826 basenametofilename[basename].append(f)
821 827 dirnametofilename[dirname].append(f)
822 828
823 829 for f in missingfiles:
824 830 basename = os.path.basename(f)
825 831 dirname = os.path.dirname(f)
826 832 samebasename = basenametofilename[basename]
827 833 samedirname = dirnametofilename[dirname]
828 834 movecandidates = samebasename + samedirname
829 835 # f is guaranteed to be present in c2, that's why
830 836 # c2.filectx(f) won't fail
831 837 f2 = c2.filectx(f)
832 838 # we can have a lot of candidates which can slow down the heuristics
833 839 # config value to limit the number of candidates moves to check
834 840 maxcandidates = repo.ui.configint('experimental',
835 841 'copytrace.movecandidateslimit')
836 842
837 843 if len(movecandidates) > maxcandidates:
838 844 repo.ui.status(_("skipping copytracing for '%s', more "
839 845 "candidates than the limit: %d\n")
840 846 % (f, len(movecandidates)))
841 847 continue
842 848
843 849 for candidate in movecandidates:
844 850 f1 = c1.filectx(candidate)
845 851 if _related(f1, f2):
846 852 # if there are a few related copies then we'll merge
847 853 # changes into all of them. This matches the behaviour
848 854 # of upstream copytracing
849 855 copies[candidate] = f
850 856
851 857 return copies, {}, {}, {}, {}
852 858
853 859 def _related(f1, f2):
854 860 """return True if f1 and f2 filectx have a common ancestor
855 861
856 862 Walk back to common ancestor to see if the two files originate
857 863 from the same file. Since workingfilectx's rev() is None it messes
858 864 up the integer comparison logic, hence the pre-step check for
859 865 None (f1 and f2 can only be workingfilectx's initially).
860 866 """
861 867
862 868 if f1 == f2:
863 869 return True # a match
864 870
865 871 g1, g2 = f1.ancestors(), f2.ancestors()
866 872 try:
867 873 f1r, f2r = f1.linkrev(), f2.linkrev()
868 874
869 875 if f1r is None:
870 876 f1 = next(g1)
871 877 if f2r is None:
872 878 f2 = next(g2)
873 879
874 880 while True:
875 881 f1r, f2r = f1.linkrev(), f2.linkrev()
876 882 if f1r > f2r:
877 883 f1 = next(g1)
878 884 elif f2r > f1r:
879 885 f2 = next(g2)
880 886 else: # f1 and f2 point to files in the same linkrev
881 887 return f1 == f2 # true if they point to the same file
882 888 except StopIteration:
883 889 return False
884 890
885 891 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
886 892 """
887 893 check possible copies of f from msrc to mdst
888 894
889 895 srcctx = starting context for f in msrc
890 896 dstctx = destination context for f in mdst
891 897 f = the filename to check (as in msrc)
892 898 base = the changectx used as a merge base
893 899 tca = topological common ancestor for graft-like scenarios
894 900 remotebase = True if base is outside tca::srcctx, False otherwise
895 901 limit = the rev number to not search beyond
896 902 data = dictionary of dictionary to store copy data. (see mergecopies)
897 903
898 904 note: limit is only an optimization, and provides no guarantee that
899 905 irrelevant revisions will not be visited
900 906 there is no easy way to make this algorithm stop in a guaranteed way
901 907 once it "goes behind a certain revision".
902 908 """
903 909
904 910 msrc = srcctx.manifest()
905 911 mdst = dstctx.manifest()
906 912 mb = base.manifest()
907 913 mta = tca.manifest()
908 914 # Might be true if this call is about finding backward renames,
909 915 # This happens in the case of grafts because the DAG is then rotated.
910 916 # If the file exists in both the base and the source, we are not looking
911 917 # for a rename on the source side, but on the part of the DAG that is
912 918 # traversed backwards.
913 919 #
914 920 # In the case there is both backward and forward renames (before and after
915 921 # the base) this is more complicated as we must detect a divergence.
916 922 # We use 'backwards = False' in that case.
917 923 backwards = not remotebase and base != tca and f in mb
918 924 getsrcfctx = _makegetfctx(srcctx)
919 925 getdstfctx = _makegetfctx(dstctx)
920 926
921 927 if msrc[f] == mb.get(f) and not remotebase:
922 928 # Nothing to merge
923 929 return
924 930
925 931 of = None
926 932 seen = {f}
927 933 for oc in getsrcfctx(f, msrc[f]).ancestors():
928 934 of = oc.path()
929 935 if of in seen:
930 936 # check limit late - grab last rename before
931 937 if oc.linkrev() < limit:
932 938 break
933 939 continue
934 940 seen.add(of)
935 941
936 942 # remember for dir rename detection
937 943 if backwards:
938 944 data['fullcopy'][of] = f # grafting backwards through renames
939 945 else:
940 946 data['fullcopy'][f] = of
941 947 if of not in mdst:
942 948 continue # no match, keep looking
943 949 if mdst[of] == mb.get(of):
944 950 return # no merge needed, quit early
945 951 c2 = getdstfctx(of, mdst[of])
946 952 # c2 might be a plain new file on added on destination side that is
947 953 # unrelated to the droids we are looking for.
948 954 cr = _related(oc, c2)
949 955 if cr and (of == f or of == c2.path()): # non-divergent
950 956 if backwards:
951 957 data['copy'][of] = f
952 958 elif of in mb:
953 959 data['copy'][f] = of
954 960 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
955 961 data['copy'][of] = f
956 962 del data['fullcopy'][f]
957 963 data['fullcopy'][of] = f
958 964 else: # divergence w.r.t. graft CA on one side of topological CA
959 965 for sf in seen:
960 966 if sf in mb:
961 967 assert sf not in data['diverge']
962 968 data['diverge'][sf] = [f, of]
963 969 break
964 970 return
965 971
966 972 if of in mta:
967 973 if backwards or remotebase:
968 974 data['incomplete'][of] = f
969 975 else:
970 976 for sf in seen:
971 977 if sf in mb:
972 978 if tca == base:
973 979 data['diverge'].setdefault(sf, []).append(f)
974 980 else:
975 981 data['incompletediverge'][sf] = [of, f]
976 982 return
977 983
978 984 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
979 985 """reproduce copies from fromrev to rev in the dirstate
980 986
981 987 If skiprev is specified, it's a revision that should be used to
982 988 filter copy records. Any copies that occur between fromrev and
983 989 skiprev will not be duplicated, even if they appear in the set of
984 990 copies between fromrev and rev.
985 991 """
986 992 exclude = {}
987 993 ctraceconfig = repo.ui.config('experimental', 'copytrace')
988 994 bctrace = stringutil.parsebool(ctraceconfig)
989 995 if (skiprev is not None and
990 996 (ctraceconfig == 'heuristics' or bctrace or bctrace is None)):
991 997 # copytrace='off' skips this line, but not the entire function because
992 998 # the line below is O(size of the repo) during a rebase, while the rest
993 999 # of the function is much faster (and is required for carrying copy
994 1000 # metadata across the rebase anyway).
995 1001 exclude = pathcopies(repo[fromrev], repo[skiprev])
996 1002 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
997 1003 # copies.pathcopies returns backward renames, so dst might not
998 1004 # actually be in the dirstate
999 1005 if dst in exclude:
1000 1006 continue
1001 1007 wctx[dst].markcopied(src)
@@ -1,633 +1,633 b''
1 1 #testcases filelog compatibility
2 2
3 3 $ cat >> $HGRCPATH << EOF
4 4 > [extensions]
5 5 > rebase=
6 6 > [alias]
7 7 > l = log -G -T '{rev} {desc}\n{files}\n'
8 8 > EOF
9 9
10 10 #if compatibility
11 11 $ cat >> $HGRCPATH << EOF
12 12 > [experimental]
13 13 > copies.read-from = compatibility
14 14 > EOF
15 15 #endif
16 16
17 17 $ REPONUM=0
18 18 $ newrepo() {
19 19 > cd $TESTTMP
20 20 > REPONUM=`expr $REPONUM + 1`
21 21 > hg init repo-$REPONUM
22 22 > cd repo-$REPONUM
23 23 > }
24 24
25 25 Simple rename case
26 26 $ newrepo
27 27 $ echo x > x
28 28 $ hg ci -Aqm 'add x'
29 29 $ hg mv x y
30 30 $ hg debugp1copies
31 31 x -> y
32 32 $ hg debugp2copies
33 33 $ hg ci -m 'rename x to y'
34 34 $ hg l
35 35 @ 1 rename x to y
36 36 | x y
37 37 o 0 add x
38 38 x
39 39 $ hg debugp1copies -r 1
40 40 x -> y
41 41 $ hg debugpathcopies 0 1
42 42 x -> y
43 43 $ hg debugpathcopies 1 0
44 44 y -> x
45 45 Test filtering copies by path. We do filtering by destination.
46 46 $ hg debugpathcopies 0 1 x
47 47 $ hg debugpathcopies 1 0 x
48 48 y -> x
49 49 $ hg debugpathcopies 0 1 y
50 50 x -> y
51 51 $ hg debugpathcopies 1 0 y
52 52
53 53 Copy a file onto another file
54 54 $ newrepo
55 55 $ echo x > x
56 56 $ echo y > y
57 57 $ hg ci -Aqm 'add x and y'
58 58 $ hg cp -f x y
59 59 $ hg debugp1copies
60 60 x -> y
61 61 $ hg debugp2copies
62 62 $ hg ci -m 'copy x onto y'
63 63 $ hg l
64 64 @ 1 copy x onto y
65 65 | y
66 66 o 0 add x and y
67 67 x y
68 68 $ hg debugp1copies -r 1
69 69 x -> y
70 70 Incorrectly doesn't show the rename
71 71 $ hg debugpathcopies 0 1
72 72
73 73 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
74 74 produce a new filelog entry. The changeset's "files" entry should still list the file.
75 75 $ newrepo
76 76 $ echo x > x
77 77 $ echo x > x2
78 78 $ hg ci -Aqm 'add x and x2 with same content'
79 79 $ hg cp -f x x2
80 80 $ hg ci -m 'copy x onto x2'
81 81 $ hg l
82 82 @ 1 copy x onto x2
83 83 | x2
84 84 o 0 add x and x2 with same content
85 85 x x2
86 86 $ hg debugp1copies -r 1
87 87 x -> x2
88 88 Incorrectly doesn't show the rename
89 89 $ hg debugpathcopies 0 1
90 90
91 91 Copy a file, then delete destination, then copy again. This does not create a new filelog entry.
92 92 $ newrepo
93 93 $ echo x > x
94 94 $ hg ci -Aqm 'add x'
95 95 $ hg cp x y
96 96 $ hg ci -m 'copy x to y'
97 97 $ hg rm y
98 98 $ hg ci -m 'remove y'
99 99 $ hg cp -f x y
100 100 $ hg ci -m 'copy x onto y (again)'
101 101 $ hg l
102 102 @ 3 copy x onto y (again)
103 103 | y
104 104 o 2 remove y
105 105 | y
106 106 o 1 copy x to y
107 107 | y
108 108 o 0 add x
109 109 x
110 110 $ hg debugp1copies -r 3
111 111 x -> y
112 112 $ hg debugpathcopies 0 3
113 113 x -> y
114 114
115 115 Rename file in a loop: x->y->z->x
116 116 $ newrepo
117 117 $ echo x > x
118 118 $ hg ci -Aqm 'add x'
119 119 $ hg mv x y
120 120 $ hg debugp1copies
121 121 x -> y
122 122 $ hg debugp2copies
123 123 $ hg ci -m 'rename x to y'
124 124 $ hg mv y z
125 125 $ hg ci -m 'rename y to z'
126 126 $ hg mv z x
127 127 $ hg ci -m 'rename z to x'
128 128 $ hg l
129 129 @ 3 rename z to x
130 130 | x z
131 131 o 2 rename y to z
132 132 | y z
133 133 o 1 rename x to y
134 134 | x y
135 135 o 0 add x
136 136 x
137 137 $ hg debugpathcopies 0 3
138 138
139 139 Copy x to y, then remove y, then add back y. With copy metadata in the changeset, this could easily
140 140 end up reporting y as copied from x (if we don't unmark it as a copy when it's removed).
141 141 $ newrepo
142 142 $ echo x > x
143 143 $ hg ci -Aqm 'add x'
144 144 $ hg mv x y
145 145 $ hg ci -m 'rename x to y'
146 146 $ hg rm y
147 147 $ hg ci -qm 'remove y'
148 148 $ echo x > y
149 149 $ hg ci -Aqm 'add back y'
150 150 $ hg l
151 151 @ 3 add back y
152 152 | y
153 153 o 2 remove y
154 154 | y
155 155 o 1 rename x to y
156 156 | x y
157 157 o 0 add x
158 158 x
159 159 $ hg debugp1copies -r 3
160 160 $ hg debugpathcopies 0 3
161 161
162 162 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
163 163 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
164 164 to the first commit that added the file. We should still report the copy as being from x2.
165 165 $ newrepo
166 166 $ echo x > x
167 167 $ echo x > x2
168 168 $ hg ci -Aqm 'add x and x2 with same content'
169 169 $ hg cp x z
170 170 $ hg ci -qm 'copy x to z'
171 171 $ hg rm z
172 172 $ hg ci -m 'remove z'
173 173 $ hg cp x2 z
174 174 $ hg ci -m 'copy x2 to z'
175 175 $ hg l
176 176 @ 3 copy x2 to z
177 177 | z
178 178 o 2 remove z
179 179 | z
180 180 o 1 copy x to z
181 181 | z
182 182 o 0 add x and x2 with same content
183 183 x x2
184 184 $ hg debugp1copies -r 3
185 185 x2 -> z
186 186 $ hg debugpathcopies 0 3
187 187 x2 -> z
188 188
189 189 Create x and y, then rename them both to the same name, but on different sides of a fork
190 190 $ newrepo
191 191 $ echo x > x
192 192 $ echo y > y
193 193 $ hg ci -Aqm 'add x and y'
194 194 $ hg mv x z
195 195 $ hg ci -qm 'rename x to z'
196 196 $ hg co -q 0
197 197 $ hg mv y z
198 198 $ hg ci -qm 'rename y to z'
199 199 $ hg l
200 200 @ 2 rename y to z
201 201 | y z
202 202 | o 1 rename x to z
203 203 |/ x z
204 204 o 0 add x and y
205 205 x y
206 206 $ hg debugpathcopies 1 2
207 207 z -> x
208 208 y -> z
209 209
210 210 Fork renames x to y on one side and removes x on the other
211 211 $ newrepo
212 212 $ echo x > x
213 213 $ hg ci -Aqm 'add x'
214 214 $ hg mv x y
215 215 $ hg ci -m 'rename x to y'
216 216 $ hg co -q 0
217 217 $ hg rm x
218 218 $ hg ci -m 'remove x'
219 219 created new head
220 220 $ hg l
221 221 @ 2 remove x
222 222 | x
223 223 | o 1 rename x to y
224 224 |/ x y
225 225 o 0 add x
226 226 x
227 227 $ hg debugpathcopies 1 2
228 228
229 229 Copies via null revision (there shouldn't be any)
230 230 $ newrepo
231 231 $ echo x > x
232 232 $ hg ci -Aqm 'add x'
233 233 $ hg cp x y
234 234 $ hg ci -m 'copy x to y'
235 235 $ hg co -q null
236 236 $ echo x > x
237 237 $ hg ci -Aqm 'add x (again)'
238 238 $ hg l
239 239 @ 2 add x (again)
240 240 x
241 241 o 1 copy x to y
242 242 | y
243 243 o 0 add x
244 244 x
245 245 $ hg debugpathcopies 1 2
246 246 $ hg debugpathcopies 2 1
247 247
248 248 Merge rename from other branch
249 249 $ newrepo
250 250 $ echo x > x
251 251 $ hg ci -Aqm 'add x'
252 252 $ hg mv x y
253 253 $ hg ci -m 'rename x to y'
254 254 $ hg co -q 0
255 255 $ echo z > z
256 256 $ hg ci -Aqm 'add z'
257 257 $ hg merge -q 1
258 258 $ hg debugp1copies
259 259 $ hg debugp2copies
260 260 $ hg ci -m 'merge rename from p2'
261 261 $ hg l
262 262 @ 3 merge rename from p2
263 263 |\ x
264 264 | o 2 add z
265 265 | | z
266 266 o | 1 rename x to y
267 267 |/ x y
268 268 o 0 add x
269 269 x
270 270 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
271 271 merges, so...
272 272 $ hg debugp1copies -r 3
273 273 $ hg debugp2copies -r 3
274 274 $ hg debugpathcopies 0 3
275 275 x -> y
276 276 $ hg debugpathcopies 1 2
277 277 y -> x
278 278 $ hg debugpathcopies 1 3
279 279 $ hg debugpathcopies 2 3
280 280 x -> y
281 281
282 282 Copy file from either side in a merge
283 283 $ newrepo
284 284 $ echo x > x
285 285 $ hg ci -Aqm 'add x'
286 286 $ hg co -q null
287 287 $ echo y > y
288 288 $ hg ci -Aqm 'add y'
289 289 $ hg merge -q 0
290 290 $ hg cp y z
291 291 $ hg debugp1copies
292 292 y -> z
293 293 $ hg debugp2copies
294 294 $ hg ci -m 'copy file from p1 in merge'
295 295 $ hg co -q 1
296 296 $ hg merge -q 0
297 297 $ hg cp x z
298 298 $ hg debugp1copies
299 299 $ hg debugp2copies
300 300 x -> z
301 301 $ hg ci -qm 'copy file from p2 in merge'
302 302 $ hg l
303 303 @ 3 copy file from p2 in merge
304 304 |\ z
305 305 +---o 2 copy file from p1 in merge
306 306 | |/ z
307 307 | o 1 add y
308 308 | y
309 309 o 0 add x
310 310 x
311 311 $ hg debugp1copies -r 2
312 312 y -> z
313 313 $ hg debugp2copies -r 2
314 314 $ hg debugpathcopies 1 2
315 315 y -> z
316 316 $ hg debugpathcopies 0 2
317 317 $ hg debugp1copies -r 3
318 318 $ hg debugp2copies -r 3
319 319 x -> z
320 320 $ hg debugpathcopies 1 3
321 321 $ hg debugpathcopies 0 3
322 322 x -> z
323 323
324 324 Copy file that exists on both sides of the merge, same content on both sides
325 325 $ newrepo
326 326 $ echo x > x
327 327 $ hg ci -Aqm 'add x on branch 1'
328 328 $ hg co -q null
329 329 $ echo x > x
330 330 $ hg ci -Aqm 'add x on branch 2'
331 331 $ hg merge -q 0
332 332 $ hg cp x z
333 333 $ hg debugp1copies
334 334 x -> z
335 335 $ hg debugp2copies
336 336 $ hg ci -qm 'merge'
337 337 $ hg l
338 338 @ 2 merge
339 339 |\ z
340 340 | o 1 add x on branch 2
341 341 | x
342 342 o 0 add x on branch 1
343 343 x
344 344 $ hg debugp1copies -r 2
345 345 x -> z
346 346 $ hg debugp2copies -r 2
347 347 It's a little weird that it shows up on both sides
348 348 $ hg debugpathcopies 1 2
349 349 x -> z
350 350 $ hg debugpathcopies 0 2
351 351 x -> z (filelog !)
352 352
353 353 Copy file that exists on both sides of the merge, different content
354 354 $ newrepo
355 355 $ echo branch1 > x
356 356 $ hg ci -Aqm 'add x on branch 1'
357 357 $ hg co -q null
358 358 $ echo branch2 > x
359 359 $ hg ci -Aqm 'add x on branch 2'
360 360 $ hg merge -q 0
361 361 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
362 362 [1]
363 363 $ echo resolved > x
364 364 $ hg resolve -m x
365 365 (no more unresolved files)
366 366 $ hg cp x z
367 367 $ hg debugp1copies
368 368 x -> z
369 369 $ hg debugp2copies
370 370 $ hg ci -qm 'merge'
371 371 $ hg l
372 372 @ 2 merge
373 373 |\ x z
374 374 | o 1 add x on branch 2
375 375 | x
376 376 o 0 add x on branch 1
377 377 x
378 378 $ hg debugp1copies -r 2
379 379 $ hg debugp2copies -r 2
380 380 x -> z
381 381 $ hg debugpathcopies 1 2
382 382 $ hg debugpathcopies 0 2
383 383 x -> z
384 384
385 385 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
386 386 of the merge to the merge should include the copy from the other side.
387 387 $ newrepo
388 388 $ echo x > x
389 389 $ hg ci -Aqm 'add x'
390 390 $ hg cp x y
391 391 $ hg ci -qm 'copy x to y'
392 392 $ hg co -q 0
393 393 $ hg cp x z
394 394 $ hg ci -qm 'copy x to z'
395 395 $ hg merge -q 1
396 396 $ hg ci -m 'merge copy x->y and copy x->z'
397 397 $ hg l
398 398 @ 3 merge copy x->y and copy x->z
399 399 |\
400 400 | o 2 copy x to z
401 401 | | z
402 402 o | 1 copy x to y
403 403 |/ y
404 404 o 0 add x
405 405 x
406 406 $ hg debugp1copies -r 3
407 407 $ hg debugp2copies -r 3
408 408 $ hg debugpathcopies 2 3
409 409 x -> y
410 410 $ hg debugpathcopies 1 3
411 411 x -> z
412 412
413 413 Copy x to y on one side of merge, create y and rename to z on the other side. Pathcopies from the
414 414 first side should not include the y->z rename since y didn't exist in the merge base.
415 415 $ newrepo
416 416 $ echo x > x
417 417 $ hg ci -Aqm 'add x'
418 418 $ hg cp x y
419 419 $ hg ci -qm 'copy x to y'
420 420 $ hg co -q 0
421 421 $ echo y > y
422 422 $ hg ci -Aqm 'add y'
423 423 $ hg mv y z
424 424 $ hg ci -m 'rename y to z'
425 425 $ hg merge -q 1
426 426 $ hg ci -m 'merge'
427 427 $ hg l
428 428 @ 4 merge
429 429 |\
430 430 | o 3 rename y to z
431 431 | | y z
432 432 | o 2 add y
433 433 | | y
434 434 o | 1 copy x to y
435 435 |/ y
436 436 o 0 add x
437 437 x
438 438 $ hg debugp1copies -r 3
439 439 y -> z
440 440 $ hg debugp2copies -r 3
441 441 $ hg debugpathcopies 2 3
442 442 y -> z
443 443 $ hg debugpathcopies 1 3
444 444
445 445 Create x and y, then rename x to z on one side of merge, and rename y to z and modify z on the
446 446 other side.
447 447 $ newrepo
448 448 $ echo x > x
449 449 $ echo y > y
450 450 $ hg ci -Aqm 'add x and y'
451 451 $ hg mv x z
452 452 $ hg ci -qm 'rename x to z'
453 453 $ hg co -q 0
454 454 $ hg mv y z
455 455 $ hg ci -qm 'rename y to z'
456 456 $ echo z >> z
457 457 $ hg ci -m 'modify z'
458 458 $ hg merge -q 1
459 459 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
460 460 [1]
461 461 $ echo z > z
462 462 $ hg resolve -qm z
463 463 $ hg ci -m 'merge 1 into 3'
464 464 Try merging the other direction too
465 465 $ hg co -q 1
466 466 $ hg merge -q 3
467 467 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
468 468 [1]
469 469 $ echo z > z
470 470 $ hg resolve -qm z
471 471 $ hg ci -m 'merge 3 into 1'
472 472 created new head
473 473 $ hg l
474 474 @ 5 merge 3 into 1
475 475 |\ y z
476 476 +---o 4 merge 1 into 3
477 477 | |/ x z
478 478 | o 3 modify z
479 479 | | z
480 480 | o 2 rename y to z
481 481 | | y z
482 482 o | 1 rename x to z
483 483 |/ x z
484 484 o 0 add x and y
485 485 x y
486 486 $ hg debugpathcopies 1 4
487 487 $ hg debugpathcopies 2 4
488 488 $ hg debugpathcopies 0 4
489 489 x -> z (filelog !)
490 490 y -> z (compatibility !)
491 491 $ hg debugpathcopies 1 5
492 492 $ hg debugpathcopies 2 5
493 493 $ hg debugpathcopies 0 5
494 494 x -> z
495 495
496 496
497 497 Test for a case in fullcopytracing algorithm where both the merging csets are
498 498 "dirty"; where a dirty cset means that cset is descendant of merge base. This
499 499 test reflect that for this particular case this algorithm correctly find the copies:
500 500
501 501 $ cat >> $HGRCPATH << EOF
502 502 > [experimental]
503 503 > evolution.createmarkers=True
504 504 > evolution.allowunstable=True
505 505 > EOF
506 506
507 507 $ newrepo
508 508 $ echo a > a
509 509 $ hg add a
510 510 $ hg ci -m "added a"
511 511 $ echo b > b
512 512 $ hg add b
513 513 $ hg ci -m "added b"
514 514
515 515 $ hg mv b b1
516 516 $ hg ci -m "rename b to b1"
517 517
518 518 $ hg up ".^"
519 519 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
520 520 $ echo d > d
521 521 $ hg add d
522 522 $ hg ci -m "added d"
523 523 created new head
524 524
525 525 $ echo baba >> b
526 526 $ hg ci --amend -m "added d, modified b"
527 527
528 528 $ hg l --hidden
529 529 @ 4 added d, modified b
530 530 | b d
531 531 | x 3 added d
532 532 |/ d
533 533 | o 2 rename b to b1
534 534 |/ b b1
535 535 o 1 added b
536 536 | b
537 537 o 0 added a
538 538 a
539 539
540 540 Grafting revision 4 on top of revision 2, showing that it respect the rename:
541 541
542 542 $ hg up 2 -q
543 543 $ hg graft -r 4 --base 3 --hidden
544 544 grafting 4:af28412ec03c "added d, modified b" (tip)
545 545 merging b1 and b to b1
546 546
547 547 $ hg l -l1 -p
548 548 @ 5 added d, modified b
549 549 | b1
550 550 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1
551 551 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
552 552 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
553 553 @@ -1,1 +1,2 @@
554 554 b
555 555 +baba
556 556
557 Test which demonstrate that fullcopytracing algorithm can fail to handle a case when both the csets are dirty
558 ----------------------------------------------------------------------------------------------------------
557 Test to make sure that fullcopytracing algorithm don't fail when both the merging csets are dirty
558 (a dirty cset is one who is not the descendant of merge base)
559 -------------------------------------------------------------------------------------------------
559 560
560 561 $ newrepo
561 562 $ echo a > a
562 563 $ hg add a
563 564 $ hg ci -m "added a"
564 565 $ echo b > b
565 566 $ hg add b
566 567 $ hg ci -m "added b"
567 568
568 569 $ echo foobar > willconflict
569 570 $ hg add willconflict
570 571 $ hg ci -m "added willconflict"
571 572 $ echo c > c
572 573 $ hg add c
573 574 $ hg ci -m "added c"
574 575
575 576 $ hg l
576 577 @ 3 added c
577 578 | c
578 579 o 2 added willconflict
579 580 | willconflict
580 581 o 1 added b
581 582 | b
582 583 o 0 added a
583 584 a
584 585
585 586 $ hg up ".^^"
586 587 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
587 588 $ echo d > d
588 589 $ hg add d
589 590 $ hg ci -m "added d"
590 591 created new head
591 592
592 593 $ echo barfoo > willconflict
593 594 $ hg add willconflict
594 595 $ hg ci --amend -m "added willconflict and d"
595 596
596 597 $ hg l
597 598 @ 5 added willconflict and d
598 599 | d willconflict
599 600 | o 3 added c
600 601 | | c
601 602 | o 2 added willconflict
602 603 |/ willconflict
603 604 o 1 added b
604 605 | b
605 606 o 0 added a
606 607 a
607 608
608 609 $ hg rebase -r . -d 2 -t :other
609 610 rebasing 5:5018b1509e94 "added willconflict and d" (tip)
610 611
611 612 $ hg up 3 -q
612 613 $ hg l --hidden
613 614 o 6 added willconflict and d
614 615 | d willconflict
615 616 | x 5 added willconflict and d
616 617 | | d willconflict
617 618 | | x 4 added d
618 619 | |/ d
619 620 +---@ 3 added c
620 621 | | c
621 622 o | 2 added willconflict
622 623 |/ willconflict
623 624 o 1 added b
624 625 | b
625 626 o 0 added a
626 627 a
627 628
628 629 Now if we trigger a merge between cset revision 3 and 6 using base revision 4, in this case
629 630 both the merging csets will be dirty as no one is descendent of base revision:
630 631
631 $ hg graft -r 6 --base 4 --hidden 2>&1 | grep "AssertionError"
632 AssertionError
633
632 $ hg graft -r 6 --base 4 --hidden -t :other
633 grafting 6:99802e4f1e46 "added willconflict and d" (tip)
General Comments 0
You need to be logged in to leave comments. Login now