##// END OF EJS Templates
copies: fix misaligned lines
Gábor Stefanik -
r33882:edf503e5 default
parent child Browse files
Show More
@@ -1,743 +1,743 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 heapq
11 11
12 12 from . import (
13 13 match as matchmod,
14 14 node,
15 15 pathutil,
16 16 scmutil,
17 17 util,
18 18 )
19 19
20 20 def _findlimit(repo, a, b):
21 21 """
22 22 Find the last revision that needs to be checked to ensure that a full
23 23 transitive closure for file copies can be properly calculated.
24 24 Generally, this means finding the earliest revision number that's an
25 25 ancestor of a or b but not both, except when a or b is a direct descendent
26 26 of the other, in which case we can return the minimum revnum of a and b.
27 27 None if no such revision exists.
28 28 """
29 29
30 30 # basic idea:
31 31 # - mark a and b with different sides
32 32 # - if a parent's children are all on the same side, the parent is
33 33 # on that side, otherwise it is on no side
34 34 # - walk the graph in topological order with the help of a heap;
35 35 # - add unseen parents to side map
36 36 # - clear side of any parent that has children on different sides
37 37 # - track number of interesting revs that might still be on a side
38 38 # - track the lowest interesting rev seen
39 39 # - quit when interesting revs is zero
40 40
41 41 cl = repo.changelog
42 42 working = len(cl) # pseudo rev for the working directory
43 43 if a is None:
44 44 a = working
45 45 if b is None:
46 46 b = working
47 47
48 48 side = {a: -1, b: 1}
49 49 visit = [-a, -b]
50 50 heapq.heapify(visit)
51 51 interesting = len(visit)
52 52 hascommonancestor = False
53 53 limit = working
54 54
55 55 while interesting:
56 56 r = -heapq.heappop(visit)
57 57 if r == working:
58 58 parents = [cl.rev(p) for p in repo.dirstate.parents()]
59 59 else:
60 60 parents = cl.parentrevs(r)
61 61 for p in parents:
62 62 if p < 0:
63 63 continue
64 64 if p not in side:
65 65 # first time we see p; add it to visit
66 66 side[p] = side[r]
67 67 if side[p]:
68 68 interesting += 1
69 69 heapq.heappush(visit, -p)
70 70 elif side[p] and side[p] != side[r]:
71 71 # p was interesting but now we know better
72 72 side[p] = 0
73 73 interesting -= 1
74 74 hascommonancestor = True
75 75 if side[r]:
76 76 limit = r # lowest rev visited
77 77 interesting -= 1
78 78
79 79 if not hascommonancestor:
80 80 return None
81 81
82 82 # Consider the following flow (see test-commit-amend.t under issue4405):
83 83 # 1/ File 'a0' committed
84 84 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
85 85 # 3/ Move back to first commit
86 86 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
87 87 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
88 88 #
89 89 # During the amend in step five, we will be in this state:
90 90 #
91 91 # @ 3 temporary amend commit for a1-amend
92 92 # |
93 93 # o 2 a1-amend
94 94 # |
95 95 # | o 1 a1
96 96 # |/
97 97 # o 0 a0
98 98 #
99 99 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
100 100 # yet the filelog has the copy information in rev 1 and we will not look
101 101 # back far enough unless we also look at the a and b as candidates.
102 102 # This only occurs when a is a descendent of b or visa-versa.
103 103 return min(limit, a, b)
104 104
105 105 def _chain(src, dst, a, b):
106 106 '''chain two sets of copies a->b'''
107 107 t = a.copy()
108 108 for k, v in b.iteritems():
109 109 if v in t:
110 110 # found a chain
111 111 if t[v] != k:
112 112 # file wasn't renamed back to itself
113 113 t[k] = t[v]
114 114 if v not in dst:
115 115 # chain was a rename, not a copy
116 116 del t[v]
117 117 if v in src:
118 118 # file is a copy of an existing file
119 119 t[k] = v
120 120
121 121 # remove criss-crossed copies
122 122 for k, v in t.items():
123 123 if k in src and v in dst:
124 124 del t[k]
125 125
126 126 return t
127 127
128 128 def _tracefile(fctx, am, limit=-1):
129 129 '''return file context that is the ancestor of fctx present in ancestor
130 130 manifest am, stopping after the first ancestor lower than limit'''
131 131
132 132 for f in fctx.ancestors():
133 133 if am.get(f.path(), None) == f.filenode():
134 134 return f
135 135 if limit >= 0 and f.linkrev() < limit and f.rev() < limit:
136 136 return None
137 137
138 138 def _dirstatecopies(d):
139 139 ds = d._repo.dirstate
140 140 c = ds.copies().copy()
141 141 for k in c.keys():
142 142 if ds[k] not in 'anm':
143 143 del c[k]
144 144 return c
145 145
146 146 def _computeforwardmissing(a, b, match=None):
147 147 """Computes which files are in b but not a.
148 148 This is its own function so extensions can easily wrap this call to see what
149 149 files _forwardcopies is about to process.
150 150 """
151 151 ma = a.manifest()
152 152 mb = b.manifest()
153 153 return mb.filesnotin(ma, match=match)
154 154
155 155 def _forwardcopies(a, b, match=None):
156 156 '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
157 157
158 158 # check for working copy
159 159 w = None
160 160 if b.rev() is None:
161 161 w = b
162 162 b = w.p1()
163 163 if a == b:
164 164 # short-circuit to avoid issues with merge states
165 165 return _dirstatecopies(w)
166 166
167 167 # files might have to be traced back to the fctx parent of the last
168 168 # one-side-only changeset, but not further back than that
169 169 limit = _findlimit(a._repo, a.rev(), b.rev())
170 170 if limit is None:
171 171 limit = -1
172 172 am = a.manifest()
173 173
174 174 # find where new files came from
175 175 # we currently don't try to find where old files went, too expensive
176 176 # this means we can miss a case like 'hg rm b; hg cp a b'
177 177 cm = {}
178 178
179 179 # Computing the forward missing is quite expensive on large manifests, since
180 180 # it compares the entire manifests. We can optimize it in the common use
181 181 # case of computing what copies are in a commit versus its parent (like
182 182 # during a rebase or histedit). Note, we exclude merge commits from this
183 183 # optimization, since the ctx.files() for a merge commit is not correct for
184 184 # this comparison.
185 185 forwardmissingmatch = match
186 186 if b.p1() == a and b.p2().node() == node.nullid:
187 187 filesmatcher = scmutil.matchfiles(a._repo, b.files())
188 188 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
189 189 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
190 190
191 191 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
192 192 for f in missing:
193 193 fctx = b[f]
194 194 fctx._ancestrycontext = ancestrycontext
195 195 ofctx = _tracefile(fctx, am, limit)
196 196 if ofctx:
197 197 cm[f] = ofctx.path()
198 198
199 199 # combine copies from dirstate if necessary
200 200 if w is not None:
201 201 cm = _chain(a, w, cm, _dirstatecopies(w))
202 202
203 203 return cm
204 204
205 205 def _backwardrenames(a, b):
206 206 if a._repo.ui.configbool('experimental', 'disablecopytrace'):
207 207 return {}
208 208
209 209 # Even though we're not taking copies into account, 1:n rename situations
210 210 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
211 211 # arbitrarily pick one of the renames.
212 212 f = _forwardcopies(b, a)
213 213 r = {}
214 214 for k, v in sorted(f.iteritems()):
215 215 # remove copies
216 216 if v in a:
217 217 continue
218 218 r[v] = k
219 219 return r
220 220
221 221 def pathcopies(x, y, match=None):
222 222 '''find {dst@y: src@x} copy mapping for directed compare'''
223 223 if x == y or not x or not y:
224 224 return {}
225 225 a = y.ancestor(x)
226 226 if a == x:
227 227 return _forwardcopies(x, y, match=match)
228 228 if a == y:
229 229 return _backwardrenames(x, y)
230 230 return _chain(x, y, _backwardrenames(x, a),
231 231 _forwardcopies(a, y, match=match))
232 232
233 233 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
234 234 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
235 235 and c2. This is its own function so extensions can easily wrap this call
236 236 to see what files mergecopies is about to process.
237 237
238 238 Even though c1 and c2 are not used in this function, they are useful in
239 239 other extensions for being able to read the file nodes of the changed files.
240 240
241 241 "baselabel" can be passed to help distinguish the multiple computations
242 242 done in the graft case.
243 243 """
244 244 u1 = sorted(addedinm1 - addedinm2)
245 245 u2 = sorted(addedinm2 - addedinm1)
246 246
247 247 header = " unmatched files in %s"
248 248 if baselabel:
249 249 header += ' (from %s)' % baselabel
250 250 if u1:
251 251 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
252 252 if u2:
253 253 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
254 254 return u1, u2
255 255
256 256 def _makegetfctx(ctx):
257 257 """return a 'getfctx' function suitable for _checkcopies usage
258 258
259 259 We have to re-setup the function building 'filectx' for each
260 260 '_checkcopies' to ensure the linkrev adjustment is properly setup for
261 261 each. Linkrev adjustment is important to avoid bug in rename
262 262 detection. Moreover, having a proper '_ancestrycontext' setup ensures
263 263 the performance impact of this adjustment is kept limited. Without it,
264 264 each file could do a full dag traversal making the time complexity of
265 265 the operation explode (see issue4537).
266 266
267 267 This function exists here mostly to limit the impact on stable. Feel
268 268 free to refactor on default.
269 269 """
270 270 rev = ctx.rev()
271 271 repo = ctx._repo
272 272 ac = getattr(ctx, '_ancestrycontext', None)
273 273 if ac is None:
274 274 revs = [rev]
275 275 if rev is None:
276 276 revs = [p.rev() for p in ctx.parents()]
277 277 ac = repo.changelog.ancestors(revs, inclusive=True)
278 278 ctx._ancestrycontext = ac
279 279 def makectx(f, n):
280 280 if n in node.wdirnodes: # in a working context?
281 281 if ctx.rev() is None:
282 282 return ctx.filectx(f)
283 283 return repo[None][f]
284 284 fctx = repo.filectx(f, fileid=n)
285 285 # setup only needed for filectx not create from a changectx
286 286 fctx._ancestrycontext = ac
287 287 fctx._descendantrev = rev
288 288 return fctx
289 289 return util.lrucachefunc(makectx)
290 290
291 291 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
292 292 """combine partial copy paths"""
293 293 remainder = {}
294 294 for f in copyfrom:
295 295 if f in copyto:
296 296 finalcopy[copyto[f]] = copyfrom[f]
297 297 del copyto[f]
298 298 for f in incompletediverge:
299 299 assert f not in diverge
300 300 ic = incompletediverge[f]
301 301 if ic[0] in copyto:
302 302 diverge[f] = [copyto[ic[0]], ic[1]]
303 303 else:
304 304 remainder[f] = ic
305 305 return remainder
306 306
307 307 def mergecopies(repo, c1, c2, base):
308 308 """
309 309 The basic algorithm for copytracing. Copytracing is used in commands like
310 310 rebase, merge, unshelve, etc to merge files that were moved/ copied in one
311 311 merge parent and modified in another. For example:
312 312
313 313 o ---> 4 another commit
314 314 |
315 315 | o ---> 3 commit that modifies a.txt
316 316 | /
317 317 o / ---> 2 commit that moves a.txt to b.txt
318 318 |/
319 319 o ---> 1 merge base
320 320
321 321 If we try to rebase revision 3 on revision 4, since there is no a.txt in
322 322 revision 4, and if user have copytrace disabled, we prints the following
323 323 message:
324 324
325 325 ```other changed <file> which local deleted```
326 326
327 327 If copytrace is enabled, this function finds all the new files that were
328 328 added from merge base up to the top commit (here 4), and for each file it
329 329 checks if this file was copied from another file (a.txt in the above case).
330 330
331 331 Find moves and copies between context c1 and c2 that are relevant
332 332 for merging. 'base' will be used as the merge base.
333 333
334 334 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
335 335 "dirmove".
336 336
337 337 "copy" is a mapping from destination name -> source name,
338 338 where source is in c1 and destination is in c2 or vice-versa.
339 339
340 340 "movewithdir" is a mapping from source name -> destination name,
341 341 where the file at source present in one context but not the other
342 342 needs to be moved to destination by the merge process, because the
343 343 other context moved the directory it is in.
344 344
345 345 "diverge" is a mapping of source name -> list of destination names
346 346 for divergent renames.
347 347
348 348 "renamedelete" is a mapping of source name -> list of destination
349 349 names for files deleted in c1 that were renamed in c2 or vice-versa.
350 350
351 351 "dirmove" is a mapping of detected source dir -> destination dir renames.
352 352 This is needed for handling changes to new files previously grafted into
353 353 renamed directories.
354 354 """
355 355 # avoid silly behavior for update from empty dir
356 356 if not c1 or not c2 or c1 == c2:
357 357 return {}, {}, {}, {}, {}
358 358
359 359 # avoid silly behavior for parent -> working dir
360 360 if c2.node() is None and c1.node() == repo.dirstate.p1():
361 361 return repo.dirstate.copies(), {}, {}, {}, {}
362 362
363 363 # Copy trace disabling is explicitly below the node == p1 logic above
364 364 # because the logic above is required for a simple copy to be kept across a
365 365 # rebase.
366 366 if repo.ui.configbool('experimental', 'disablecopytrace'):
367 367 return {}, {}, {}, {}, {}
368 368
369 369 # In certain scenarios (e.g. graft, update or rebase), base can be
370 370 # overridden We still need to know a real common ancestor in this case We
371 371 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
372 372 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
373 373 # caller may not know if the revision passed in lieu of the CA is a genuine
374 374 # common ancestor or not without explicitly checking it, it's better to
375 375 # determine that here.
376 376 #
377 377 # base.descendant(wc) and base.descendant(base) are False, work around that
378 378 _c1 = c1.p1() if c1.rev() is None else c1
379 379 _c2 = c2.p1() if c2.rev() is None else c2
380 380 # an endpoint is "dirty" if it isn't a descendant of the merge base
381 381 # if we have a dirty endpoint, we need to trigger graft logic, and also
382 382 # keep track of which endpoint is dirty
383 383 dirtyc1 = not (base == _c1 or base.descendant(_c1))
384 dirtyc2 = not (base== _c2 or base.descendant(_c2))
384 dirtyc2 = not (base == _c2 or base.descendant(_c2))
385 385 graft = dirtyc1 or dirtyc2
386 386 tca = base
387 387 if graft:
388 388 tca = _c1.ancestor(_c2)
389 389
390 390 limit = _findlimit(repo, c1.rev(), c2.rev())
391 391 if limit is None:
392 392 # no common ancestor, no copies
393 393 return {}, {}, {}, {}, {}
394 394 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
395 395
396 396 m1 = c1.manifest()
397 397 m2 = c2.manifest()
398 398 mb = base.manifest()
399 399
400 400 # gather data from _checkcopies:
401 401 # - diverge = record all diverges in this dict
402 402 # - copy = record all non-divergent copies in this dict
403 403 # - fullcopy = record all copies in this dict
404 404 # - incomplete = record non-divergent partial copies here
405 405 # - incompletediverge = record divergent partial copies here
406 406 diverge = {} # divergence data is shared
407 407 incompletediverge = {}
408 408 data1 = {'copy': {},
409 409 'fullcopy': {},
410 410 'incomplete': {},
411 411 'diverge': diverge,
412 412 'incompletediverge': incompletediverge,
413 413 }
414 414 data2 = {'copy': {},
415 415 'fullcopy': {},
416 416 'incomplete': {},
417 417 'diverge': diverge,
418 418 'incompletediverge': incompletediverge,
419 419 }
420 420
421 421 # find interesting file sets from manifests
422 422 addedinm1 = m1.filesnotin(mb)
423 423 addedinm2 = m2.filesnotin(mb)
424 424 bothnew = sorted(addedinm1 & addedinm2)
425 425 if tca == base:
426 426 # unmatched file from base
427 427 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
428 428 u1u, u2u = u1r, u2r
429 429 else:
430 430 # unmatched file from base (DAG rotation in the graft case)
431 431 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
432 432 baselabel='base')
433 433 # unmatched file from topological common ancestors (no DAG rotation)
434 434 # need to recompute this for directory move handling when grafting
435 435 mta = tca.manifest()
436 436 u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
437 437 m2.filesnotin(mta),
438 438 baselabel='topological common ancestor')
439 439
440 440 for f in u1u:
441 441 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
442 442
443 443 for f in u2u:
444 444 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
445 445
446 446 copy = dict(data1['copy'])
447 447 copy.update(data2['copy'])
448 448 fullcopy = dict(data1['fullcopy'])
449 449 fullcopy.update(data2['fullcopy'])
450 450
451 451 if dirtyc1:
452 452 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
453 453 incompletediverge)
454 454 else:
455 455 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
456 456 incompletediverge)
457 457
458 458 renamedelete = {}
459 459 renamedeleteset = set()
460 460 divergeset = set()
461 461 for of, fl in diverge.items():
462 462 if len(fl) == 1 or of in c1 or of in c2:
463 463 del diverge[of] # not actually divergent, or not a rename
464 464 if of not in c1 and of not in c2:
465 465 # renamed on one side, deleted on the other side, but filter
466 466 # out files that have been renamed and then deleted
467 467 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
468 468 renamedeleteset.update(fl) # reverse map for below
469 469 else:
470 470 divergeset.update(fl) # reverse map for below
471 471
472 472 if bothnew:
473 473 repo.ui.debug(" unmatched files new in both:\n %s\n"
474 474 % "\n ".join(bothnew))
475 475 bothdiverge = {}
476 476 bothincompletediverge = {}
477 477 remainder = {}
478 478 both1 = {'copy': {},
479 479 'fullcopy': {},
480 480 'incomplete': {},
481 481 'diverge': bothdiverge,
482 482 'incompletediverge': bothincompletediverge
483 483 }
484 484 both2 = {'copy': {},
485 485 'fullcopy': {},
486 486 'incomplete': {},
487 487 'diverge': bothdiverge,
488 488 'incompletediverge': bothincompletediverge
489 489 }
490 490 for f in bothnew:
491 491 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
492 492 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
493 493 if dirtyc1:
494 494 # incomplete copies may only be found on the "dirty" side for bothnew
495 495 assert not both2['incomplete']
496 496 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
497 497 bothincompletediverge)
498 498 elif dirtyc2:
499 499 assert not both1['incomplete']
500 500 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
501 501 bothincompletediverge)
502 502 else:
503 503 # incomplete copies and divergences can't happen outside grafts
504 504 assert not both1['incomplete']
505 505 assert not both2['incomplete']
506 506 assert not bothincompletediverge
507 507 for f in remainder:
508 508 assert f not in bothdiverge
509 509 ic = remainder[f]
510 510 if ic[0] in (m1 if dirtyc1 else m2):
511 511 # backed-out rename on one side, but watch out for deleted files
512 512 bothdiverge[f] = ic
513 513 for of, fl in bothdiverge.items():
514 514 if len(fl) == 2 and fl[0] == fl[1]:
515 515 copy[fl[0]] = of # not actually divergent, just matching renames
516 516
517 517 if fullcopy and repo.ui.debugflag:
518 518 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
519 519 "% = renamed and deleted):\n")
520 520 for f in sorted(fullcopy):
521 521 note = ""
522 522 if f in copy:
523 523 note += "*"
524 524 if f in divergeset:
525 525 note += "!"
526 526 if f in renamedeleteset:
527 527 note += "%"
528 528 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
529 529 note))
530 530 del divergeset
531 531
532 532 if not fullcopy:
533 533 return copy, {}, diverge, renamedelete, {}
534 534
535 535 repo.ui.debug(" checking for directory renames\n")
536 536
537 537 # generate a directory move map
538 538 d1, d2 = c1.dirs(), c2.dirs()
539 539 # Hack for adding '', which is not otherwise added, to d1 and d2
540 540 d1.addpath('/')
541 541 d2.addpath('/')
542 542 invalid = set()
543 543 dirmove = {}
544 544
545 545 # examine each file copy for a potential directory move, which is
546 546 # when all the files in a directory are moved to a new directory
547 547 for dst, src in fullcopy.iteritems():
548 548 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
549 549 if dsrc in invalid:
550 550 # already seen to be uninteresting
551 551 continue
552 552 elif dsrc in d1 and ddst in d1:
553 553 # directory wasn't entirely moved locally
554 554 invalid.add(dsrc + "/")
555 555 elif dsrc in d2 and ddst in d2:
556 556 # directory wasn't entirely moved remotely
557 557 invalid.add(dsrc + "/")
558 558 elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/":
559 559 # files from the same directory moved to two different places
560 560 invalid.add(dsrc + "/")
561 561 else:
562 562 # looks good so far
563 563 dirmove[dsrc + "/"] = ddst + "/"
564 564
565 565 for i in invalid:
566 566 if i in dirmove:
567 567 del dirmove[i]
568 568 del d1, d2, invalid
569 569
570 570 if not dirmove:
571 571 return copy, {}, diverge, renamedelete, {}
572 572
573 573 for d in dirmove:
574 574 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
575 575 (d, dirmove[d]))
576 576
577 577 movewithdir = {}
578 578 # check unaccounted nonoverlapping files against directory moves
579 579 for f in u1r + u2r:
580 580 if f not in fullcopy:
581 581 for d in dirmove:
582 582 if f.startswith(d):
583 583 # new file added in a directory that was moved, move it
584 584 df = dirmove[d] + f[len(d):]
585 585 if df not in copy:
586 586 movewithdir[f] = df
587 587 repo.ui.debug((" pending file src: '%s' -> "
588 588 "dst: '%s'\n") % (f, df))
589 589 break
590 590
591 591 return copy, movewithdir, diverge, renamedelete, dirmove
592 592
593 593 def _related(f1, f2, limit):
594 594 """return True if f1 and f2 filectx have a common ancestor
595 595
596 596 Walk back to common ancestor to see if the two files originate
597 597 from the same file. Since workingfilectx's rev() is None it messes
598 598 up the integer comparison logic, hence the pre-step check for
599 599 None (f1 and f2 can only be workingfilectx's initially).
600 600 """
601 601
602 602 if f1 == f2:
603 603 return f1 # a match
604 604
605 605 g1, g2 = f1.ancestors(), f2.ancestors()
606 606 try:
607 607 f1r, f2r = f1.linkrev(), f2.linkrev()
608 608
609 609 if f1r is None:
610 610 f1 = next(g1)
611 611 if f2r is None:
612 612 f2 = next(g2)
613 613
614 614 while True:
615 615 f1r, f2r = f1.linkrev(), f2.linkrev()
616 616 if f1r > f2r:
617 617 f1 = next(g1)
618 618 elif f2r > f1r:
619 619 f2 = next(g2)
620 620 elif f1 == f2:
621 621 return f1 # a match
622 622 elif f1r == f2r or f1r < limit or f2r < limit:
623 623 return False # copy no longer relevant
624 624 except StopIteration:
625 625 return False
626 626
627 627 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
628 628 """
629 629 check possible copies of f from msrc to mdst
630 630
631 631 srcctx = starting context for f in msrc
632 632 dstctx = destination context for f in mdst
633 633 f = the filename to check (as in msrc)
634 634 base = the changectx used as a merge base
635 635 tca = topological common ancestor for graft-like scenarios
636 636 remotebase = True if base is outside tca::srcctx, False otherwise
637 637 limit = the rev number to not search beyond
638 638 data = dictionary of dictionary to store copy data. (see mergecopies)
639 639
640 640 note: limit is only an optimization, and provides no guarantee that
641 641 irrelevant revisions will not be visited
642 642 there is no easy way to make this algorithm stop in a guaranteed way
643 643 once it "goes behind a certain revision".
644 644 """
645 645
646 646 msrc = srcctx.manifest()
647 647 mdst = dstctx.manifest()
648 648 mb = base.manifest()
649 649 mta = tca.manifest()
650 650 # Might be true if this call is about finding backward renames,
651 651 # This happens in the case of grafts because the DAG is then rotated.
652 652 # If the file exists in both the base and the source, we are not looking
653 653 # for a rename on the source side, but on the part of the DAG that is
654 654 # traversed backwards.
655 655 #
656 656 # In the case there is both backward and forward renames (before and after
657 657 # the base) this is more complicated as we must detect a divergence.
658 658 # We use 'backwards = False' in that case.
659 659 backwards = not remotebase and base != tca and f in mb
660 660 getsrcfctx = _makegetfctx(srcctx)
661 661 getdstfctx = _makegetfctx(dstctx)
662 662
663 663 if msrc[f] == mb.get(f) and not remotebase:
664 664 # Nothing to merge
665 665 return
666 666
667 667 of = None
668 668 seen = {f}
669 669 for oc in getsrcfctx(f, msrc[f]).ancestors():
670 670 ocr = oc.linkrev()
671 671 of = oc.path()
672 672 if of in seen:
673 673 # check limit late - grab last rename before
674 674 if ocr < limit:
675 675 break
676 676 continue
677 677 seen.add(of)
678 678
679 679 # remember for dir rename detection
680 680 if backwards:
681 681 data['fullcopy'][of] = f # grafting backwards through renames
682 682 else:
683 683 data['fullcopy'][f] = of
684 684 if of not in mdst:
685 685 continue # no match, keep looking
686 686 if mdst[of] == mb.get(of):
687 687 return # no merge needed, quit early
688 688 c2 = getdstfctx(of, mdst[of])
689 689 # c2 might be a plain new file on added on destination side that is
690 690 # unrelated to the droids we are looking for.
691 691 cr = _related(oc, c2, tca.rev())
692 692 if cr and (of == f or of == c2.path()): # non-divergent
693 693 if backwards:
694 694 data['copy'][of] = f
695 695 elif of in mb:
696 696 data['copy'][f] = of
697 697 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
698 698 data['copy'][of] = f
699 699 del data['fullcopy'][f]
700 700 data['fullcopy'][of] = f
701 701 else: # divergence w.r.t. graft CA on one side of topological CA
702 702 for sf in seen:
703 703 if sf in mb:
704 704 assert sf not in data['diverge']
705 705 data['diverge'][sf] = [f, of]
706 706 break
707 707 return
708 708
709 709 if of in mta:
710 710 if backwards or remotebase:
711 711 data['incomplete'][of] = f
712 712 else:
713 713 for sf in seen:
714 714 if sf in mb:
715 715 if tca == base:
716 716 data['diverge'].setdefault(sf, []).append(f)
717 717 else:
718 718 data['incompletediverge'][sf] = [of, f]
719 719 return
720 720
721 721 def duplicatecopies(repo, rev, fromrev, skiprev=None):
722 722 '''reproduce copies from fromrev to rev in the dirstate
723 723
724 724 If skiprev is specified, it's a revision that should be used to
725 725 filter copy records. Any copies that occur between fromrev and
726 726 skiprev will not be duplicated, even if they appear in the set of
727 727 copies between fromrev and rev.
728 728 '''
729 729 exclude = {}
730 730 if (skiprev is not None and
731 731 not repo.ui.configbool('experimental', 'disablecopytrace')):
732 732 # disablecopytrace skips this line, but not the entire function because
733 733 # the line below is O(size of the repo) during a rebase, while the rest
734 734 # of the function is much faster (and is required for carrying copy
735 735 # metadata across the rebase anyway).
736 736 exclude = pathcopies(repo[fromrev], repo[skiprev])
737 737 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
738 738 # copies.pathcopies returns backward renames, so dst might not
739 739 # actually be in the dirstate
740 740 if dst in exclude:
741 741 continue
742 742 if repo.dirstate[dst] in "nma":
743 743 repo.dirstate.copy(src, dst)
General Comments 0
You need to be logged in to leave comments. Login now