##// END OF EJS Templates
copies: add time information to the debug information
Boris Feld -
r40094:cf01616f default
parent child Browse files
Show More
@@ -1,916 +1,921
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, a, b):
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 None if no such revision exists.
35 35 """
36 36
37 37 # basic idea:
38 38 # - mark a and b with different sides
39 39 # - if a parent's children are all on the same side, the parent is
40 40 # on that side, otherwise it is on no side
41 41 # - walk the graph in topological order with the help of a heap;
42 42 # - add unseen parents to side map
43 43 # - clear side of any parent that has children on different sides
44 44 # - track number of interesting revs that might still be on a side
45 45 # - track the lowest interesting rev seen
46 46 # - quit when interesting revs is zero
47 47
48 48 cl = repo.changelog
49 49 working = len(cl) # pseudo rev for the working directory
50 50 if a is None:
51 51 a = working
52 52 if b is None:
53 53 b = working
54 54
55 55 side = {a: -1, b: 1}
56 56 visit = [-a, -b]
57 57 heapq.heapify(visit)
58 58 interesting = len(visit)
59 59 hascommonancestor = False
60 60 limit = working
61 61
62 62 while interesting:
63 63 r = -heapq.heappop(visit)
64 64 if r == working:
65 65 parents = [cl.rev(p) for p in repo.dirstate.parents()]
66 66 else:
67 67 parents = cl.parentrevs(r)
68 68 for p in parents:
69 69 if p < 0:
70 70 continue
71 71 if p not in side:
72 72 # first time we see p; add it to visit
73 73 side[p] = side[r]
74 74 if side[p]:
75 75 interesting += 1
76 76 heapq.heappush(visit, -p)
77 77 elif side[p] and side[p] != side[r]:
78 78 # p was interesting but now we know better
79 79 side[p] = 0
80 80 interesting -= 1
81 81 hascommonancestor = True
82 82 if side[r]:
83 83 limit = r # lowest rev visited
84 84 interesting -= 1
85 85
86 86 if not hascommonancestor:
87 87 return None
88 88
89 89 # Consider the following flow (see test-commit-amend.t under issue4405):
90 90 # 1/ File 'a0' committed
91 91 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
92 92 # 3/ Move back to first commit
93 93 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
94 94 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
95 95 #
96 96 # During the amend in step five, we will be in this state:
97 97 #
98 98 # @ 3 temporary amend commit for a1-amend
99 99 # |
100 100 # o 2 a1-amend
101 101 # |
102 102 # | o 1 a1
103 103 # |/
104 104 # o 0 a0
105 105 #
106 106 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
107 107 # yet the filelog has the copy information in rev 1 and we will not look
108 108 # back far enough unless we also look at the a and b as candidates.
109 109 # This only occurs when a is a descendent of b or visa-versa.
110 110 return min(limit, a, b)
111 111
112 112 def _chain(src, dst, a, b):
113 113 """chain two sets of copies a->b"""
114 114 t = a.copy()
115 115 for k, v in b.iteritems():
116 116 if v in t:
117 117 # found a chain
118 118 if t[v] != k:
119 119 # file wasn't renamed back to itself
120 120 t[k] = t[v]
121 121 if v not in dst:
122 122 # chain was a rename, not a copy
123 123 del t[v]
124 124 if v in src:
125 125 # file is a copy of an existing file
126 126 t[k] = v
127 127
128 128 # remove criss-crossed copies
129 129 for k, v in list(t.items()):
130 130 if k in src and v in dst:
131 131 del t[k]
132 132
133 133 return t
134 134
135 135 def _tracefile(fctx, am, limit=-1):
136 136 """return file context that is the ancestor of fctx present in ancestor
137 137 manifest am, stopping after the first ancestor lower than limit"""
138 138
139 139 for f in fctx.ancestors():
140 140 if am.get(f.path(), None) == f.filenode():
141 141 return f
142 142 if limit >= 0 and f.linkrev() < limit and f.rev() < limit:
143 143 return None
144 144
145 145 def _dirstatecopies(d, match=None):
146 146 ds = d._repo.dirstate
147 147 c = ds.copies().copy()
148 148 for k in list(c):
149 149 if ds[k] not in 'anm' or (match and not match(k)):
150 150 del c[k]
151 151 return c
152 152
153 153 def _computeforwardmissing(a, b, match=None):
154 154 """Computes which files are in b but not a.
155 155 This is its own function so extensions can easily wrap this call to see what
156 156 files _forwardcopies is about to process.
157 157 """
158 158 ma = a.manifest()
159 159 mb = b.manifest()
160 160 return mb.filesnotin(ma, match=match)
161 161
162 162 def _committedforwardcopies(a, b, match):
163 163 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
164 164 # files might have to be traced back to the fctx parent of the last
165 165 # one-side-only changeset, but not further back than that
166 166 repo = a._repo
167 167 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
168 168 dbg = repo.ui.debug
169 169 if debug:
170 170 dbg('debug.copies: looking into rename from %s to %s\n'
171 171 % (a, b))
172 172 limit = _findlimit(repo, a.rev(), b.rev())
173 173 if limit is None:
174 174 limit = -1
175 175 if debug:
176 176 dbg('debug.copies: search limit: %d\n' % limit)
177 177 am = a.manifest()
178 178
179 179 # find where new files came from
180 180 # we currently don't try to find where old files went, too expensive
181 181 # this means we can miss a case like 'hg rm b; hg cp a b'
182 182 cm = {}
183 183
184 184 # Computing the forward missing is quite expensive on large manifests, since
185 185 # it compares the entire manifests. We can optimize it in the common use
186 186 # case of computing what copies are in a commit versus its parent (like
187 187 # during a rebase or histedit). Note, we exclude merge commits from this
188 188 # optimization, since the ctx.files() for a merge commit is not correct for
189 189 # this comparison.
190 190 forwardmissingmatch = match
191 191 if b.p1() == a and b.p2().node() == node.nullid:
192 192 filesmatcher = scmutil.matchfiles(a._repo, b.files())
193 193 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
194 194 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
195 195
196 196 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
197 197
198 198 if debug:
199 199 dbg('debug.copies: missing file to search: %d\n' % len(missing))
200 200
201 201 for f in missing:
202 202 if debug:
203 203 dbg('debug.copies: tracing file: %s\n' % f)
204 204 fctx = b[f]
205 205 fctx._ancestrycontext = ancestrycontext
206 206
207 if debug:
208 start = util.timer()
207 209 ofctx = _tracefile(fctx, am, limit)
208 210 if ofctx:
209 211 if debug:
210 212 dbg('debug.copies: rename of: %s\n' % ofctx._path)
211 213 cm[f] = ofctx.path()
214 if debug:
215 dbg('debug.copies: time: %s seconds\n'
216 % (util.timer() - start))
212 217 return cm
213 218
214 219 def _forwardcopies(a, b, match=None):
215 220 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
216 221
217 222 # check for working copy
218 223 if b.rev() is None:
219 224 if a == b.p1():
220 225 # short-circuit to avoid issues with merge states
221 226 return _dirstatecopies(b, match)
222 227
223 228 cm = _committedforwardcopies(a, b.p1(), match)
224 229 # combine copies from dirstate if necessary
225 230 return _chain(a, b, cm, _dirstatecopies(b, match))
226 231 return _committedforwardcopies(a, b, match)
227 232
228 233 def _backwardrenames(a, b):
229 234 if a._repo.ui.config('experimental', 'copytrace') == 'off':
230 235 return {}
231 236
232 237 # Even though we're not taking copies into account, 1:n rename situations
233 238 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
234 239 # arbitrarily pick one of the renames.
235 240 f = _forwardcopies(b, a)
236 241 r = {}
237 242 for k, v in sorted(f.iteritems()):
238 243 # remove copies
239 244 if v in a:
240 245 continue
241 246 r[v] = k
242 247 return r
243 248
244 249 def pathcopies(x, y, match=None):
245 250 """find {dst@y: src@x} copy mapping for directed compare"""
246 251 repo = x._repo
247 252 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
248 253 if debug:
249 254 repo.ui.debug('debug.copies: searching copies from %s to %s\n'
250 255 % (x, y))
251 256 if x == y or not x or not y:
252 257 return {}
253 258 a = y.ancestor(x)
254 259 if a == x:
255 260 if debug:
256 261 repo.ui.debug('debug.copies: search mode: forward\n')
257 262 return _forwardcopies(x, y, match=match)
258 263 if a == y:
259 264 if debug:
260 265 repo.ui.debug('debug.copies: search mode: backward\n')
261 266 return _backwardrenames(x, y)
262 267 if debug:
263 268 repo.ui.debug('debug.copies: search mode: combined\n')
264 269 return _chain(x, y, _backwardrenames(x, a),
265 270 _forwardcopies(a, y, match=match))
266 271
267 272 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
268 273 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
269 274 and c2. This is its own function so extensions can easily wrap this call
270 275 to see what files mergecopies is about to process.
271 276
272 277 Even though c1 and c2 are not used in this function, they are useful in
273 278 other extensions for being able to read the file nodes of the changed files.
274 279
275 280 "baselabel" can be passed to help distinguish the multiple computations
276 281 done in the graft case.
277 282 """
278 283 u1 = sorted(addedinm1 - addedinm2)
279 284 u2 = sorted(addedinm2 - addedinm1)
280 285
281 286 header = " unmatched files in %s"
282 287 if baselabel:
283 288 header += ' (from %s)' % baselabel
284 289 if u1:
285 290 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
286 291 if u2:
287 292 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
288 293
289 294 return u1, u2
290 295
291 296 def _makegetfctx(ctx):
292 297 """return a 'getfctx' function suitable for _checkcopies usage
293 298
294 299 We have to re-setup the function building 'filectx' for each
295 300 '_checkcopies' to ensure the linkrev adjustment is properly setup for
296 301 each. Linkrev adjustment is important to avoid bug in rename
297 302 detection. Moreover, having a proper '_ancestrycontext' setup ensures
298 303 the performance impact of this adjustment is kept limited. Without it,
299 304 each file could do a full dag traversal making the time complexity of
300 305 the operation explode (see issue4537).
301 306
302 307 This function exists here mostly to limit the impact on stable. Feel
303 308 free to refactor on default.
304 309 """
305 310 rev = ctx.rev()
306 311 repo = ctx._repo
307 312 ac = getattr(ctx, '_ancestrycontext', None)
308 313 if ac is None:
309 314 revs = [rev]
310 315 if rev is None:
311 316 revs = [p.rev() for p in ctx.parents()]
312 317 ac = repo.changelog.ancestors(revs, inclusive=True)
313 318 ctx._ancestrycontext = ac
314 319 def makectx(f, n):
315 320 if n in node.wdirfilenodeids: # in a working context?
316 321 if ctx.rev() is None:
317 322 return ctx.filectx(f)
318 323 return repo[None][f]
319 324 fctx = repo.filectx(f, fileid=n)
320 325 # setup only needed for filectx not create from a changectx
321 326 fctx._ancestrycontext = ac
322 327 fctx._descendantrev = rev
323 328 return fctx
324 329 return util.lrucachefunc(makectx)
325 330
326 331 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
327 332 """combine partial copy paths"""
328 333 remainder = {}
329 334 for f in copyfrom:
330 335 if f in copyto:
331 336 finalcopy[copyto[f]] = copyfrom[f]
332 337 del copyto[f]
333 338 for f in incompletediverge:
334 339 assert f not in diverge
335 340 ic = incompletediverge[f]
336 341 if ic[0] in copyto:
337 342 diverge[f] = [copyto[ic[0]], ic[1]]
338 343 else:
339 344 remainder[f] = ic
340 345 return remainder
341 346
342 347 def mergecopies(repo, c1, c2, base):
343 348 """
344 349 The function calling different copytracing algorithms on the basis of config
345 350 which find moves and copies between context c1 and c2 that are relevant for
346 351 merging. 'base' will be used as the merge base.
347 352
348 353 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
349 354 files that were moved/ copied in one merge parent and modified in another.
350 355 For example:
351 356
352 357 o ---> 4 another commit
353 358 |
354 359 | o ---> 3 commit that modifies a.txt
355 360 | /
356 361 o / ---> 2 commit that moves a.txt to b.txt
357 362 |/
358 363 o ---> 1 merge base
359 364
360 365 If we try to rebase revision 3 on revision 4, since there is no a.txt in
361 366 revision 4, and if user have copytrace disabled, we prints the following
362 367 message:
363 368
364 369 ```other changed <file> which local deleted```
365 370
366 371 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
367 372 "dirmove".
368 373
369 374 "copy" is a mapping from destination name -> source name,
370 375 where source is in c1 and destination is in c2 or vice-versa.
371 376
372 377 "movewithdir" is a mapping from source name -> destination name,
373 378 where the file at source present in one context but not the other
374 379 needs to be moved to destination by the merge process, because the
375 380 other context moved the directory it is in.
376 381
377 382 "diverge" is a mapping of source name -> list of destination names
378 383 for divergent renames.
379 384
380 385 "renamedelete" is a mapping of source name -> list of destination
381 386 names for files deleted in c1 that were renamed in c2 or vice-versa.
382 387
383 388 "dirmove" is a mapping of detected source dir -> destination dir renames.
384 389 This is needed for handling changes to new files previously grafted into
385 390 renamed directories.
386 391 """
387 392 # avoid silly behavior for update from empty dir
388 393 if not c1 or not c2 or c1 == c2:
389 394 return {}, {}, {}, {}, {}
390 395
391 396 # avoid silly behavior for parent -> working dir
392 397 if c2.node() is None and c1.node() == repo.dirstate.p1():
393 398 return repo.dirstate.copies(), {}, {}, {}, {}
394 399
395 400 copytracing = repo.ui.config('experimental', 'copytrace')
396 401 boolctrace = stringutil.parsebool(copytracing)
397 402
398 403 # Copy trace disabling is explicitly below the node == p1 logic above
399 404 # because the logic above is required for a simple copy to be kept across a
400 405 # rebase.
401 406 if copytracing == 'heuristics':
402 407 # Do full copytracing if only non-public revisions are involved as
403 408 # that will be fast enough and will also cover the copies which could
404 409 # be missed by heuristics
405 410 if _isfullcopytraceable(repo, c1, base):
406 411 return _fullcopytracing(repo, c1, c2, base)
407 412 return _heuristicscopytracing(repo, c1, c2, base)
408 413 elif boolctrace is False:
409 414 # stringutil.parsebool() returns None when it is unable to parse the
410 415 # value, so we should rely on making sure copytracing is on such cases
411 416 return {}, {}, {}, {}, {}
412 417 else:
413 418 return _fullcopytracing(repo, c1, c2, base)
414 419
415 420 def _isfullcopytraceable(repo, c1, base):
416 421 """ Checks that if base, source and destination are all no-public branches,
417 422 if yes let's use the full copytrace algorithm for increased capabilities
418 423 since it will be fast enough.
419 424
420 425 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
421 426 number of changesets from c1 to base such that if number of changesets are
422 427 more than the limit, full copytracing algorithm won't be used.
423 428 """
424 429 if c1.rev() is None:
425 430 c1 = c1.p1()
426 431 if c1.mutable() and base.mutable():
427 432 sourcecommitlimit = repo.ui.configint('experimental',
428 433 'copytrace.sourcecommitlimit')
429 434 commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
430 435 return commits < sourcecommitlimit
431 436 return False
432 437
433 438 def _fullcopytracing(repo, c1, c2, base):
434 439 """ The full copytracing algorithm which finds all the new files that were
435 440 added from merge base up to the top commit and for each file it checks if
436 441 this file was copied from another file.
437 442
438 443 This is pretty slow when a lot of changesets are involved but will track all
439 444 the copies.
440 445 """
441 446 # In certain scenarios (e.g. graft, update or rebase), base can be
442 447 # overridden We still need to know a real common ancestor in this case We
443 448 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
444 449 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
445 450 # caller may not know if the revision passed in lieu of the CA is a genuine
446 451 # common ancestor or not without explicitly checking it, it's better to
447 452 # determine that here.
448 453 #
449 454 # base.isancestorof(wc) is False, work around that
450 455 _c1 = c1.p1() if c1.rev() is None else c1
451 456 _c2 = c2.p1() if c2.rev() is None else c2
452 457 # an endpoint is "dirty" if it isn't a descendant of the merge base
453 458 # if we have a dirty endpoint, we need to trigger graft logic, and also
454 459 # keep track of which endpoint is dirty
455 460 dirtyc1 = not base.isancestorof(_c1)
456 461 dirtyc2 = not base.isancestorof(_c2)
457 462 graft = dirtyc1 or dirtyc2
458 463 tca = base
459 464 if graft:
460 465 tca = _c1.ancestor(_c2)
461 466
462 467 limit = _findlimit(repo, c1.rev(), c2.rev())
463 468 if limit is None:
464 469 # no common ancestor, no copies
465 470 return {}, {}, {}, {}, {}
466 471 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
467 472
468 473 m1 = c1.manifest()
469 474 m2 = c2.manifest()
470 475 mb = base.manifest()
471 476
472 477 # gather data from _checkcopies:
473 478 # - diverge = record all diverges in this dict
474 479 # - copy = record all non-divergent copies in this dict
475 480 # - fullcopy = record all copies in this dict
476 481 # - incomplete = record non-divergent partial copies here
477 482 # - incompletediverge = record divergent partial copies here
478 483 diverge = {} # divergence data is shared
479 484 incompletediverge = {}
480 485 data1 = {'copy': {},
481 486 'fullcopy': {},
482 487 'incomplete': {},
483 488 'diverge': diverge,
484 489 'incompletediverge': incompletediverge,
485 490 }
486 491 data2 = {'copy': {},
487 492 'fullcopy': {},
488 493 'incomplete': {},
489 494 'diverge': diverge,
490 495 'incompletediverge': incompletediverge,
491 496 }
492 497
493 498 # find interesting file sets from manifests
494 499 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
495 500 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
496 501 bothnew = sorted(addedinm1 & addedinm2)
497 502 if tca == base:
498 503 # unmatched file from base
499 504 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
500 505 u1u, u2u = u1r, u2r
501 506 else:
502 507 # unmatched file from base (DAG rotation in the graft case)
503 508 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
504 509 baselabel='base')
505 510 # unmatched file from topological common ancestors (no DAG rotation)
506 511 # need to recompute this for directory move handling when grafting
507 512 mta = tca.manifest()
508 513 u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
509 514 m2.filesnotin(mta),
510 515 baselabel='topological common ancestor')
511 516
512 517 for f in u1u:
513 518 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
514 519
515 520 for f in u2u:
516 521 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
517 522
518 523 copy = dict(data1['copy'])
519 524 copy.update(data2['copy'])
520 525 fullcopy = dict(data1['fullcopy'])
521 526 fullcopy.update(data2['fullcopy'])
522 527
523 528 if dirtyc1:
524 529 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
525 530 incompletediverge)
526 531 else:
527 532 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
528 533 incompletediverge)
529 534
530 535 renamedelete = {}
531 536 renamedeleteset = set()
532 537 divergeset = set()
533 538 for of, fl in list(diverge.items()):
534 539 if len(fl) == 1 or of in c1 or of in c2:
535 540 del diverge[of] # not actually divergent, or not a rename
536 541 if of not in c1 and of not in c2:
537 542 # renamed on one side, deleted on the other side, but filter
538 543 # out files that have been renamed and then deleted
539 544 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
540 545 renamedeleteset.update(fl) # reverse map for below
541 546 else:
542 547 divergeset.update(fl) # reverse map for below
543 548
544 549 if bothnew:
545 550 repo.ui.debug(" unmatched files new in both:\n %s\n"
546 551 % "\n ".join(bothnew))
547 552 bothdiverge = {}
548 553 bothincompletediverge = {}
549 554 remainder = {}
550 555 both1 = {'copy': {},
551 556 'fullcopy': {},
552 557 'incomplete': {},
553 558 'diverge': bothdiverge,
554 559 'incompletediverge': bothincompletediverge
555 560 }
556 561 both2 = {'copy': {},
557 562 'fullcopy': {},
558 563 'incomplete': {},
559 564 'diverge': bothdiverge,
560 565 'incompletediverge': bothincompletediverge
561 566 }
562 567 for f in bothnew:
563 568 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
564 569 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
565 570 if dirtyc1:
566 571 # incomplete copies may only be found on the "dirty" side for bothnew
567 572 assert not both2['incomplete']
568 573 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
569 574 bothincompletediverge)
570 575 elif dirtyc2:
571 576 assert not both1['incomplete']
572 577 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
573 578 bothincompletediverge)
574 579 else:
575 580 # incomplete copies and divergences can't happen outside grafts
576 581 assert not both1['incomplete']
577 582 assert not both2['incomplete']
578 583 assert not bothincompletediverge
579 584 for f in remainder:
580 585 assert f not in bothdiverge
581 586 ic = remainder[f]
582 587 if ic[0] in (m1 if dirtyc1 else m2):
583 588 # backed-out rename on one side, but watch out for deleted files
584 589 bothdiverge[f] = ic
585 590 for of, fl in bothdiverge.items():
586 591 if len(fl) == 2 and fl[0] == fl[1]:
587 592 copy[fl[0]] = of # not actually divergent, just matching renames
588 593
589 594 if fullcopy and repo.ui.debugflag:
590 595 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
591 596 "% = renamed and deleted):\n")
592 597 for f in sorted(fullcopy):
593 598 note = ""
594 599 if f in copy:
595 600 note += "*"
596 601 if f in divergeset:
597 602 note += "!"
598 603 if f in renamedeleteset:
599 604 note += "%"
600 605 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
601 606 note))
602 607 del divergeset
603 608
604 609 if not fullcopy:
605 610 return copy, {}, diverge, renamedelete, {}
606 611
607 612 repo.ui.debug(" checking for directory renames\n")
608 613
609 614 # generate a directory move map
610 615 d1, d2 = c1.dirs(), c2.dirs()
611 616 # Hack for adding '', which is not otherwise added, to d1 and d2
612 617 d1.addpath('/')
613 618 d2.addpath('/')
614 619 invalid = set()
615 620 dirmove = {}
616 621
617 622 # examine each file copy for a potential directory move, which is
618 623 # when all the files in a directory are moved to a new directory
619 624 for dst, src in fullcopy.iteritems():
620 625 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
621 626 if dsrc in invalid:
622 627 # already seen to be uninteresting
623 628 continue
624 629 elif dsrc in d1 and ddst in d1:
625 630 # directory wasn't entirely moved locally
626 631 invalid.add(dsrc)
627 632 elif dsrc in d2 and ddst in d2:
628 633 # directory wasn't entirely moved remotely
629 634 invalid.add(dsrc)
630 635 elif dsrc in dirmove and dirmove[dsrc] != ddst:
631 636 # files from the same directory moved to two different places
632 637 invalid.add(dsrc)
633 638 else:
634 639 # looks good so far
635 640 dirmove[dsrc] = ddst
636 641
637 642 for i in invalid:
638 643 if i in dirmove:
639 644 del dirmove[i]
640 645 del d1, d2, invalid
641 646
642 647 if not dirmove:
643 648 return copy, {}, diverge, renamedelete, {}
644 649
645 650 dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
646 651
647 652 for d in dirmove:
648 653 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
649 654 (d, dirmove[d]))
650 655
651 656 movewithdir = {}
652 657 # check unaccounted nonoverlapping files against directory moves
653 658 for f in u1r + u2r:
654 659 if f not in fullcopy:
655 660 for d in dirmove:
656 661 if f.startswith(d):
657 662 # new file added in a directory that was moved, move it
658 663 df = dirmove[d] + f[len(d):]
659 664 if df not in copy:
660 665 movewithdir[f] = df
661 666 repo.ui.debug((" pending file src: '%s' -> "
662 667 "dst: '%s'\n") % (f, df))
663 668 break
664 669
665 670 return copy, movewithdir, diverge, renamedelete, dirmove
666 671
667 672 def _heuristicscopytracing(repo, c1, c2, base):
668 673 """ Fast copytracing using filename heuristics
669 674
670 675 Assumes that moves or renames are of following two types:
671 676
672 677 1) Inside a directory only (same directory name but different filenames)
673 678 2) Move from one directory to another
674 679 (same filenames but different directory names)
675 680
676 681 Works only when there are no merge commits in the "source branch".
677 682 Source branch is commits from base up to c2 not including base.
678 683
679 684 If merge is involved it fallbacks to _fullcopytracing().
680 685
681 686 Can be used by setting the following config:
682 687
683 688 [experimental]
684 689 copytrace = heuristics
685 690
686 691 In some cases the copy/move candidates found by heuristics can be very large
687 692 in number and that will make the algorithm slow. The number of possible
688 693 candidates to check can be limited by using the config
689 694 `experimental.copytrace.movecandidateslimit` which defaults to 100.
690 695 """
691 696
692 697 if c1.rev() is None:
693 698 c1 = c1.p1()
694 699 if c2.rev() is None:
695 700 c2 = c2.p1()
696 701
697 702 copies = {}
698 703
699 704 changedfiles = set()
700 705 m1 = c1.manifest()
701 706 if not repo.revs('%d::%d', base.rev(), c2.rev()):
702 707 # If base is not in c2 branch, we switch to fullcopytracing
703 708 repo.ui.debug("switching to full copytracing as base is not "
704 709 "an ancestor of c2\n")
705 710 return _fullcopytracing(repo, c1, c2, base)
706 711
707 712 ctx = c2
708 713 while ctx != base:
709 714 if len(ctx.parents()) == 2:
710 715 # To keep things simple let's not handle merges
711 716 repo.ui.debug("switching to full copytracing because of merges\n")
712 717 return _fullcopytracing(repo, c1, c2, base)
713 718 changedfiles.update(ctx.files())
714 719 ctx = ctx.p1()
715 720
716 721 cp = _forwardcopies(base, c2)
717 722 for dst, src in cp.iteritems():
718 723 if src in m1:
719 724 copies[dst] = src
720 725
721 726 # file is missing if it isn't present in the destination, but is present in
722 727 # the base and present in the source.
723 728 # Presence in the base is important to exclude added files, presence in the
724 729 # source is important to exclude removed files.
725 730 filt = lambda f: f not in m1 and f in base and f in c2
726 731 missingfiles = [f for f in changedfiles if filt(f)]
727 732
728 733 if missingfiles:
729 734 basenametofilename = collections.defaultdict(list)
730 735 dirnametofilename = collections.defaultdict(list)
731 736
732 737 for f in m1.filesnotin(base.manifest()):
733 738 basename = os.path.basename(f)
734 739 dirname = os.path.dirname(f)
735 740 basenametofilename[basename].append(f)
736 741 dirnametofilename[dirname].append(f)
737 742
738 743 for f in missingfiles:
739 744 basename = os.path.basename(f)
740 745 dirname = os.path.dirname(f)
741 746 samebasename = basenametofilename[basename]
742 747 samedirname = dirnametofilename[dirname]
743 748 movecandidates = samebasename + samedirname
744 749 # f is guaranteed to be present in c2, that's why
745 750 # c2.filectx(f) won't fail
746 751 f2 = c2.filectx(f)
747 752 # we can have a lot of candidates which can slow down the heuristics
748 753 # config value to limit the number of candidates moves to check
749 754 maxcandidates = repo.ui.configint('experimental',
750 755 'copytrace.movecandidateslimit')
751 756
752 757 if len(movecandidates) > maxcandidates:
753 758 repo.ui.status(_("skipping copytracing for '%s', more "
754 759 "candidates than the limit: %d\n")
755 760 % (f, len(movecandidates)))
756 761 continue
757 762
758 763 for candidate in movecandidates:
759 764 f1 = c1.filectx(candidate)
760 765 if _related(f1, f2):
761 766 # if there are a few related copies then we'll merge
762 767 # changes into all of them. This matches the behaviour
763 768 # of upstream copytracing
764 769 copies[candidate] = f
765 770
766 771 return copies, {}, {}, {}, {}
767 772
768 773 def _related(f1, f2):
769 774 """return True if f1 and f2 filectx have a common ancestor
770 775
771 776 Walk back to common ancestor to see if the two files originate
772 777 from the same file. Since workingfilectx's rev() is None it messes
773 778 up the integer comparison logic, hence the pre-step check for
774 779 None (f1 and f2 can only be workingfilectx's initially).
775 780 """
776 781
777 782 if f1 == f2:
778 783 return f1 # a match
779 784
780 785 g1, g2 = f1.ancestors(), f2.ancestors()
781 786 try:
782 787 f1r, f2r = f1.linkrev(), f2.linkrev()
783 788
784 789 if f1r is None:
785 790 f1 = next(g1)
786 791 if f2r is None:
787 792 f2 = next(g2)
788 793
789 794 while True:
790 795 f1r, f2r = f1.linkrev(), f2.linkrev()
791 796 if f1r > f2r:
792 797 f1 = next(g1)
793 798 elif f2r > f1r:
794 799 f2 = next(g2)
795 800 else: # f1 and f2 point to files in the same linkrev
796 801 return f1 == f2 # true if they point to the same file
797 802 except StopIteration:
798 803 return False
799 804
800 805 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
801 806 """
802 807 check possible copies of f from msrc to mdst
803 808
804 809 srcctx = starting context for f in msrc
805 810 dstctx = destination context for f in mdst
806 811 f = the filename to check (as in msrc)
807 812 base = the changectx used as a merge base
808 813 tca = topological common ancestor for graft-like scenarios
809 814 remotebase = True if base is outside tca::srcctx, False otherwise
810 815 limit = the rev number to not search beyond
811 816 data = dictionary of dictionary to store copy data. (see mergecopies)
812 817
813 818 note: limit is only an optimization, and provides no guarantee that
814 819 irrelevant revisions will not be visited
815 820 there is no easy way to make this algorithm stop in a guaranteed way
816 821 once it "goes behind a certain revision".
817 822 """
818 823
819 824 msrc = srcctx.manifest()
820 825 mdst = dstctx.manifest()
821 826 mb = base.manifest()
822 827 mta = tca.manifest()
823 828 # Might be true if this call is about finding backward renames,
824 829 # This happens in the case of grafts because the DAG is then rotated.
825 830 # If the file exists in both the base and the source, we are not looking
826 831 # for a rename on the source side, but on the part of the DAG that is
827 832 # traversed backwards.
828 833 #
829 834 # In the case there is both backward and forward renames (before and after
830 835 # the base) this is more complicated as we must detect a divergence.
831 836 # We use 'backwards = False' in that case.
832 837 backwards = not remotebase and base != tca and f in mb
833 838 getsrcfctx = _makegetfctx(srcctx)
834 839 getdstfctx = _makegetfctx(dstctx)
835 840
836 841 if msrc[f] == mb.get(f) and not remotebase:
837 842 # Nothing to merge
838 843 return
839 844
840 845 of = None
841 846 seen = {f}
842 847 for oc in getsrcfctx(f, msrc[f]).ancestors():
843 848 of = oc.path()
844 849 if of in seen:
845 850 # check limit late - grab last rename before
846 851 if oc.linkrev() < limit:
847 852 break
848 853 continue
849 854 seen.add(of)
850 855
851 856 # remember for dir rename detection
852 857 if backwards:
853 858 data['fullcopy'][of] = f # grafting backwards through renames
854 859 else:
855 860 data['fullcopy'][f] = of
856 861 if of not in mdst:
857 862 continue # no match, keep looking
858 863 if mdst[of] == mb.get(of):
859 864 return # no merge needed, quit early
860 865 c2 = getdstfctx(of, mdst[of])
861 866 # c2 might be a plain new file on added on destination side that is
862 867 # unrelated to the droids we are looking for.
863 868 cr = _related(oc, c2)
864 869 if cr and (of == f or of == c2.path()): # non-divergent
865 870 if backwards:
866 871 data['copy'][of] = f
867 872 elif of in mb:
868 873 data['copy'][f] = of
869 874 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
870 875 data['copy'][of] = f
871 876 del data['fullcopy'][f]
872 877 data['fullcopy'][of] = f
873 878 else: # divergence w.r.t. graft CA on one side of topological CA
874 879 for sf in seen:
875 880 if sf in mb:
876 881 assert sf not in data['diverge']
877 882 data['diverge'][sf] = [f, of]
878 883 break
879 884 return
880 885
881 886 if of in mta:
882 887 if backwards or remotebase:
883 888 data['incomplete'][of] = f
884 889 else:
885 890 for sf in seen:
886 891 if sf in mb:
887 892 if tca == base:
888 893 data['diverge'].setdefault(sf, []).append(f)
889 894 else:
890 895 data['incompletediverge'][sf] = [of, f]
891 896 return
892 897
893 898 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
894 899 """reproduce copies from fromrev to rev in the dirstate
895 900
896 901 If skiprev is specified, it's a revision that should be used to
897 902 filter copy records. Any copies that occur between fromrev and
898 903 skiprev will not be duplicated, even if they appear in the set of
899 904 copies between fromrev and rev.
900 905 """
901 906 exclude = {}
902 907 ctraceconfig = repo.ui.config('experimental', 'copytrace')
903 908 bctrace = stringutil.parsebool(ctraceconfig)
904 909 if (skiprev is not None and
905 910 (ctraceconfig == 'heuristics' or bctrace or bctrace is None)):
906 911 # copytrace='off' skips this line, but not the entire function because
907 912 # the line below is O(size of the repo) during a rebase, while the rest
908 913 # of the function is much faster (and is required for carrying copy
909 914 # metadata across the rebase anyway).
910 915 exclude = pathcopies(repo[fromrev], repo[skiprev])
911 916 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
912 917 # copies.pathcopies returns backward renames, so dst might not
913 918 # actually be in the dirstate
914 919 if dst in exclude:
915 920 continue
916 921 wctx[dst].markcopied(src)
@@ -1,1683 +1,1684
1 1
2 2 $ add()
3 3 > {
4 4 > echo $2 >> $1
5 5 > }
6 6 $ hg init t
7 7 $ cd t
8 8
9 9 set up a boring main branch
10 10
11 11 $ add a a
12 12 $ hg add a
13 13 $ mkdir x
14 14 $ add x/x x
15 15 $ hg add x/x
16 16 $ hg ci -m0
17 17 $ add a m1
18 18 $ hg ci -m1
19 19 $ add a m2
20 20 $ add x/y y1
21 21 $ hg add x/y
22 22 $ hg ci -m2
23 23 $ cd ..
24 24
25 25 $ show()
26 26 > {
27 27 > echo "# $2:"
28 28 > echo
29 29 > echo "% hg st -C $1"
30 30 > hg st -C $1
31 31 > echo
32 32 > echo "% hg diff --git $1"
33 33 > hg diff --git $1
34 34 > echo
35 35 > }
36 36 $ count=0
37 37
38 38 make a new branch and get diff/status output
39 39 $1 - first commit
40 40 $2 - second commit
41 41 $3 - working dir action
42 42
43 43 $ tb()
44 44 > {
45 45 > hg clone -q t t2 ; cd t2
46 46 > hg co -q -C 0
47 47 >
48 48 > echo % add a $count
49 49 > add a $count
50 50 > count=`expr $count + 1`
51 51 > echo % hg ci -m "t0"
52 52 > hg ci -m "t0"
53 53 > echo % $1
54 54 > $1
55 55 > echo % hg ci -m "t1"
56 56 > hg ci -m "t1"
57 57 > echo % $2
58 58 > $2
59 59 > echo % hg ci -m "t2"
60 60 > hg ci -m "t2"
61 61 > echo % $3
62 62 > $3
63 63 > echo
64 64 > show "" "working to parent"
65 65 > show "--rev 0" "working to root"
66 66 > show "--rev 2" "working to branch"
67 67 > show "--rev 0 --rev ." "root to parent"
68 68 > show "--rev . --rev 0" "parent to root"
69 69 > show "--rev 2 --rev ." "branch to parent"
70 70 > show "--rev . --rev 2" "parent to branch"
71 71 > echo
72 72 > cd ..
73 73 > rm -rf t2
74 74 > }
75 75
76 76 rename in working dir
77 77
78 78 $ tb "add a a1" "add a a2" "hg mv a b"
79 79 % add a 0
80 80 % hg ci -m t0
81 81 created new head
82 82 % add a a1
83 83 % hg ci -m t1
84 84 % add a a2
85 85 % hg ci -m t2
86 86 % hg mv a b
87 87
88 88 # working to parent:
89 89
90 90 % hg st -C
91 91 A b
92 92 a
93 93 R a
94 94
95 95 % hg diff --git
96 96 diff --git a/a b/b
97 97 rename from a
98 98 rename to b
99 99
100 100 # working to root:
101 101
102 102 % hg st -C --rev 0
103 103 A b
104 104 a
105 105 R a
106 106
107 107 % hg diff --git --rev 0
108 108 diff --git a/a b/b
109 109 rename from a
110 110 rename to b
111 111 --- a/a
112 112 +++ b/b
113 113 @@ -1,1 +1,4 @@
114 114 a
115 115 +0
116 116 +a1
117 117 +a2
118 118
119 119 # working to branch:
120 120
121 121 % hg st -C --rev 2
122 122 A b
123 123 a
124 124 R a
125 125 R x/y
126 126
127 127 % hg diff --git --rev 2
128 128 diff --git a/a b/b
129 129 rename from a
130 130 rename to b
131 131 --- a/a
132 132 +++ b/b
133 133 @@ -1,3 +1,4 @@
134 134 a
135 135 -m1
136 136 -m2
137 137 +0
138 138 +a1
139 139 +a2
140 140 diff --git a/x/y b/x/y
141 141 deleted file mode 100644
142 142 --- a/x/y
143 143 +++ /dev/null
144 144 @@ -1,1 +0,0 @@
145 145 -y1
146 146
147 147 # root to parent:
148 148
149 149 % hg st -C --rev 0 --rev .
150 150 M a
151 151
152 152 % hg diff --git --rev 0 --rev .
153 153 diff --git a/a b/a
154 154 --- a/a
155 155 +++ b/a
156 156 @@ -1,1 +1,4 @@
157 157 a
158 158 +0
159 159 +a1
160 160 +a2
161 161
162 162 # parent to root:
163 163
164 164 % hg st -C --rev . --rev 0
165 165 M a
166 166
167 167 % hg diff --git --rev . --rev 0
168 168 diff --git a/a b/a
169 169 --- a/a
170 170 +++ b/a
171 171 @@ -1,4 +1,1 @@
172 172 a
173 173 -0
174 174 -a1
175 175 -a2
176 176
177 177 # branch to parent:
178 178
179 179 % hg st -C --rev 2 --rev .
180 180 M a
181 181 R x/y
182 182
183 183 % hg diff --git --rev 2 --rev .
184 184 diff --git a/a b/a
185 185 --- a/a
186 186 +++ b/a
187 187 @@ -1,3 +1,4 @@
188 188 a
189 189 -m1
190 190 -m2
191 191 +0
192 192 +a1
193 193 +a2
194 194 diff --git a/x/y b/x/y
195 195 deleted file mode 100644
196 196 --- a/x/y
197 197 +++ /dev/null
198 198 @@ -1,1 +0,0 @@
199 199 -y1
200 200
201 201 # parent to branch:
202 202
203 203 % hg st -C --rev . --rev 2
204 204 M a
205 205 A x/y
206 206
207 207 % hg diff --git --rev . --rev 2
208 208 diff --git a/a b/a
209 209 --- a/a
210 210 +++ b/a
211 211 @@ -1,4 +1,3 @@
212 212 a
213 213 -0
214 214 -a1
215 215 -a2
216 216 +m1
217 217 +m2
218 218 diff --git a/x/y b/x/y
219 219 new file mode 100644
220 220 --- /dev/null
221 221 +++ b/x/y
222 222 @@ -0,0 +1,1 @@
223 223 +y1
224 224
225 225
226 226 copy in working dir
227 227
228 228 $ tb "add a a1" "add a a2" "hg cp a b"
229 229 % add a 1
230 230 % hg ci -m t0
231 231 created new head
232 232 % add a a1
233 233 % hg ci -m t1
234 234 % add a a2
235 235 % hg ci -m t2
236 236 % hg cp a b
237 237
238 238 # working to parent:
239 239
240 240 % hg st -C
241 241 A b
242 242 a
243 243
244 244 % hg diff --git
245 245 diff --git a/a b/b
246 246 copy from a
247 247 copy to b
248 248
249 249 # working to root:
250 250
251 251 % hg st -C --rev 0
252 252 M a
253 253 A b
254 254 a
255 255
256 256 % hg diff --git --rev 0
257 257 diff --git a/a b/a
258 258 --- a/a
259 259 +++ b/a
260 260 @@ -1,1 +1,4 @@
261 261 a
262 262 +1
263 263 +a1
264 264 +a2
265 265 diff --git a/a b/b
266 266 copy from a
267 267 copy to b
268 268 --- a/a
269 269 +++ b/b
270 270 @@ -1,1 +1,4 @@
271 271 a
272 272 +1
273 273 +a1
274 274 +a2
275 275
276 276 # working to branch:
277 277
278 278 % hg st -C --rev 2
279 279 M a
280 280 A b
281 281 a
282 282 R x/y
283 283
284 284 % hg diff --git --rev 2
285 285 diff --git a/a b/a
286 286 --- a/a
287 287 +++ b/a
288 288 @@ -1,3 +1,4 @@
289 289 a
290 290 -m1
291 291 -m2
292 292 +1
293 293 +a1
294 294 +a2
295 295 diff --git a/a b/b
296 296 copy from a
297 297 copy to b
298 298 --- a/a
299 299 +++ b/b
300 300 @@ -1,3 +1,4 @@
301 301 a
302 302 -m1
303 303 -m2
304 304 +1
305 305 +a1
306 306 +a2
307 307 diff --git a/x/y b/x/y
308 308 deleted file mode 100644
309 309 --- a/x/y
310 310 +++ /dev/null
311 311 @@ -1,1 +0,0 @@
312 312 -y1
313 313
314 314 # root to parent:
315 315
316 316 % hg st -C --rev 0 --rev .
317 317 M a
318 318
319 319 % hg diff --git --rev 0 --rev .
320 320 diff --git a/a b/a
321 321 --- a/a
322 322 +++ b/a
323 323 @@ -1,1 +1,4 @@
324 324 a
325 325 +1
326 326 +a1
327 327 +a2
328 328
329 329 # parent to root:
330 330
331 331 % hg st -C --rev . --rev 0
332 332 M a
333 333
334 334 % hg diff --git --rev . --rev 0
335 335 diff --git a/a b/a
336 336 --- a/a
337 337 +++ b/a
338 338 @@ -1,4 +1,1 @@
339 339 a
340 340 -1
341 341 -a1
342 342 -a2
343 343
344 344 # branch to parent:
345 345
346 346 % hg st -C --rev 2 --rev .
347 347 M a
348 348 R x/y
349 349
350 350 % hg diff --git --rev 2 --rev .
351 351 diff --git a/a b/a
352 352 --- a/a
353 353 +++ b/a
354 354 @@ -1,3 +1,4 @@
355 355 a
356 356 -m1
357 357 -m2
358 358 +1
359 359 +a1
360 360 +a2
361 361 diff --git a/x/y b/x/y
362 362 deleted file mode 100644
363 363 --- a/x/y
364 364 +++ /dev/null
365 365 @@ -1,1 +0,0 @@
366 366 -y1
367 367
368 368 # parent to branch:
369 369
370 370 % hg st -C --rev . --rev 2
371 371 M a
372 372 A x/y
373 373
374 374 % hg diff --git --rev . --rev 2
375 375 diff --git a/a b/a
376 376 --- a/a
377 377 +++ b/a
378 378 @@ -1,4 +1,3 @@
379 379 a
380 380 -1
381 381 -a1
382 382 -a2
383 383 +m1
384 384 +m2
385 385 diff --git a/x/y b/x/y
386 386 new file mode 100644
387 387 --- /dev/null
388 388 +++ b/x/y
389 389 @@ -0,0 +1,1 @@
390 390 +y1
391 391
392 392
393 393 single rename
394 394
395 395 $ tb "hg mv a b" "add b b1" "add b w"
396 396 % add a 2
397 397 % hg ci -m t0
398 398 created new head
399 399 % hg mv a b
400 400 % hg ci -m t1
401 401 % add b b1
402 402 % hg ci -m t2
403 403 % add b w
404 404
405 405 # working to parent:
406 406
407 407 % hg st -C
408 408 M b
409 409
410 410 % hg diff --git
411 411 diff --git a/b b/b
412 412 --- a/b
413 413 +++ b/b
414 414 @@ -1,3 +1,4 @@
415 415 a
416 416 2
417 417 b1
418 418 +w
419 419
420 420 # working to root:
421 421
422 422 % hg st -C --rev 0
423 423 A b
424 424 a
425 425 R a
426 426
427 427 % hg diff --git --rev 0
428 428 diff --git a/a b/b
429 429 rename from a
430 430 rename to b
431 431 --- a/a
432 432 +++ b/b
433 433 @@ -1,1 +1,4 @@
434 434 a
435 435 +2
436 436 +b1
437 437 +w
438 438
439 439 # working to branch:
440 440
441 441 % hg st -C --rev 2
442 442 A b
443 443 a
444 444 R a
445 445 R x/y
446 446
447 447 % hg diff --git --rev 2
448 448 diff --git a/a b/b
449 449 rename from a
450 450 rename to b
451 451 --- a/a
452 452 +++ b/b
453 453 @@ -1,3 +1,4 @@
454 454 a
455 455 -m1
456 456 -m2
457 457 +2
458 458 +b1
459 459 +w
460 460 diff --git a/x/y b/x/y
461 461 deleted file mode 100644
462 462 --- a/x/y
463 463 +++ /dev/null
464 464 @@ -1,1 +0,0 @@
465 465 -y1
466 466
467 467 # root to parent:
468 468
469 469 % hg st -C --rev 0 --rev .
470 470 A b
471 471 a
472 472 R a
473 473
474 474 % hg diff --git --rev 0 --rev .
475 475 diff --git a/a b/b
476 476 rename from a
477 477 rename to b
478 478 --- a/a
479 479 +++ b/b
480 480 @@ -1,1 +1,3 @@
481 481 a
482 482 +2
483 483 +b1
484 484
485 485 # parent to root:
486 486
487 487 % hg st -C --rev . --rev 0
488 488 A a
489 489 b
490 490 R b
491 491
492 492 % hg diff --git --rev . --rev 0
493 493 diff --git a/b b/a
494 494 rename from b
495 495 rename to a
496 496 --- a/b
497 497 +++ b/a
498 498 @@ -1,3 +1,1 @@
499 499 a
500 500 -2
501 501 -b1
502 502
503 503 # branch to parent:
504 504
505 505 % hg st -C --rev 2 --rev .
506 506 A b
507 507 a
508 508 R a
509 509 R x/y
510 510
511 511 % hg diff --git --rev 2 --rev .
512 512 diff --git a/a b/b
513 513 rename from a
514 514 rename to b
515 515 --- a/a
516 516 +++ b/b
517 517 @@ -1,3 +1,3 @@
518 518 a
519 519 -m1
520 520 -m2
521 521 +2
522 522 +b1
523 523 diff --git a/x/y b/x/y
524 524 deleted file mode 100644
525 525 --- a/x/y
526 526 +++ /dev/null
527 527 @@ -1,1 +0,0 @@
528 528 -y1
529 529
530 530 # parent to branch:
531 531
532 532 % hg st -C --rev . --rev 2
533 533 A a
534 534 b
535 535 A x/y
536 536 R b
537 537
538 538 % hg diff --git --rev . --rev 2
539 539 diff --git a/b b/a
540 540 rename from b
541 541 rename to a
542 542 --- a/b
543 543 +++ b/a
544 544 @@ -1,3 +1,3 @@
545 545 a
546 546 -2
547 547 -b1
548 548 +m1
549 549 +m2
550 550 diff --git a/x/y b/x/y
551 551 new file mode 100644
552 552 --- /dev/null
553 553 +++ b/x/y
554 554 @@ -0,0 +1,1 @@
555 555 +y1
556 556
557 557
558 558 single copy
559 559
560 560 $ tb "hg cp a b" "add b b1" "add a w"
561 561 % add a 3
562 562 % hg ci -m t0
563 563 created new head
564 564 % hg cp a b
565 565 % hg ci -m t1
566 566 % add b b1
567 567 % hg ci -m t2
568 568 % add a w
569 569
570 570 # working to parent:
571 571
572 572 % hg st -C
573 573 M a
574 574
575 575 % hg diff --git
576 576 diff --git a/a b/a
577 577 --- a/a
578 578 +++ b/a
579 579 @@ -1,2 +1,3 @@
580 580 a
581 581 3
582 582 +w
583 583
584 584 # working to root:
585 585
586 586 % hg st -C --rev 0
587 587 M a
588 588 A b
589 589 a
590 590
591 591 % hg diff --git --rev 0
592 592 diff --git a/a b/a
593 593 --- a/a
594 594 +++ b/a
595 595 @@ -1,1 +1,3 @@
596 596 a
597 597 +3
598 598 +w
599 599 diff --git a/a b/b
600 600 copy from a
601 601 copy to b
602 602 --- a/a
603 603 +++ b/b
604 604 @@ -1,1 +1,3 @@
605 605 a
606 606 +3
607 607 +b1
608 608
609 609 # working to branch:
610 610
611 611 % hg st -C --rev 2
612 612 M a
613 613 A b
614 614 a
615 615 R x/y
616 616
617 617 % hg diff --git --rev 2
618 618 diff --git a/a b/a
619 619 --- a/a
620 620 +++ b/a
621 621 @@ -1,3 +1,3 @@
622 622 a
623 623 -m1
624 624 -m2
625 625 +3
626 626 +w
627 627 diff --git a/a b/b
628 628 copy from a
629 629 copy to b
630 630 --- a/a
631 631 +++ b/b
632 632 @@ -1,3 +1,3 @@
633 633 a
634 634 -m1
635 635 -m2
636 636 +3
637 637 +b1
638 638 diff --git a/x/y b/x/y
639 639 deleted file mode 100644
640 640 --- a/x/y
641 641 +++ /dev/null
642 642 @@ -1,1 +0,0 @@
643 643 -y1
644 644
645 645 # root to parent:
646 646
647 647 % hg st -C --rev 0 --rev .
648 648 M a
649 649 A b
650 650 a
651 651
652 652 % hg diff --git --rev 0 --rev .
653 653 diff --git a/a b/a
654 654 --- a/a
655 655 +++ b/a
656 656 @@ -1,1 +1,2 @@
657 657 a
658 658 +3
659 659 diff --git a/a b/b
660 660 copy from a
661 661 copy to b
662 662 --- a/a
663 663 +++ b/b
664 664 @@ -1,1 +1,3 @@
665 665 a
666 666 +3
667 667 +b1
668 668
669 669 # parent to root:
670 670
671 671 % hg st -C --rev . --rev 0
672 672 M a
673 673 R b
674 674
675 675 % hg diff --git --rev . --rev 0
676 676 diff --git a/a b/a
677 677 --- a/a
678 678 +++ b/a
679 679 @@ -1,2 +1,1 @@
680 680 a
681 681 -3
682 682 diff --git a/b b/b
683 683 deleted file mode 100644
684 684 --- a/b
685 685 +++ /dev/null
686 686 @@ -1,3 +0,0 @@
687 687 -a
688 688 -3
689 689 -b1
690 690
691 691 # branch to parent:
692 692
693 693 % hg st -C --rev 2 --rev .
694 694 M a
695 695 A b
696 696 a
697 697 R x/y
698 698
699 699 % hg diff --git --rev 2 --rev .
700 700 diff --git a/a b/a
701 701 --- a/a
702 702 +++ b/a
703 703 @@ -1,3 +1,2 @@
704 704 a
705 705 -m1
706 706 -m2
707 707 +3
708 708 diff --git a/a b/b
709 709 copy from a
710 710 copy to b
711 711 --- a/a
712 712 +++ b/b
713 713 @@ -1,3 +1,3 @@
714 714 a
715 715 -m1
716 716 -m2
717 717 +3
718 718 +b1
719 719 diff --git a/x/y b/x/y
720 720 deleted file mode 100644
721 721 --- a/x/y
722 722 +++ /dev/null
723 723 @@ -1,1 +0,0 @@
724 724 -y1
725 725
726 726 # parent to branch:
727 727
728 728 % hg st -C --rev . --rev 2
729 729 M a
730 730 A x/y
731 731 R b
732 732
733 733 % hg diff --git --rev . --rev 2
734 734 diff --git a/a b/a
735 735 --- a/a
736 736 +++ b/a
737 737 @@ -1,2 +1,3 @@
738 738 a
739 739 -3
740 740 +m1
741 741 +m2
742 742 diff --git a/b b/b
743 743 deleted file mode 100644
744 744 --- a/b
745 745 +++ /dev/null
746 746 @@ -1,3 +0,0 @@
747 747 -a
748 748 -3
749 749 -b1
750 750 diff --git a/x/y b/x/y
751 751 new file mode 100644
752 752 --- /dev/null
753 753 +++ b/x/y
754 754 @@ -0,0 +1,1 @@
755 755 +y1
756 756
757 757
758 758 rename chain
759 759
760 760 $ tb "hg mv a b" "hg mv b c" "hg mv c d"
761 761 % add a 4
762 762 % hg ci -m t0
763 763 created new head
764 764 % hg mv a b
765 765 % hg ci -m t1
766 766 % hg mv b c
767 767 % hg ci -m t2
768 768 % hg mv c d
769 769
770 770 # working to parent:
771 771
772 772 % hg st -C
773 773 A d
774 774 c
775 775 R c
776 776
777 777 % hg diff --git
778 778 diff --git a/c b/d
779 779 rename from c
780 780 rename to d
781 781
782 782 # working to root:
783 783
784 784 % hg st -C --rev 0
785 785 A d
786 786 a
787 787 R a
788 788
789 789 % hg diff --git --rev 0
790 790 diff --git a/a b/d
791 791 rename from a
792 792 rename to d
793 793 --- a/a
794 794 +++ b/d
795 795 @@ -1,1 +1,2 @@
796 796 a
797 797 +4
798 798
799 799 # working to branch:
800 800
801 801 % hg st -C --rev 2
802 802 A d
803 803 a
804 804 R a
805 805 R x/y
806 806
807 807 % hg diff --git --rev 2
808 808 diff --git a/a b/d
809 809 rename from a
810 810 rename to d
811 811 --- a/a
812 812 +++ b/d
813 813 @@ -1,3 +1,2 @@
814 814 a
815 815 -m1
816 816 -m2
817 817 +4
818 818 diff --git a/x/y b/x/y
819 819 deleted file mode 100644
820 820 --- a/x/y
821 821 +++ /dev/null
822 822 @@ -1,1 +0,0 @@
823 823 -y1
824 824
825 825 # root to parent:
826 826
827 827 % hg st -C --rev 0 --rev .
828 828 A c
829 829 a
830 830 R a
831 831
832 832 % hg diff --git --rev 0 --rev .
833 833 diff --git a/a b/c
834 834 rename from a
835 835 rename to c
836 836 --- a/a
837 837 +++ b/c
838 838 @@ -1,1 +1,2 @@
839 839 a
840 840 +4
841 841
842 842 # parent to root:
843 843
844 844 % hg st -C --rev . --rev 0
845 845 A a
846 846 c
847 847 R c
848 848
849 849 % hg diff --git --rev . --rev 0
850 850 diff --git a/c b/a
851 851 rename from c
852 852 rename to a
853 853 --- a/c
854 854 +++ b/a
855 855 @@ -1,2 +1,1 @@
856 856 a
857 857 -4
858 858
859 859 # branch to parent:
860 860
861 861 % hg st -C --rev 2 --rev .
862 862 A c
863 863 a
864 864 R a
865 865 R x/y
866 866
867 867 % hg diff --git --rev 2 --rev .
868 868 diff --git a/a b/c
869 869 rename from a
870 870 rename to c
871 871 --- a/a
872 872 +++ b/c
873 873 @@ -1,3 +1,2 @@
874 874 a
875 875 -m1
876 876 -m2
877 877 +4
878 878 diff --git a/x/y b/x/y
879 879 deleted file mode 100644
880 880 --- a/x/y
881 881 +++ /dev/null
882 882 @@ -1,1 +0,0 @@
883 883 -y1
884 884
885 885 # parent to branch:
886 886
887 887 % hg st -C --rev . --rev 2
888 888 A a
889 889 c
890 890 A x/y
891 891 R c
892 892
893 893 % hg diff --git --rev . --rev 2
894 894 diff --git a/c b/a
895 895 rename from c
896 896 rename to a
897 897 --- a/c
898 898 +++ b/a
899 899 @@ -1,2 +1,3 @@
900 900 a
901 901 -4
902 902 +m1
903 903 +m2
904 904 diff --git a/x/y b/x/y
905 905 new file mode 100644
906 906 --- /dev/null
907 907 +++ b/x/y
908 908 @@ -0,0 +1,1 @@
909 909 +y1
910 910
911 911
912 912 copy chain
913 913
914 914 $ tb "hg cp a b" "hg cp b c" "hg cp c d"
915 915 % add a 5
916 916 % hg ci -m t0
917 917 created new head
918 918 % hg cp a b
919 919 % hg ci -m t1
920 920 % hg cp b c
921 921 % hg ci -m t2
922 922 % hg cp c d
923 923
924 924 # working to parent:
925 925
926 926 % hg st -C
927 927 A d
928 928 c
929 929
930 930 % hg diff --git
931 931 diff --git a/c b/d
932 932 copy from c
933 933 copy to d
934 934
935 935 # working to root:
936 936
937 937 % hg st -C --rev 0
938 938 M a
939 939 A b
940 940 a
941 941 A c
942 942 a
943 943 A d
944 944 a
945 945
946 946 % hg diff --git --rev 0
947 947 diff --git a/a b/a
948 948 --- a/a
949 949 +++ b/a
950 950 @@ -1,1 +1,2 @@
951 951 a
952 952 +5
953 953 diff --git a/a b/b
954 954 copy from a
955 955 copy to b
956 956 --- a/a
957 957 +++ b/b
958 958 @@ -1,1 +1,2 @@
959 959 a
960 960 +5
961 961 diff --git a/a b/c
962 962 copy from a
963 963 copy to c
964 964 --- a/a
965 965 +++ b/c
966 966 @@ -1,1 +1,2 @@
967 967 a
968 968 +5
969 969 diff --git a/a b/d
970 970 copy from a
971 971 copy to d
972 972 --- a/a
973 973 +++ b/d
974 974 @@ -1,1 +1,2 @@
975 975 a
976 976 +5
977 977
978 978 # working to branch:
979 979
980 980 % hg st -C --rev 2
981 981 M a
982 982 A b
983 983 a
984 984 A c
985 985 a
986 986 A d
987 987 a
988 988 R x/y
989 989
990 990 % hg diff --git --rev 2
991 991 diff --git a/a b/a
992 992 --- a/a
993 993 +++ b/a
994 994 @@ -1,3 +1,2 @@
995 995 a
996 996 -m1
997 997 -m2
998 998 +5
999 999 diff --git a/a b/b
1000 1000 copy from a
1001 1001 copy to b
1002 1002 --- a/a
1003 1003 +++ b/b
1004 1004 @@ -1,3 +1,2 @@
1005 1005 a
1006 1006 -m1
1007 1007 -m2
1008 1008 +5
1009 1009 diff --git a/a b/c
1010 1010 copy from a
1011 1011 copy to c
1012 1012 --- a/a
1013 1013 +++ b/c
1014 1014 @@ -1,3 +1,2 @@
1015 1015 a
1016 1016 -m1
1017 1017 -m2
1018 1018 +5
1019 1019 diff --git a/a b/d
1020 1020 copy from a
1021 1021 copy to d
1022 1022 --- a/a
1023 1023 +++ b/d
1024 1024 @@ -1,3 +1,2 @@
1025 1025 a
1026 1026 -m1
1027 1027 -m2
1028 1028 +5
1029 1029 diff --git a/x/y b/x/y
1030 1030 deleted file mode 100644
1031 1031 --- a/x/y
1032 1032 +++ /dev/null
1033 1033 @@ -1,1 +0,0 @@
1034 1034 -y1
1035 1035
1036 1036 # root to parent:
1037 1037
1038 1038 % hg st -C --rev 0 --rev .
1039 1039 M a
1040 1040 A b
1041 1041 a
1042 1042 A c
1043 1043 a
1044 1044
1045 1045 % hg diff --git --rev 0 --rev .
1046 1046 diff --git a/a b/a
1047 1047 --- a/a
1048 1048 +++ b/a
1049 1049 @@ -1,1 +1,2 @@
1050 1050 a
1051 1051 +5
1052 1052 diff --git a/a b/b
1053 1053 copy from a
1054 1054 copy to b
1055 1055 --- a/a
1056 1056 +++ b/b
1057 1057 @@ -1,1 +1,2 @@
1058 1058 a
1059 1059 +5
1060 1060 diff --git a/a b/c
1061 1061 copy from a
1062 1062 copy to c
1063 1063 --- a/a
1064 1064 +++ b/c
1065 1065 @@ -1,1 +1,2 @@
1066 1066 a
1067 1067 +5
1068 1068
1069 1069 # parent to root:
1070 1070
1071 1071 % hg st -C --rev . --rev 0
1072 1072 M a
1073 1073 R b
1074 1074 R c
1075 1075
1076 1076 % hg diff --git --rev . --rev 0
1077 1077 diff --git a/a b/a
1078 1078 --- a/a
1079 1079 +++ b/a
1080 1080 @@ -1,2 +1,1 @@
1081 1081 a
1082 1082 -5
1083 1083 diff --git a/b b/b
1084 1084 deleted file mode 100644
1085 1085 --- a/b
1086 1086 +++ /dev/null
1087 1087 @@ -1,2 +0,0 @@
1088 1088 -a
1089 1089 -5
1090 1090 diff --git a/c b/c
1091 1091 deleted file mode 100644
1092 1092 --- a/c
1093 1093 +++ /dev/null
1094 1094 @@ -1,2 +0,0 @@
1095 1095 -a
1096 1096 -5
1097 1097
1098 1098 # branch to parent:
1099 1099
1100 1100 % hg st -C --rev 2 --rev .
1101 1101 M a
1102 1102 A b
1103 1103 a
1104 1104 A c
1105 1105 a
1106 1106 R x/y
1107 1107
1108 1108 % hg diff --git --rev 2 --rev .
1109 1109 diff --git a/a b/a
1110 1110 --- a/a
1111 1111 +++ b/a
1112 1112 @@ -1,3 +1,2 @@
1113 1113 a
1114 1114 -m1
1115 1115 -m2
1116 1116 +5
1117 1117 diff --git a/a b/b
1118 1118 copy from a
1119 1119 copy to b
1120 1120 --- a/a
1121 1121 +++ b/b
1122 1122 @@ -1,3 +1,2 @@
1123 1123 a
1124 1124 -m1
1125 1125 -m2
1126 1126 +5
1127 1127 diff --git a/a b/c
1128 1128 copy from a
1129 1129 copy to c
1130 1130 --- a/a
1131 1131 +++ b/c
1132 1132 @@ -1,3 +1,2 @@
1133 1133 a
1134 1134 -m1
1135 1135 -m2
1136 1136 +5
1137 1137 diff --git a/x/y b/x/y
1138 1138 deleted file mode 100644
1139 1139 --- a/x/y
1140 1140 +++ /dev/null
1141 1141 @@ -1,1 +0,0 @@
1142 1142 -y1
1143 1143
1144 1144 # parent to branch:
1145 1145
1146 1146 % hg st -C --rev . --rev 2
1147 1147 M a
1148 1148 A x/y
1149 1149 R b
1150 1150 R c
1151 1151
1152 1152 % hg diff --git --rev . --rev 2
1153 1153 diff --git a/a b/a
1154 1154 --- a/a
1155 1155 +++ b/a
1156 1156 @@ -1,2 +1,3 @@
1157 1157 a
1158 1158 -5
1159 1159 +m1
1160 1160 +m2
1161 1161 diff --git a/b b/b
1162 1162 deleted file mode 100644
1163 1163 --- a/b
1164 1164 +++ /dev/null
1165 1165 @@ -1,2 +0,0 @@
1166 1166 -a
1167 1167 -5
1168 1168 diff --git a/c b/c
1169 1169 deleted file mode 100644
1170 1170 --- a/c
1171 1171 +++ /dev/null
1172 1172 @@ -1,2 +0,0 @@
1173 1173 -a
1174 1174 -5
1175 1175 diff --git a/x/y b/x/y
1176 1176 new file mode 100644
1177 1177 --- /dev/null
1178 1178 +++ b/x/y
1179 1179 @@ -0,0 +1,1 @@
1180 1180 +y1
1181 1181
1182 1182
1183 1183 circular rename
1184 1184
1185 1185 $ tb "add a a1" "hg mv a b" "hg mv b a"
1186 1186 % add a 6
1187 1187 % hg ci -m t0
1188 1188 created new head
1189 1189 % add a a1
1190 1190 % hg ci -m t1
1191 1191 % hg mv a b
1192 1192 % hg ci -m t2
1193 1193 % hg mv b a
1194 1194
1195 1195 # working to parent:
1196 1196
1197 1197 % hg st -C
1198 1198 A a
1199 1199 b
1200 1200 R b
1201 1201
1202 1202 % hg diff --git
1203 1203 diff --git a/b b/a
1204 1204 rename from b
1205 1205 rename to a
1206 1206
1207 1207 # working to root:
1208 1208
1209 1209 % hg st -C --rev 0
1210 1210 M a
1211 1211
1212 1212 % hg diff --git --rev 0
1213 1213 diff --git a/a b/a
1214 1214 --- a/a
1215 1215 +++ b/a
1216 1216 @@ -1,1 +1,3 @@
1217 1217 a
1218 1218 +6
1219 1219 +a1
1220 1220
1221 1221 # working to branch:
1222 1222
1223 1223 % hg st -C --rev 2
1224 1224 M a
1225 1225 R x/y
1226 1226
1227 1227 % hg diff --git --rev 2
1228 1228 diff --git a/a b/a
1229 1229 --- a/a
1230 1230 +++ b/a
1231 1231 @@ -1,3 +1,3 @@
1232 1232 a
1233 1233 -m1
1234 1234 -m2
1235 1235 +6
1236 1236 +a1
1237 1237 diff --git a/x/y b/x/y
1238 1238 deleted file mode 100644
1239 1239 --- a/x/y
1240 1240 +++ /dev/null
1241 1241 @@ -1,1 +0,0 @@
1242 1242 -y1
1243 1243
1244 1244 # root to parent:
1245 1245
1246 1246 % hg st -C --rev 0 --rev .
1247 1247 A b
1248 1248 a
1249 1249 R a
1250 1250
1251 1251 % hg diff --git --rev 0 --rev .
1252 1252 diff --git a/a b/b
1253 1253 rename from a
1254 1254 rename to b
1255 1255 --- a/a
1256 1256 +++ b/b
1257 1257 @@ -1,1 +1,3 @@
1258 1258 a
1259 1259 +6
1260 1260 +a1
1261 1261
1262 1262 # parent to root:
1263 1263
1264 1264 % hg st -C --rev . --rev 0
1265 1265 A a
1266 1266 b
1267 1267 R b
1268 1268
1269 1269 % hg diff --git --rev . --rev 0
1270 1270 diff --git a/b b/a
1271 1271 rename from b
1272 1272 rename to a
1273 1273 --- a/b
1274 1274 +++ b/a
1275 1275 @@ -1,3 +1,1 @@
1276 1276 a
1277 1277 -6
1278 1278 -a1
1279 1279
1280 1280 # branch to parent:
1281 1281
1282 1282 % hg st -C --rev 2 --rev .
1283 1283 A b
1284 1284 a
1285 1285 R a
1286 1286 R x/y
1287 1287
1288 1288 % hg diff --git --rev 2 --rev .
1289 1289 diff --git a/a b/b
1290 1290 rename from a
1291 1291 rename to b
1292 1292 --- a/a
1293 1293 +++ b/b
1294 1294 @@ -1,3 +1,3 @@
1295 1295 a
1296 1296 -m1
1297 1297 -m2
1298 1298 +6
1299 1299 +a1
1300 1300 diff --git a/x/y b/x/y
1301 1301 deleted file mode 100644
1302 1302 --- a/x/y
1303 1303 +++ /dev/null
1304 1304 @@ -1,1 +0,0 @@
1305 1305 -y1
1306 1306
1307 1307 # parent to branch:
1308 1308
1309 1309 % hg st -C --rev . --rev 2
1310 1310 A a
1311 1311 b
1312 1312 A x/y
1313 1313 R b
1314 1314
1315 1315 % hg diff --git --rev . --rev 2
1316 1316 diff --git a/b b/a
1317 1317 rename from b
1318 1318 rename to a
1319 1319 --- a/b
1320 1320 +++ b/a
1321 1321 @@ -1,3 +1,3 @@
1322 1322 a
1323 1323 -6
1324 1324 -a1
1325 1325 +m1
1326 1326 +m2
1327 1327 diff --git a/x/y b/x/y
1328 1328 new file mode 100644
1329 1329 --- /dev/null
1330 1330 +++ b/x/y
1331 1331 @@ -0,0 +1,1 @@
1332 1332 +y1
1333 1333
1334 1334
1335 1335 directory move
1336 1336
1337 1337 $ tb "hg mv x y" "add y/x x1" "add y/x x2"
1338 1338 % add a 7
1339 1339 % hg ci -m t0
1340 1340 created new head
1341 1341 % hg mv x y
1342 1342 moving x/x to y/x
1343 1343 % hg ci -m t1
1344 1344 % add y/x x1
1345 1345 % hg ci -m t2
1346 1346 % add y/x x2
1347 1347
1348 1348 # working to parent:
1349 1349
1350 1350 % hg st -C
1351 1351 M y/x
1352 1352
1353 1353 % hg diff --git
1354 1354 diff --git a/y/x b/y/x
1355 1355 --- a/y/x
1356 1356 +++ b/y/x
1357 1357 @@ -1,2 +1,3 @@
1358 1358 x
1359 1359 x1
1360 1360 +x2
1361 1361
1362 1362 # working to root:
1363 1363
1364 1364 % hg st -C --rev 0
1365 1365 M a
1366 1366 A y/x
1367 1367 x/x
1368 1368 R x/x
1369 1369
1370 1370 % hg diff --git --rev 0
1371 1371 diff --git a/a b/a
1372 1372 --- a/a
1373 1373 +++ b/a
1374 1374 @@ -1,1 +1,2 @@
1375 1375 a
1376 1376 +7
1377 1377 diff --git a/x/x b/y/x
1378 1378 rename from x/x
1379 1379 rename to y/x
1380 1380 --- a/x/x
1381 1381 +++ b/y/x
1382 1382 @@ -1,1 +1,3 @@
1383 1383 x
1384 1384 +x1
1385 1385 +x2
1386 1386
1387 1387 # working to branch:
1388 1388
1389 1389 % hg st -C --rev 2
1390 1390 M a
1391 1391 A y/x
1392 1392 x/x
1393 1393 R x/x
1394 1394 R x/y
1395 1395
1396 1396 % hg diff --git --rev 2
1397 1397 diff --git a/a b/a
1398 1398 --- a/a
1399 1399 +++ b/a
1400 1400 @@ -1,3 +1,2 @@
1401 1401 a
1402 1402 -m1
1403 1403 -m2
1404 1404 +7
1405 1405 diff --git a/x/y b/x/y
1406 1406 deleted file mode 100644
1407 1407 --- a/x/y
1408 1408 +++ /dev/null
1409 1409 @@ -1,1 +0,0 @@
1410 1410 -y1
1411 1411 diff --git a/x/x b/y/x
1412 1412 rename from x/x
1413 1413 rename to y/x
1414 1414 --- a/x/x
1415 1415 +++ b/y/x
1416 1416 @@ -1,1 +1,3 @@
1417 1417 x
1418 1418 +x1
1419 1419 +x2
1420 1420
1421 1421 # root to parent:
1422 1422
1423 1423 % hg st -C --rev 0 --rev .
1424 1424 M a
1425 1425 A y/x
1426 1426 x/x
1427 1427 R x/x
1428 1428
1429 1429 % hg diff --git --rev 0 --rev .
1430 1430 diff --git a/a b/a
1431 1431 --- a/a
1432 1432 +++ b/a
1433 1433 @@ -1,1 +1,2 @@
1434 1434 a
1435 1435 +7
1436 1436 diff --git a/x/x b/y/x
1437 1437 rename from x/x
1438 1438 rename to y/x
1439 1439 --- a/x/x
1440 1440 +++ b/y/x
1441 1441 @@ -1,1 +1,2 @@
1442 1442 x
1443 1443 +x1
1444 1444
1445 1445 # parent to root:
1446 1446
1447 1447 % hg st -C --rev . --rev 0
1448 1448 M a
1449 1449 A x/x
1450 1450 y/x
1451 1451 R y/x
1452 1452
1453 1453 % hg diff --git --rev . --rev 0
1454 1454 diff --git a/a b/a
1455 1455 --- a/a
1456 1456 +++ b/a
1457 1457 @@ -1,2 +1,1 @@
1458 1458 a
1459 1459 -7
1460 1460 diff --git a/y/x b/x/x
1461 1461 rename from y/x
1462 1462 rename to x/x
1463 1463 --- a/y/x
1464 1464 +++ b/x/x
1465 1465 @@ -1,2 +1,1 @@
1466 1466 x
1467 1467 -x1
1468 1468
1469 1469 # branch to parent:
1470 1470
1471 1471 % hg st -C --rev 2 --rev .
1472 1472 M a
1473 1473 A y/x
1474 1474 x/x
1475 1475 R x/x
1476 1476 R x/y
1477 1477
1478 1478 % hg diff --git --rev 2 --rev .
1479 1479 diff --git a/a b/a
1480 1480 --- a/a
1481 1481 +++ b/a
1482 1482 @@ -1,3 +1,2 @@
1483 1483 a
1484 1484 -m1
1485 1485 -m2
1486 1486 +7
1487 1487 diff --git a/x/y b/x/y
1488 1488 deleted file mode 100644
1489 1489 --- a/x/y
1490 1490 +++ /dev/null
1491 1491 @@ -1,1 +0,0 @@
1492 1492 -y1
1493 1493 diff --git a/x/x b/y/x
1494 1494 rename from x/x
1495 1495 rename to y/x
1496 1496 --- a/x/x
1497 1497 +++ b/y/x
1498 1498 @@ -1,1 +1,2 @@
1499 1499 x
1500 1500 +x1
1501 1501
1502 1502 # parent to branch:
1503 1503
1504 1504 % hg st -C --rev . --rev 2
1505 1505 M a
1506 1506 A x/x
1507 1507 y/x
1508 1508 A x/y
1509 1509 R y/x
1510 1510
1511 1511 % hg diff --git --rev . --rev 2
1512 1512 diff --git a/a b/a
1513 1513 --- a/a
1514 1514 +++ b/a
1515 1515 @@ -1,2 +1,3 @@
1516 1516 a
1517 1517 -7
1518 1518 +m1
1519 1519 +m2
1520 1520 diff --git a/y/x b/x/x
1521 1521 rename from y/x
1522 1522 rename to x/x
1523 1523 --- a/y/x
1524 1524 +++ b/x/x
1525 1525 @@ -1,2 +1,1 @@
1526 1526 x
1527 1527 -x1
1528 1528 diff --git a/x/y b/x/y
1529 1529 new file mode 100644
1530 1530 --- /dev/null
1531 1531 +++ b/x/y
1532 1532 @@ -0,0 +1,1 @@
1533 1533 +y1
1534 1534
1535 1535
1536 1536
1537 1537 Cannot implement unrelated branch with tb
1538 1538 testing copies with unrelated branch
1539 1539
1540 1540 $ hg init unrelated
1541 1541 $ cd unrelated
1542 1542 $ echo a >> a
1543 1543 $ hg ci -Am adda
1544 1544 adding a
1545 1545 $ hg mv a b
1546 1546 $ hg ci -m movea
1547 1547 $ hg up -C null
1548 1548 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1549 1549 $ echo a >> a
1550 1550 $ hg ci -Am addunrelateda
1551 1551 adding a
1552 1552 created new head
1553 1553
1554 1554 unrelated branch diff
1555 1555
1556 1556 $ hg diff --git -r 2 -r 1
1557 1557 diff --git a/a b/a
1558 1558 deleted file mode 100644
1559 1559 --- a/a
1560 1560 +++ /dev/null
1561 1561 @@ -1,1 +0,0 @@
1562 1562 -a
1563 1563 diff --git a/b b/b
1564 1564 new file mode 100644
1565 1565 --- /dev/null
1566 1566 +++ b/b
1567 1567 @@ -0,0 +1,1 @@
1568 1568 +a
1569 1569 $ cd ..
1570 1570
1571 1571
1572 1572 test for case where we didn't look sufficiently far back to find rename ancestor
1573 1573
1574 1574 $ hg init diffstop
1575 1575 $ cd diffstop
1576 1576 $ echo > f
1577 1577 $ hg ci -qAmf
1578 1578 $ hg mv f g
1579 1579 $ hg ci -m'f->g'
1580 1580 $ hg up -qr0
1581 1581 $ touch x
1582 1582 $ hg ci -qAmx
1583 1583 $ echo f > f
1584 1584 $ hg ci -qmf=f
1585 1585 $ hg merge -q
1586 1586 $ hg ci -mmerge
1587 1587 $ hg log -G --template '{rev} {desc}'
1588 1588 @ 4 merge
1589 1589 |\
1590 1590 | o 3 f=f
1591 1591 | |
1592 1592 | o 2 x
1593 1593 | |
1594 1594 o | 1 f->g
1595 1595 |/
1596 1596 o 0 f
1597 1597
1598 1598 $ hg diff --git -r 2
1599 1599 diff --git a/f b/g
1600 1600 rename from f
1601 1601 rename to g
1602 1602 --- a/f
1603 1603 +++ b/g
1604 1604 @@ -1,1 +1,1 @@
1605 1605 -
1606 1606 +f
1607 1607 $ cd ..
1608 1608
1609 1609 Additional tricky linkrev case
1610 1610 ------------------------------
1611 1611
1612 1612 If the first file revision after the diff base has a linkrev pointing to a
1613 1613 changeset on another branch with a revision lower that the diff base, we can
1614 1614 jump past the copy detection limit and fail to detect the rename.
1615 1615
1616 1616 $ hg init diffstoplinkrev
1617 1617 $ cd diffstoplinkrev
1618 1618
1619 1619 $ touch f
1620 1620 $ hg ci -Aqm 'empty f'
1621 1621
1622 1622 Make a simple change
1623 1623
1624 1624 $ echo change > f
1625 1625 $ hg ci -m 'change f'
1626 1626
1627 1627 Make a second branch, we use a named branch to create a simple commit
1628 1628 that does not touch f.
1629 1629
1630 1630 $ hg up -qr 'desc(empty)'
1631 1631 $ hg branch -q dev
1632 1632 $ hg ci -Aqm dev
1633 1633
1634 1634 Graft the initial change, as f was untouched, we reuse the same entry and the
1635 1635 linkrev point to the older branch.
1636 1636
1637 1637 $ hg graft -q 'desc(change)'
1638 1638
1639 1639 Make a rename because we want to track renames. It is also important that the
1640 1640 faulty linkrev is not the "start" commit to ensure the linkrev will be used.
1641 1641
1642 1642 $ hg mv f renamed
1643 1643 $ hg ci -m renamed
1644 1644
1645 1645 $ hg log -G -T '{rev} {desc}'
1646 1646 @ 4 renamed
1647 1647 |
1648 1648 o 3 change f
1649 1649 |
1650 1650 o 2 dev
1651 1651 |
1652 1652 | o 1 change f
1653 1653 |/
1654 1654 o 0 empty f
1655 1655
1656 1656
1657 1657 The copy tracking should still reach rev 2 (branch creation).
1658 1658 accessing the parent of 4 (renamed) should not jump use to revision 1.
1659 1659
1660 1660 $ hg diff --git -r 'desc(dev)' -r .
1661 1661 diff --git a/f b/renamed
1662 1662 rename from f
1663 1663 rename to renamed
1664 1664 --- a/f
1665 1665 +++ b/renamed
1666 1666 @@ -0,0 +1,1 @@
1667 1667 +change
1668 1668
1669 1669 Check debug output for copy tracing
1670 1670
1671 1671 $ hg status --copies --rev 'desc(dev)' --rev . --config devel.debug.copies=yes --debug
1672 1672 debug.copies: searching copies from a51f36ab1704 to 7935fd48a8f9
1673 1673 debug.copies: search mode: forward
1674 1674 debug.copies: looking into rename from a51f36ab1704 to 7935fd48a8f9
1675 1675 debug.copies: search limit: 2
1676 1676 debug.copies: missing file to search: 1
1677 1677 debug.copies: tracing file: renamed
1678 1678 debug.copies: rename of: f
1679 debug.copies: time: * seconds (glob)
1679 1680 A renamed
1680 1681 f
1681 1682 R f
1682 1683
1683 1684 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now