##// END OF EJS Templates
copies: don't filter out copy targets created on other side of merge commit...
Martin von Zweigbergk -
r43071:35d674a3 default
parent child Browse files
Show More
@@ -1,809 +1,804 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 util,
21 21 )
22 22 from .utils import (
23 23 stringutil,
24 24 )
25 25
26 26 def _findlimit(repo, ctxa, ctxb):
27 27 """
28 28 Find the last revision that needs to be checked to ensure that a full
29 29 transitive closure for file copies can be properly calculated.
30 30 Generally, this means finding the earliest revision number that's an
31 31 ancestor of a or b but not both, except when a or b is a direct descendent
32 32 of the other, in which case we can return the minimum revnum of a and b.
33 33 """
34 34
35 35 # basic idea:
36 36 # - mark a and b with different sides
37 37 # - if a parent's children are all on the same side, the parent is
38 38 # on that side, otherwise it is on no side
39 39 # - walk the graph in topological order with the help of a heap;
40 40 # - add unseen parents to side map
41 41 # - clear side of any parent that has children on different sides
42 42 # - track number of interesting revs that might still be on a side
43 43 # - track the lowest interesting rev seen
44 44 # - quit when interesting revs is zero
45 45
46 46 cl = repo.changelog
47 47 wdirparents = None
48 48 a = ctxa.rev()
49 49 b = ctxb.rev()
50 50 if a is None:
51 51 wdirparents = (ctxa.p1(), ctxa.p2())
52 52 a = node.wdirrev
53 53 if b is None:
54 54 assert not wdirparents
55 55 wdirparents = (ctxb.p1(), ctxb.p2())
56 56 b = node.wdirrev
57 57
58 58 side = {a: -1, b: 1}
59 59 visit = [-a, -b]
60 60 heapq.heapify(visit)
61 61 interesting = len(visit)
62 62 limit = node.wdirrev
63 63
64 64 while interesting:
65 65 r = -heapq.heappop(visit)
66 66 if r == node.wdirrev:
67 67 parents = [pctx.rev() for pctx in wdirparents]
68 68 else:
69 69 parents = cl.parentrevs(r)
70 70 if parents[1] == node.nullrev:
71 71 parents = parents[:1]
72 72 for p in parents:
73 73 if p not in side:
74 74 # first time we see p; add it to visit
75 75 side[p] = side[r]
76 76 if side[p]:
77 77 interesting += 1
78 78 heapq.heappush(visit, -p)
79 79 elif side[p] and side[p] != side[r]:
80 80 # p was interesting but now we know better
81 81 side[p] = 0
82 82 interesting -= 1
83 83 if side[r]:
84 84 limit = r # lowest rev visited
85 85 interesting -= 1
86 86
87 87 # Consider the following flow (see test-commit-amend.t under issue4405):
88 88 # 1/ File 'a0' committed
89 89 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
90 90 # 3/ Move back to first commit
91 91 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
92 92 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
93 93 #
94 94 # During the amend in step five, we will be in this state:
95 95 #
96 96 # @ 3 temporary amend commit for a1-amend
97 97 # |
98 98 # o 2 a1-amend
99 99 # |
100 100 # | o 1 a1
101 101 # |/
102 102 # o 0 a0
103 103 #
104 104 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
105 105 # yet the filelog has the copy information in rev 1 and we will not look
106 106 # back far enough unless we also look at the a and b as candidates.
107 107 # This only occurs when a is a descendent of b or visa-versa.
108 108 return min(limit, a, b)
109 109
110 110 def _chainandfilter(src, dst, a, b):
111 111 """chain two sets of copies 'a' and 'b' and filter result"""
112 112
113 113 # When chaining copies in 'a' (from 'src' via some other commit 'mid') with
114 114 # copies in 'b' (from 'mid' to 'dst'), we can get the different cases in the
115 115 # following table (not including trivial cases). For example, case 2 is
116 116 # where a file existed in 'src' and remained under that name in 'mid' and
117 117 # then was renamed between 'mid' and 'dst'.
118 118 #
119 119 # case src mid dst result
120 120 # 1 x y - -
121 121 # 2 x y y x->y
122 122 # 3 x y x -
123 123 # 4 x y z x->z
124 124 # 5 - x y -
125 125 # 6 x x y x->y
126 126 #
127 127 # _chain() takes care of chaining the copies in 'a' and 'b', but it
128 128 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
129 129 # between 5 and 6, so it includes all cases in its result.
130 130 # Cases 1, 3, and 5 are then removed by _filter().
131 131
132 132 t = _chain(a, b)
133 133 _filter(src, dst, t)
134 134 return t
135 135
136 136 def _filter(src, dst, t):
137 137 """filters out invalid copies after chaining"""
138 138 for k, v in list(t.items()):
139 139 # remove copies from files that didn't exist
140 140 if v not in src:
141 141 del t[k]
142 142 # remove criss-crossed copies
143 143 elif k in src and v in dst:
144 144 del t[k]
145 145 # remove copies to files that were then removed
146 146 elif k not in dst:
147 147 del t[k]
148 148
149 149 def _chain(a, b):
150 150 """chain two sets of copies 'a' and 'b'"""
151 151 t = a.copy()
152 152 for k, v in b.iteritems():
153 153 if v in t:
154 154 t[k] = t[v]
155 155 else:
156 156 t[k] = v
157 157 return t
158 158
159 159 def _tracefile(fctx, am, limit):
160 160 """return file context that is the ancestor of fctx present in ancestor
161 161 manifest am, stopping after the first ancestor lower than limit"""
162 162
163 163 for f in fctx.ancestors():
164 164 if am.get(f.path(), None) == f.filenode():
165 165 return f
166 166 if not f.isintroducedafter(limit):
167 167 return None
168 168
169 169 def _dirstatecopies(repo, match=None):
170 170 ds = repo.dirstate
171 171 c = ds.copies().copy()
172 172 for k in list(c):
173 173 if ds[k] not in 'anm' or (match and not match(k)):
174 174 del c[k]
175 175 return c
176 176
177 177 def _computeforwardmissing(a, b, match=None):
178 178 """Computes which files are in b but not a.
179 179 This is its own function so extensions can easily wrap this call to see what
180 180 files _forwardcopies is about to process.
181 181 """
182 182 ma = a.manifest()
183 183 mb = b.manifest()
184 184 return mb.filesnotin(ma, match=match)
185 185
186 186 def usechangesetcentricalgo(repo):
187 187 """Checks if we should use changeset-centric copy algorithms"""
188 188 return (repo.ui.config('experimental', 'copies.read-from') in
189 189 ('changeset-only', 'compatibility'))
190 190
191 191 def _committedforwardcopies(a, b, match):
192 192 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
193 193 # files might have to be traced back to the fctx parent of the last
194 194 # one-side-only changeset, but not further back than that
195 195 repo = a._repo
196 196
197 197 if usechangesetcentricalgo(repo):
198 198 return _changesetforwardcopies(a, b, match)
199 199
200 200 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
201 201 dbg = repo.ui.debug
202 202 if debug:
203 203 dbg('debug.copies: looking into rename from %s to %s\n'
204 204 % (a, b))
205 205 limit = _findlimit(repo, a, b)
206 206 if debug:
207 207 dbg('debug.copies: search limit: %d\n' % limit)
208 208 am = a.manifest()
209 209
210 210 # find where new files came from
211 211 # we currently don't try to find where old files went, too expensive
212 212 # this means we can miss a case like 'hg rm b; hg cp a b'
213 213 cm = {}
214 214
215 215 # Computing the forward missing is quite expensive on large manifests, since
216 216 # it compares the entire manifests. We can optimize it in the common use
217 217 # case of computing what copies are in a commit versus its parent (like
218 218 # during a rebase or histedit). Note, we exclude merge commits from this
219 219 # optimization, since the ctx.files() for a merge commit is not correct for
220 220 # this comparison.
221 221 forwardmissingmatch = match
222 222 if b.p1() == a and b.p2().node() == node.nullid:
223 223 filesmatcher = matchmod.exact(b.files())
224 224 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
225 225 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
226 226
227 227 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
228 228
229 229 if debug:
230 230 dbg('debug.copies: missing files to search: %d\n' % len(missing))
231 231
232 232 for f in sorted(missing):
233 233 if debug:
234 234 dbg('debug.copies: tracing file: %s\n' % f)
235 235 fctx = b[f]
236 236 fctx._ancestrycontext = ancestrycontext
237 237
238 238 if debug:
239 239 start = util.timer()
240 240 ofctx = _tracefile(fctx, am, limit)
241 241 if ofctx:
242 242 if debug:
243 243 dbg('debug.copies: rename of: %s\n' % ofctx._path)
244 244 cm[f] = ofctx.path()
245 245 if debug:
246 246 dbg('debug.copies: time: %f seconds\n'
247 247 % (util.timer() - start))
248 248 return cm
249 249
250 250 def _changesetforwardcopies(a, b, match):
251 251 if a.rev() == node.nullrev:
252 252 return {}
253 253
254 254 repo = a.repo()
255 255 children = {}
256 256 cl = repo.changelog
257 257 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
258 258 for r in missingrevs:
259 259 for p in cl.parentrevs(r):
260 260 if p == node.nullrev:
261 261 continue
262 262 if p not in children:
263 263 children[p] = [r]
264 264 else:
265 265 children[p].append(r)
266 266
267 267 roots = set(children) - set(missingrevs)
268 268 # 'work' contains 3-tuples of a (revision number, parent number, copies).
269 269 # The parent number is only used for knowing which parent the copies dict
270 270 # came from.
271 271 work = [(r, 1, {}) for r in roots]
272 272 heapq.heapify(work)
273 273 while work:
274 274 r, i1, copies1 = heapq.heappop(work)
275 275 if work and work[0][0] == r:
276 276 # We are tracing copies from both parents
277 277 r, i2, copies2 = heapq.heappop(work)
278 278 copies = {}
279 ctx = repo[r]
280 p1man, p2man = ctx.p1().manifest(), ctx.p2().manifest()
281 279 allcopies = set(copies1) | set(copies2)
282 280 # TODO: perhaps this filtering should be done as long as ctx
283 281 # is merge, whether or not we're tracing from both parent.
284 282 for dst in allcopies:
285 283 if not match(dst):
286 284 continue
287 if dst not in copies2:
288 # Copied on p1 side: mark as copy from p1 side if it didn't
289 # already exist on p2 side
290 if dst not in p2man:
291 copies[dst] = copies1[dst]
292 elif dst not in copies1:
293 # Copied on p2 side: mark as copy from p2 side if it didn't
294 # already exist on p1 side
295 if dst not in p1man:
296 copies[dst] = copies2[dst]
285 # Unlike when copies are stored in the filelog, we consider
286 # it a copy even if the destination already existed on the
287 # other branch. It's simply too expensive to check if the
288 # file existed in the manifest.
289 if dst in copies1:
290 # If it was copied on the p1 side, mark it as copied from
291 # that side, even if it was also copied on the p2 side.
292 copies[dst] = copies1[dst]
297 293 else:
298 # Copied on both sides: mark as copy from p1 side
299 copies[dst] = copies1[dst]
294 copies[dst] = copies2[dst]
300 295 else:
301 296 copies = copies1
302 297 if r == b.rev():
303 298 _filter(a, b, copies)
304 299 return copies
305 300 for c in children[r]:
306 301 childctx = repo[c]
307 302 if r == childctx.p1().rev():
308 303 parent = 1
309 304 childcopies = childctx.p1copies()
310 305 else:
311 306 assert r == childctx.p2().rev()
312 307 parent = 2
313 308 childcopies = childctx.p2copies()
314 309 if not match.always():
315 310 childcopies = {dst: src for dst, src in childcopies.items()
316 311 if match(dst)}
317 312 childcopies = _chain(copies, childcopies)
318 313 for f in childctx.filesremoved():
319 314 if f in childcopies:
320 315 del childcopies[f]
321 316 heapq.heappush(work, (c, parent, childcopies))
322 317 assert False
323 318
324 319 def _forwardcopies(a, b, match=None):
325 320 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
326 321
327 322 match = a.repo().narrowmatch(match)
328 323 # check for working copy
329 324 if b.rev() is None:
330 325 if a == b.p1():
331 326 # short-circuit to avoid issues with merge states
332 327 return _dirstatecopies(b._repo, match)
333 328
334 329 cm = _committedforwardcopies(a, b.p1(), match)
335 330 # combine copies from dirstate if necessary
336 331 return _chainandfilter(a, b, cm, _dirstatecopies(b._repo, match))
337 332 return _committedforwardcopies(a, b, match)
338 333
339 334 def _backwardrenames(a, b, match):
340 335 if a._repo.ui.config('experimental', 'copytrace') == 'off':
341 336 return {}
342 337
343 338 # Even though we're not taking copies into account, 1:n rename situations
344 339 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
345 340 # arbitrarily pick one of the renames.
346 341 # We don't want to pass in "match" here, since that would filter
347 342 # the destination by it. Since we're reversing the copies, we want
348 343 # to filter the source instead.
349 344 f = _forwardcopies(b, a)
350 345 r = {}
351 346 for k, v in sorted(f.iteritems()):
352 347 if match and not match(v):
353 348 continue
354 349 # remove copies
355 350 if v in a:
356 351 continue
357 352 r[v] = k
358 353 return r
359 354
360 355 def pathcopies(x, y, match=None):
361 356 """find {dst@y: src@x} copy mapping for directed compare"""
362 357 repo = x._repo
363 358 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
364 359 if debug:
365 360 repo.ui.debug('debug.copies: searching copies from %s to %s\n'
366 361 % (x, y))
367 362 if x == y or not x or not y:
368 363 return {}
369 364 a = y.ancestor(x)
370 365 if a == x:
371 366 if debug:
372 367 repo.ui.debug('debug.copies: search mode: forward\n')
373 368 return _forwardcopies(x, y, match=match)
374 369 if a == y:
375 370 if debug:
376 371 repo.ui.debug('debug.copies: search mode: backward\n')
377 372 return _backwardrenames(x, y, match=match)
378 373 if debug:
379 374 repo.ui.debug('debug.copies: search mode: combined\n')
380 375 return _chainandfilter(x, y, _backwardrenames(x, a, match=match),
381 376 _forwardcopies(a, y, match=match))
382 377
383 378 def mergecopies(repo, c1, c2, base):
384 379 """
385 380 Finds moves and copies between context c1 and c2 that are relevant for
386 381 merging. 'base' will be used as the merge base.
387 382
388 383 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
389 384 files that were moved/ copied in one merge parent and modified in another.
390 385 For example:
391 386
392 387 o ---> 4 another commit
393 388 |
394 389 | o ---> 3 commit that modifies a.txt
395 390 | /
396 391 o / ---> 2 commit that moves a.txt to b.txt
397 392 |/
398 393 o ---> 1 merge base
399 394
400 395 If we try to rebase revision 3 on revision 4, since there is no a.txt in
401 396 revision 4, and if user have copytrace disabled, we prints the following
402 397 message:
403 398
404 399 ```other changed <file> which local deleted```
405 400
406 401 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
407 402 "dirmove".
408 403
409 404 "copy" is a mapping from destination name -> source name,
410 405 where source is in c1 and destination is in c2 or vice-versa.
411 406
412 407 "movewithdir" is a mapping from source name -> destination name,
413 408 where the file at source present in one context but not the other
414 409 needs to be moved to destination by the merge process, because the
415 410 other context moved the directory it is in.
416 411
417 412 "diverge" is a mapping of source name -> list of destination names
418 413 for divergent renames.
419 414
420 415 "renamedelete" is a mapping of source name -> list of destination
421 416 names for files deleted in c1 that were renamed in c2 or vice-versa.
422 417
423 418 "dirmove" is a mapping of detected source dir -> destination dir renames.
424 419 This is needed for handling changes to new files previously grafted into
425 420 renamed directories.
426 421
427 422 This function calls different copytracing algorithms based on config.
428 423 """
429 424 # avoid silly behavior for update from empty dir
430 425 if not c1 or not c2 or c1 == c2:
431 426 return {}, {}, {}, {}, {}
432 427
433 428 narrowmatch = c1.repo().narrowmatch()
434 429
435 430 # avoid silly behavior for parent -> working dir
436 431 if c2.node() is None and c1.node() == repo.dirstate.p1():
437 432 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
438 433
439 434 copytracing = repo.ui.config('experimental', 'copytrace')
440 435 if stringutil.parsebool(copytracing) is False:
441 436 # stringutil.parsebool() returns None when it is unable to parse the
442 437 # value, so we should rely on making sure copytracing is on such cases
443 438 return {}, {}, {}, {}, {}
444 439
445 440 if usechangesetcentricalgo(repo):
446 441 # The heuristics don't make sense when we need changeset-centric algos
447 442 return _fullcopytracing(repo, c1, c2, base)
448 443
449 444 # Copy trace disabling is explicitly below the node == p1 logic above
450 445 # because the logic above is required for a simple copy to be kept across a
451 446 # rebase.
452 447 if copytracing == 'heuristics':
453 448 # Do full copytracing if only non-public revisions are involved as
454 449 # that will be fast enough and will also cover the copies which could
455 450 # be missed by heuristics
456 451 if _isfullcopytraceable(repo, c1, base):
457 452 return _fullcopytracing(repo, c1, c2, base)
458 453 return _heuristicscopytracing(repo, c1, c2, base)
459 454 else:
460 455 return _fullcopytracing(repo, c1, c2, base)
461 456
462 457 def _isfullcopytraceable(repo, c1, base):
463 458 """ Checks that if base, source and destination are all no-public branches,
464 459 if yes let's use the full copytrace algorithm for increased capabilities
465 460 since it will be fast enough.
466 461
467 462 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
468 463 number of changesets from c1 to base such that if number of changesets are
469 464 more than the limit, full copytracing algorithm won't be used.
470 465 """
471 466 if c1.rev() is None:
472 467 c1 = c1.p1()
473 468 if c1.mutable() and base.mutable():
474 469 sourcecommitlimit = repo.ui.configint('experimental',
475 470 'copytrace.sourcecommitlimit')
476 471 commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
477 472 return commits < sourcecommitlimit
478 473 return False
479 474
480 475 def _checksinglesidecopies(src, dsts1, m1, m2, mb, c2, base,
481 476 copy, renamedelete):
482 477 if src not in m2:
483 478 # deleted on side 2
484 479 if src not in m1:
485 480 # renamed on side 1, deleted on side 2
486 481 renamedelete[src] = dsts1
487 482 elif m2[src] != mb[src]:
488 483 if not _related(c2[src], base[src]):
489 484 return
490 485 # modified on side 2
491 486 for dst in dsts1:
492 487 if dst not in m2:
493 488 # dst not added on side 2 (handle as regular
494 489 # "both created" case in manifestmerge otherwise)
495 490 copy[dst] = src
496 491
497 492 def _fullcopytracing(repo, c1, c2, base):
498 493 """ The full copytracing algorithm which finds all the new files that were
499 494 added from merge base up to the top commit and for each file it checks if
500 495 this file was copied from another file.
501 496
502 497 This is pretty slow when a lot of changesets are involved but will track all
503 498 the copies.
504 499 """
505 500 m1 = c1.manifest()
506 501 m2 = c2.manifest()
507 502 mb = base.manifest()
508 503
509 504 copies1 = pathcopies(base, c1)
510 505 copies2 = pathcopies(base, c2)
511 506
512 507 inversecopies1 = {}
513 508 inversecopies2 = {}
514 509 for dst, src in copies1.items():
515 510 inversecopies1.setdefault(src, []).append(dst)
516 511 for dst, src in copies2.items():
517 512 inversecopies2.setdefault(src, []).append(dst)
518 513
519 514 copy = {}
520 515 diverge = {}
521 516 renamedelete = {}
522 517 allsources = set(inversecopies1) | set(inversecopies2)
523 518 for src in allsources:
524 519 dsts1 = inversecopies1.get(src)
525 520 dsts2 = inversecopies2.get(src)
526 521 if dsts1 and dsts2:
527 522 # copied/renamed on both sides
528 523 if src not in m1 and src not in m2:
529 524 # renamed on both sides
530 525 dsts1 = set(dsts1)
531 526 dsts2 = set(dsts2)
532 527 # If there's some overlap in the rename destinations, we
533 528 # consider it not divergent. For example, if side 1 copies 'a'
534 529 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
535 530 # and 'd' and deletes 'a'.
536 531 if dsts1 & dsts2:
537 532 for dst in (dsts1 & dsts2):
538 533 copy[dst] = src
539 534 else:
540 535 diverge[src] = sorted(dsts1 | dsts2)
541 536 elif src in m1 and src in m2:
542 537 # copied on both sides
543 538 dsts1 = set(dsts1)
544 539 dsts2 = set(dsts2)
545 540 for dst in (dsts1 & dsts2):
546 541 copy[dst] = src
547 542 # TODO: Handle cases where it was renamed on one side and copied
548 543 # on the other side
549 544 elif dsts1:
550 545 # copied/renamed only on side 1
551 546 _checksinglesidecopies(src, dsts1, m1, m2, mb, c2, base,
552 547 copy, renamedelete)
553 548 elif dsts2:
554 549 # copied/renamed only on side 2
555 550 _checksinglesidecopies(src, dsts2, m2, m1, mb, c1, base,
556 551 copy, renamedelete)
557 552
558 553 renamedeleteset = set()
559 554 divergeset = set()
560 555 for dsts in diverge.values():
561 556 divergeset.update(dsts)
562 557 for dsts in renamedelete.values():
563 558 renamedeleteset.update(dsts)
564 559
565 560 # find interesting file sets from manifests
566 561 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
567 562 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
568 563 u1 = sorted(addedinm1 - addedinm2)
569 564 u2 = sorted(addedinm2 - addedinm1)
570 565
571 566 header = " unmatched files in %s"
572 567 if u1:
573 568 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
574 569 if u2:
575 570 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
576 571
577 572 fullcopy = copies1.copy()
578 573 fullcopy.update(copies2)
579 574 if not fullcopy:
580 575 return copy, {}, diverge, renamedelete, {}
581 576
582 577 if repo.ui.debugflag:
583 578 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
584 579 "% = renamed and deleted):\n")
585 580 for f in sorted(fullcopy):
586 581 note = ""
587 582 if f in copy:
588 583 note += "*"
589 584 if f in divergeset:
590 585 note += "!"
591 586 if f in renamedeleteset:
592 587 note += "%"
593 588 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
594 589 note))
595 590 del divergeset
596 591
597 592 repo.ui.debug(" checking for directory renames\n")
598 593
599 594 # generate a directory move map
600 595 d1, d2 = c1.dirs(), c2.dirs()
601 596 invalid = set()
602 597 dirmove = {}
603 598
604 599 # examine each file copy for a potential directory move, which is
605 600 # when all the files in a directory are moved to a new directory
606 601 for dst, src in fullcopy.iteritems():
607 602 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
608 603 if dsrc in invalid:
609 604 # already seen to be uninteresting
610 605 continue
611 606 elif dsrc in d1 and ddst in d1:
612 607 # directory wasn't entirely moved locally
613 608 invalid.add(dsrc)
614 609 elif dsrc in d2 and ddst in d2:
615 610 # directory wasn't entirely moved remotely
616 611 invalid.add(dsrc)
617 612 elif dsrc in dirmove and dirmove[dsrc] != ddst:
618 613 # files from the same directory moved to two different places
619 614 invalid.add(dsrc)
620 615 else:
621 616 # looks good so far
622 617 dirmove[dsrc] = ddst
623 618
624 619 for i in invalid:
625 620 if i in dirmove:
626 621 del dirmove[i]
627 622 del d1, d2, invalid
628 623
629 624 if not dirmove:
630 625 return copy, {}, diverge, renamedelete, {}
631 626
632 627 dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
633 628
634 629 for d in dirmove:
635 630 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
636 631 (d, dirmove[d]))
637 632
638 633 movewithdir = {}
639 634 # check unaccounted nonoverlapping files against directory moves
640 635 for f in u1 + u2:
641 636 if f not in fullcopy:
642 637 for d in dirmove:
643 638 if f.startswith(d):
644 639 # new file added in a directory that was moved, move it
645 640 df = dirmove[d] + f[len(d):]
646 641 if df not in copy:
647 642 movewithdir[f] = df
648 643 repo.ui.debug((" pending file src: '%s' -> "
649 644 "dst: '%s'\n") % (f, df))
650 645 break
651 646
652 647 return copy, movewithdir, diverge, renamedelete, dirmove
653 648
654 649 def _heuristicscopytracing(repo, c1, c2, base):
655 650 """ Fast copytracing using filename heuristics
656 651
657 652 Assumes that moves or renames are of following two types:
658 653
659 654 1) Inside a directory only (same directory name but different filenames)
660 655 2) Move from one directory to another
661 656 (same filenames but different directory names)
662 657
663 658 Works only when there are no merge commits in the "source branch".
664 659 Source branch is commits from base up to c2 not including base.
665 660
666 661 If merge is involved it fallbacks to _fullcopytracing().
667 662
668 663 Can be used by setting the following config:
669 664
670 665 [experimental]
671 666 copytrace = heuristics
672 667
673 668 In some cases the copy/move candidates found by heuristics can be very large
674 669 in number and that will make the algorithm slow. The number of possible
675 670 candidates to check can be limited by using the config
676 671 `experimental.copytrace.movecandidateslimit` which defaults to 100.
677 672 """
678 673
679 674 if c1.rev() is None:
680 675 c1 = c1.p1()
681 676 if c2.rev() is None:
682 677 c2 = c2.p1()
683 678
684 679 copies = {}
685 680
686 681 changedfiles = set()
687 682 m1 = c1.manifest()
688 683 if not repo.revs('%d::%d', base.rev(), c2.rev()):
689 684 # If base is not in c2 branch, we switch to fullcopytracing
690 685 repo.ui.debug("switching to full copytracing as base is not "
691 686 "an ancestor of c2\n")
692 687 return _fullcopytracing(repo, c1, c2, base)
693 688
694 689 ctx = c2
695 690 while ctx != base:
696 691 if len(ctx.parents()) == 2:
697 692 # To keep things simple let's not handle merges
698 693 repo.ui.debug("switching to full copytracing because of merges\n")
699 694 return _fullcopytracing(repo, c1, c2, base)
700 695 changedfiles.update(ctx.files())
701 696 ctx = ctx.p1()
702 697
703 698 cp = _forwardcopies(base, c2)
704 699 for dst, src in cp.iteritems():
705 700 if src in m1:
706 701 copies[dst] = src
707 702
708 703 # file is missing if it isn't present in the destination, but is present in
709 704 # the base and present in the source.
710 705 # Presence in the base is important to exclude added files, presence in the
711 706 # source is important to exclude removed files.
712 707 filt = lambda f: f not in m1 and f in base and f in c2
713 708 missingfiles = [f for f in changedfiles if filt(f)]
714 709
715 710 if missingfiles:
716 711 basenametofilename = collections.defaultdict(list)
717 712 dirnametofilename = collections.defaultdict(list)
718 713
719 714 for f in m1.filesnotin(base.manifest()):
720 715 basename = os.path.basename(f)
721 716 dirname = os.path.dirname(f)
722 717 basenametofilename[basename].append(f)
723 718 dirnametofilename[dirname].append(f)
724 719
725 720 for f in missingfiles:
726 721 basename = os.path.basename(f)
727 722 dirname = os.path.dirname(f)
728 723 samebasename = basenametofilename[basename]
729 724 samedirname = dirnametofilename[dirname]
730 725 movecandidates = samebasename + samedirname
731 726 # f is guaranteed to be present in c2, that's why
732 727 # c2.filectx(f) won't fail
733 728 f2 = c2.filectx(f)
734 729 # we can have a lot of candidates which can slow down the heuristics
735 730 # config value to limit the number of candidates moves to check
736 731 maxcandidates = repo.ui.configint('experimental',
737 732 'copytrace.movecandidateslimit')
738 733
739 734 if len(movecandidates) > maxcandidates:
740 735 repo.ui.status(_("skipping copytracing for '%s', more "
741 736 "candidates than the limit: %d\n")
742 737 % (f, len(movecandidates)))
743 738 continue
744 739
745 740 for candidate in movecandidates:
746 741 f1 = c1.filectx(candidate)
747 742 if _related(f1, f2):
748 743 # if there are a few related copies then we'll merge
749 744 # changes into all of them. This matches the behaviour
750 745 # of upstream copytracing
751 746 copies[candidate] = f
752 747
753 748 return copies, {}, {}, {}, {}
754 749
755 750 def _related(f1, f2):
756 751 """return True if f1 and f2 filectx have a common ancestor
757 752
758 753 Walk back to common ancestor to see if the two files originate
759 754 from the same file. Since workingfilectx's rev() is None it messes
760 755 up the integer comparison logic, hence the pre-step check for
761 756 None (f1 and f2 can only be workingfilectx's initially).
762 757 """
763 758
764 759 if f1 == f2:
765 760 return True # a match
766 761
767 762 g1, g2 = f1.ancestors(), f2.ancestors()
768 763 try:
769 764 f1r, f2r = f1.linkrev(), f2.linkrev()
770 765
771 766 if f1r is None:
772 767 f1 = next(g1)
773 768 if f2r is None:
774 769 f2 = next(g2)
775 770
776 771 while True:
777 772 f1r, f2r = f1.linkrev(), f2.linkrev()
778 773 if f1r > f2r:
779 774 f1 = next(g1)
780 775 elif f2r > f1r:
781 776 f2 = next(g2)
782 777 else: # f1 and f2 point to files in the same linkrev
783 778 return f1 == f2 # true if they point to the same file
784 779 except StopIteration:
785 780 return False
786 781
787 782 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
788 783 """reproduce copies from fromrev to rev in the dirstate
789 784
790 785 If skiprev is specified, it's a revision that should be used to
791 786 filter copy records. Any copies that occur between fromrev and
792 787 skiprev will not be duplicated, even if they appear in the set of
793 788 copies between fromrev and rev.
794 789 """
795 790 exclude = {}
796 791 ctraceconfig = repo.ui.config('experimental', 'copytrace')
797 792 bctrace = stringutil.parsebool(ctraceconfig)
798 793 if (skiprev is not None and
799 794 (ctraceconfig == 'heuristics' or bctrace or bctrace is None)):
800 795 # copytrace='off' skips this line, but not the entire function because
801 796 # the line below is O(size of the repo) during a rebase, while the rest
802 797 # of the function is much faster (and is required for carrying copy
803 798 # metadata across the rebase anyway).
804 799 exclude = pathcopies(repo[fromrev], repo[skiprev])
805 800 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
806 801 if dst in exclude:
807 802 continue
808 803 if dst in wctx:
809 804 wctx[dst].markcopied(src)
@@ -1,647 +1,652 b''
1 1 #testcases filelog compatibility changeset
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 #if changeset
18 18 $ cat >> $HGRCPATH << EOF
19 19 > [experimental]
20 20 > copies.read-from = changeset-only
21 21 > copies.write-to = changeset-only
22 22 > EOF
23 23 #endif
24 24
25 25 $ REPONUM=0
26 26 $ newrepo() {
27 27 > cd $TESTTMP
28 28 > REPONUM=`expr $REPONUM + 1`
29 29 > hg init repo-$REPONUM
30 30 > cd repo-$REPONUM
31 31 > }
32 32
33 33 Simple rename case
34 34 $ newrepo
35 35 $ echo x > x
36 36 $ hg ci -Aqm 'add x'
37 37 $ hg mv x y
38 38 $ hg debugp1copies
39 39 x -> y
40 40 $ hg debugp2copies
41 41 $ hg ci -m 'rename x to y'
42 42 $ hg l
43 43 @ 1 rename x to y
44 44 | x y
45 45 o 0 add x
46 46 x
47 47 $ hg debugp1copies -r 1
48 48 x -> y
49 49 $ hg debugpathcopies 0 1
50 50 x -> y
51 51 $ hg debugpathcopies 1 0
52 52 y -> x
53 53 Test filtering copies by path. We do filtering by destination.
54 54 $ hg debugpathcopies 0 1 x
55 55 $ hg debugpathcopies 1 0 x
56 56 y -> x
57 57 $ hg debugpathcopies 0 1 y
58 58 x -> y
59 59 $ hg debugpathcopies 1 0 y
60 60
61 61 Copy a file onto another file
62 62 $ newrepo
63 63 $ echo x > x
64 64 $ echo y > y
65 65 $ hg ci -Aqm 'add x and y'
66 66 $ hg cp -f x y
67 67 $ hg debugp1copies
68 68 x -> y
69 69 $ hg debugp2copies
70 70 $ hg ci -m 'copy x onto y'
71 71 $ hg l
72 72 @ 1 copy x onto y
73 73 | y
74 74 o 0 add x and y
75 75 x y
76 76 $ hg debugp1copies -r 1
77 77 x -> y
78 78 Incorrectly doesn't show the rename
79 79 $ hg debugpathcopies 0 1
80 80
81 81 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
82 82 produce a new filelog entry. The changeset's "files" entry should still list the file.
83 83 $ newrepo
84 84 $ echo x > x
85 85 $ echo x > x2
86 86 $ hg ci -Aqm 'add x and x2 with same content'
87 87 $ hg cp -f x x2
88 88 $ hg ci -m 'copy x onto x2'
89 89 $ hg l
90 90 @ 1 copy x onto x2
91 91 | x2
92 92 o 0 add x and x2 with same content
93 93 x x2
94 94 $ hg debugp1copies -r 1
95 95 x -> x2
96 96 Incorrectly doesn't show the rename
97 97 $ hg debugpathcopies 0 1
98 98
99 99 Copy a file, then delete destination, then copy again. This does not create a new filelog entry.
100 100 $ newrepo
101 101 $ echo x > x
102 102 $ hg ci -Aqm 'add x'
103 103 $ hg cp x y
104 104 $ hg ci -m 'copy x to y'
105 105 $ hg rm y
106 106 $ hg ci -m 'remove y'
107 107 $ hg cp -f x y
108 108 $ hg ci -m 'copy x onto y (again)'
109 109 $ hg l
110 110 @ 3 copy x onto y (again)
111 111 | y
112 112 o 2 remove y
113 113 | y
114 114 o 1 copy x to y
115 115 | y
116 116 o 0 add x
117 117 x
118 118 $ hg debugp1copies -r 3
119 119 x -> y
120 120 $ hg debugpathcopies 0 3
121 121 x -> y
122 122
123 123 Rename file in a loop: x->y->z->x
124 124 $ newrepo
125 125 $ echo x > x
126 126 $ hg ci -Aqm 'add x'
127 127 $ hg mv x y
128 128 $ hg debugp1copies
129 129 x -> y
130 130 $ hg debugp2copies
131 131 $ hg ci -m 'rename x to y'
132 132 $ hg mv y z
133 133 $ hg ci -m 'rename y to z'
134 134 $ hg mv z x
135 135 $ hg ci -m 'rename z to x'
136 136 $ hg l
137 137 @ 3 rename z to x
138 138 | x z
139 139 o 2 rename y to z
140 140 | y z
141 141 o 1 rename x to y
142 142 | x y
143 143 o 0 add x
144 144 x
145 145 $ hg debugpathcopies 0 3
146 146
147 147 Copy x to y, then remove y, then add back y. With copy metadata in the changeset, this could easily
148 148 end up reporting y as copied from x (if we don't unmark it as a copy when it's removed).
149 149 $ newrepo
150 150 $ echo x > x
151 151 $ hg ci -Aqm 'add x'
152 152 $ hg mv x y
153 153 $ hg ci -m 'rename x to y'
154 154 $ hg rm y
155 155 $ hg ci -qm 'remove y'
156 156 $ echo x > y
157 157 $ hg ci -Aqm 'add back y'
158 158 $ hg l
159 159 @ 3 add back y
160 160 | y
161 161 o 2 remove y
162 162 | y
163 163 o 1 rename x to y
164 164 | x y
165 165 o 0 add x
166 166 x
167 167 $ hg debugp1copies -r 3
168 168 $ hg debugpathcopies 0 3
169 169
170 170 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
171 171 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
172 172 to the first commit that added the file. We should still report the copy as being from x2.
173 173 $ newrepo
174 174 $ echo x > x
175 175 $ echo x > x2
176 176 $ hg ci -Aqm 'add x and x2 with same content'
177 177 $ hg cp x z
178 178 $ hg ci -qm 'copy x to z'
179 179 $ hg rm z
180 180 $ hg ci -m 'remove z'
181 181 $ hg cp x2 z
182 182 $ hg ci -m 'copy x2 to z'
183 183 $ hg l
184 184 @ 3 copy x2 to z
185 185 | z
186 186 o 2 remove z
187 187 | z
188 188 o 1 copy x to z
189 189 | z
190 190 o 0 add x and x2 with same content
191 191 x x2
192 192 $ hg debugp1copies -r 3
193 193 x2 -> z
194 194 $ hg debugpathcopies 0 3
195 195 x2 -> z
196 196
197 197 Create x and y, then rename them both to the same name, but on different sides of a fork
198 198 $ newrepo
199 199 $ echo x > x
200 200 $ echo y > y
201 201 $ hg ci -Aqm 'add x and y'
202 202 $ hg mv x z
203 203 $ hg ci -qm 'rename x to z'
204 204 $ hg co -q 0
205 205 $ hg mv y z
206 206 $ hg ci -qm 'rename y to z'
207 207 $ hg l
208 208 @ 2 rename y to z
209 209 | y z
210 210 | o 1 rename x to z
211 211 |/ x z
212 212 o 0 add x and y
213 213 x y
214 214 $ hg debugpathcopies 1 2
215 215 z -> x
216 216 y -> z
217 217
218 218 Fork renames x to y on one side and removes x on the other
219 219 $ newrepo
220 220 $ echo x > x
221 221 $ hg ci -Aqm 'add x'
222 222 $ hg mv x y
223 223 $ hg ci -m 'rename x to y'
224 224 $ hg co -q 0
225 225 $ hg rm x
226 226 $ hg ci -m 'remove x'
227 227 created new head
228 228 $ hg l
229 229 @ 2 remove x
230 230 | x
231 231 | o 1 rename x to y
232 232 |/ x y
233 233 o 0 add x
234 234 x
235 235 $ hg debugpathcopies 1 2
236 236
237 237 Copies via null revision (there shouldn't be any)
238 238 $ newrepo
239 239 $ echo x > x
240 240 $ hg ci -Aqm 'add x'
241 241 $ hg cp x y
242 242 $ hg ci -m 'copy x to y'
243 243 $ hg co -q null
244 244 $ echo x > x
245 245 $ hg ci -Aqm 'add x (again)'
246 246 $ hg l
247 247 @ 2 add x (again)
248 248 x
249 249 o 1 copy x to y
250 250 | y
251 251 o 0 add x
252 252 x
253 253 $ hg debugpathcopies 1 2
254 254 $ hg debugpathcopies 2 1
255 255
256 256 Merge rename from other branch
257 257 $ newrepo
258 258 $ echo x > x
259 259 $ hg ci -Aqm 'add x'
260 260 $ hg mv x y
261 261 $ hg ci -m 'rename x to y'
262 262 $ hg co -q 0
263 263 $ echo z > z
264 264 $ hg ci -Aqm 'add z'
265 265 $ hg merge -q 1
266 266 $ hg debugp1copies
267 267 $ hg debugp2copies
268 268 $ hg ci -m 'merge rename from p2'
269 269 $ hg l
270 270 @ 3 merge rename from p2
271 271 |\ x
272 272 | o 2 add z
273 273 | | z
274 274 o | 1 rename x to y
275 275 |/ x y
276 276 o 0 add x
277 277 x
278 278 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
279 279 merges, so...
280 280 $ hg debugp1copies -r 3
281 281 $ hg debugp2copies -r 3
282 282 $ hg debugpathcopies 0 3
283 283 x -> y
284 284 $ hg debugpathcopies 1 2
285 285 y -> x
286 286 $ hg debugpathcopies 1 3
287 287 $ hg debugpathcopies 2 3
288 288 x -> y
289 289
290 290 Copy file from either side in a merge
291 291 $ newrepo
292 292 $ echo x > x
293 293 $ hg ci -Aqm 'add x'
294 294 $ hg co -q null
295 295 $ echo y > y
296 296 $ hg ci -Aqm 'add y'
297 297 $ hg merge -q 0
298 298 $ hg cp y z
299 299 $ hg debugp1copies
300 300 y -> z
301 301 $ hg debugp2copies
302 302 $ hg ci -m 'copy file from p1 in merge'
303 303 $ hg co -q 1
304 304 $ hg merge -q 0
305 305 $ hg cp x z
306 306 $ hg debugp1copies
307 307 $ hg debugp2copies
308 308 x -> z
309 309 $ hg ci -qm 'copy file from p2 in merge'
310 310 $ hg l
311 311 @ 3 copy file from p2 in merge
312 312 |\ z
313 313 +---o 2 copy file from p1 in merge
314 314 | |/ z
315 315 | o 1 add y
316 316 | y
317 317 o 0 add x
318 318 x
319 319 $ hg debugp1copies -r 2
320 320 y -> z
321 321 $ hg debugp2copies -r 2
322 322 $ hg debugpathcopies 1 2
323 323 y -> z
324 324 $ hg debugpathcopies 0 2
325 325 $ hg debugp1copies -r 3
326 326 $ hg debugp2copies -r 3
327 327 x -> z
328 328 $ hg debugpathcopies 1 3
329 329 $ hg debugpathcopies 0 3
330 330 x -> z
331 331
332 332 Copy file that exists on both sides of the merge, same content on both sides
333 333 $ newrepo
334 334 $ echo x > x
335 335 $ hg ci -Aqm 'add x on branch 1'
336 336 $ hg co -q null
337 337 $ echo x > x
338 338 $ hg ci -Aqm 'add x on branch 2'
339 339 $ hg merge -q 0
340 340 $ hg cp x z
341 341 $ hg debugp1copies
342 342 x -> z
343 343 $ hg debugp2copies
344 344 $ hg ci -qm 'merge'
345 345 $ hg l
346 346 @ 2 merge
347 347 |\ z
348 348 | o 1 add x on branch 2
349 349 | x
350 350 o 0 add x on branch 1
351 351 x
352 352 $ hg debugp1copies -r 2
353 353 x -> z
354 354 $ hg debugp2copies -r 2
355 355 It's a little weird that it shows up on both sides
356 356 $ hg debugpathcopies 1 2
357 357 x -> z
358 358 $ hg debugpathcopies 0 2
359 359 x -> z (filelog !)
360 360
361 361 Copy file that exists on both sides of the merge, different content
362 362 $ newrepo
363 363 $ echo branch1 > x
364 364 $ hg ci -Aqm 'add x on branch 1'
365 365 $ hg co -q null
366 366 $ echo branch2 > x
367 367 $ hg ci -Aqm 'add x on branch 2'
368 368 $ hg merge -q 0
369 369 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
370 370 [1]
371 371 $ echo resolved > x
372 372 $ hg resolve -m x
373 373 (no more unresolved files)
374 374 $ hg cp x z
375 375 $ hg debugp1copies
376 376 x -> z
377 377 $ hg debugp2copies
378 378 $ hg ci -qm 'merge'
379 379 $ hg l
380 380 @ 2 merge
381 381 |\ x z
382 382 | o 1 add x on branch 2
383 383 | x
384 384 o 0 add x on branch 1
385 385 x
386 386 $ hg debugp1copies -r 2
387 387 x -> z (changeset !)
388 388 $ hg debugp2copies -r 2
389 389 x -> z (no-changeset !)
390 390 $ hg debugpathcopies 1 2
391 391 x -> z (changeset !)
392 392 $ hg debugpathcopies 0 2
393 393 x -> z (no-changeset !)
394 394
395 395 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
396 396 of the merge to the merge should include the copy from the other side.
397 397 $ newrepo
398 398 $ echo x > x
399 399 $ hg ci -Aqm 'add x'
400 400 $ hg cp x y
401 401 $ hg ci -qm 'copy x to y'
402 402 $ hg co -q 0
403 403 $ hg cp x z
404 404 $ hg ci -qm 'copy x to z'
405 405 $ hg merge -q 1
406 406 $ hg ci -m 'merge copy x->y and copy x->z'
407 407 $ hg l
408 408 @ 3 merge copy x->y and copy x->z
409 409 |\
410 410 | o 2 copy x to z
411 411 | | z
412 412 o | 1 copy x to y
413 413 |/ y
414 414 o 0 add x
415 415 x
416 416 $ hg debugp1copies -r 3
417 417 $ hg debugp2copies -r 3
418 418 $ hg debugpathcopies 2 3
419 419 x -> y
420 420 $ hg debugpathcopies 1 3
421 421 x -> z
422 422
423 423 Copy x to y on one side of merge, create y and rename to z on the other side. Pathcopies from the
424 424 first side should not include the y->z rename since y didn't exist in the merge base.
425 425 $ newrepo
426 426 $ echo x > x
427 427 $ hg ci -Aqm 'add x'
428 428 $ hg cp x y
429 429 $ hg ci -qm 'copy x to y'
430 430 $ hg co -q 0
431 431 $ echo y > y
432 432 $ hg ci -Aqm 'add y'
433 433 $ hg mv y z
434 434 $ hg ci -m 'rename y to z'
435 435 $ hg merge -q 1
436 436 $ hg ci -m 'merge'
437 437 $ hg l
438 438 @ 4 merge
439 439 |\
440 440 | o 3 rename y to z
441 441 | | y z
442 442 | o 2 add y
443 443 | | y
444 444 o | 1 copy x to y
445 445 |/ y
446 446 o 0 add x
447 447 x
448 448 $ hg debugp1copies -r 3
449 449 y -> z
450 450 $ hg debugp2copies -r 3
451 451 $ hg debugpathcopies 2 3
452 452 y -> z
453 453 $ hg debugpathcopies 1 3
454 454
455 Create x and y, then rename x to z on one side of merge, and rename y to z and modify z on the
456 other side.
455 Create x and y, then rename x to z on one side of merge, and rename y to z and
456 modify z on the other side. When storing copies in the changeset, we don't
457 filter out copies whose target was created on the other side of the merge.
457 458 $ newrepo
458 459 $ echo x > x
459 460 $ echo y > y
460 461 $ hg ci -Aqm 'add x and y'
461 462 $ hg mv x z
462 463 $ hg ci -qm 'rename x to z'
463 464 $ hg co -q 0
464 465 $ hg mv y z
465 466 $ hg ci -qm 'rename y to z'
466 467 $ echo z >> z
467 468 $ hg ci -m 'modify z'
468 469 $ hg merge -q 1
469 470 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
470 471 [1]
471 472 $ echo z > z
472 473 $ hg resolve -qm z
473 474 $ hg ci -m 'merge 1 into 3'
474 475 Try merging the other direction too
475 476 $ hg co -q 1
476 477 $ hg merge -q 3
477 478 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
478 479 [1]
479 480 $ echo z > z
480 481 $ hg resolve -qm z
481 482 $ hg ci -m 'merge 3 into 1'
482 483 created new head
483 484 $ hg l
484 485 @ 5 merge 3 into 1
485 486 |\ y z
486 487 +---o 4 merge 1 into 3
487 488 | |/ x z
488 489 | o 3 modify z
489 490 | | z
490 491 | o 2 rename y to z
491 492 | | y z
492 493 o | 1 rename x to z
493 494 |/ x z
494 495 o 0 add x and y
495 496 x y
496 497 $ hg debugpathcopies 1 4
498 y -> z (no-filelog !)
497 499 $ hg debugpathcopies 2 4
500 x -> z (no-filelog !)
498 501 $ hg debugpathcopies 0 4
499 502 x -> z (filelog !)
500 503 y -> z (compatibility !)
501 504 $ hg debugpathcopies 1 5
505 y -> z (no-filelog !)
502 506 $ hg debugpathcopies 2 5
507 x -> z (no-filelog !)
503 508 $ hg debugpathcopies 0 5
504 509 x -> z
505 510
506 511
507 512 Test for a case in fullcopytracing algorithm where neither of the merging csets
508 513 is a descendant of the merge base. This test reflects that the algorithm
509 514 correctly finds the copies:
510 515
511 516 $ cat >> $HGRCPATH << EOF
512 517 > [experimental]
513 518 > evolution.createmarkers=True
514 519 > evolution.allowunstable=True
515 520 > EOF
516 521
517 522 $ newrepo
518 523 $ echo a > a
519 524 $ hg add a
520 525 $ hg ci -m "added a"
521 526 $ echo b > b
522 527 $ hg add b
523 528 $ hg ci -m "added b"
524 529
525 530 $ hg mv b b1
526 531 $ hg ci -m "rename b to b1"
527 532
528 533 $ hg up ".^"
529 534 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
530 535 $ echo d > d
531 536 $ hg add d
532 537 $ hg ci -m "added d"
533 538 created new head
534 539
535 540 $ echo baba >> b
536 541 $ hg ci --amend -m "added d, modified b"
537 542
538 543 $ hg l --hidden
539 544 @ 4 added d, modified b
540 545 | b d
541 546 | x 3 added d
542 547 |/ d
543 548 | o 2 rename b to b1
544 549 |/ b b1
545 550 o 1 added b
546 551 | b
547 552 o 0 added a
548 553 a
549 554
550 555 Grafting revision 4 on top of revision 2, showing that it respect the rename:
551 556
552 557 $ hg up 2 -q
553 558 $ hg graft -r 4 --base 3 --hidden
554 559 grafting 4:af28412ec03c "added d, modified b" (tip) (no-changeset !)
555 560 grafting 4:6325ca0b7a1c "added d, modified b" (tip) (changeset !)
556 561 merging b1 and b to b1
557 562
558 563 $ hg l -l1 -p
559 564 @ 5 added d, modified b
560 565 | b1
561 566 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1 (no-changeset !)
562 567 ~ diff -r 0a0ed3b3251c -r d544fb655520 b1 (changeset !)
563 568 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
564 569 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
565 570 @@ -1,1 +1,2 @@
566 571 b
567 572 +baba
568 573
569 574 Test to make sure that fullcopytracing algorithm doesn't fail when neither of the
570 575 merging csets is a descendant of the base.
571 576 -------------------------------------------------------------------------------------------------
572 577
573 578 $ newrepo
574 579 $ echo a > a
575 580 $ hg add a
576 581 $ hg ci -m "added a"
577 582 $ echo b > b
578 583 $ hg add b
579 584 $ hg ci -m "added b"
580 585
581 586 $ echo foobar > willconflict
582 587 $ hg add willconflict
583 588 $ hg ci -m "added willconflict"
584 589 $ echo c > c
585 590 $ hg add c
586 591 $ hg ci -m "added c"
587 592
588 593 $ hg l
589 594 @ 3 added c
590 595 | c
591 596 o 2 added willconflict
592 597 | willconflict
593 598 o 1 added b
594 599 | b
595 600 o 0 added a
596 601 a
597 602
598 603 $ hg up ".^^"
599 604 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
600 605 $ echo d > d
601 606 $ hg add d
602 607 $ hg ci -m "added d"
603 608 created new head
604 609
605 610 $ echo barfoo > willconflict
606 611 $ hg add willconflict
607 612 $ hg ci --amend -m "added willconflict and d"
608 613
609 614 $ hg l
610 615 @ 5 added willconflict and d
611 616 | d willconflict
612 617 | o 3 added c
613 618 | | c
614 619 | o 2 added willconflict
615 620 |/ willconflict
616 621 o 1 added b
617 622 | b
618 623 o 0 added a
619 624 a
620 625
621 626 $ hg rebase -r . -d 2 -t :other
622 627 rebasing 5:5018b1509e94 "added willconflict and d" (tip) (no-changeset !)
623 628 rebasing 5:af8d273bf580 "added willconflict and d" (tip) (changeset !)
624 629
625 630 $ hg up 3 -q
626 631 $ hg l --hidden
627 632 o 6 added willconflict and d
628 633 | d willconflict
629 634 | x 5 added willconflict and d
630 635 | | d willconflict
631 636 | | x 4 added d
632 637 | |/ d
633 638 +---@ 3 added c
634 639 | | c
635 640 o | 2 added willconflict
636 641 |/ willconflict
637 642 o 1 added b
638 643 | b
639 644 o 0 added a
640 645 a
641 646
642 647 Now if we trigger a merge between revision 3 and 6 using base revision 4,
643 648 neither of the merging csets will be a descendant of the base revision:
644 649
645 650 $ hg graft -r 6 --base 4 --hidden -t :other
646 651 grafting 6:99802e4f1e46 "added willconflict and d" (tip) (no-changeset !)
647 652 grafting 6:b19f0df72728 "added willconflict and d" (tip) (changeset !)
General Comments 0
You need to be logged in to leave comments. Login now