##// END OF EJS Templates
copies: drop the findlimit logic...
marmoute -
r43470:069cbbb5 default
parent child Browse files
Show More
@@ -1,1011 +1,923 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
17 17 from .revlogutils.flagutil import REVIDX_SIDEDATA
18 18
19 19 from . import (
20 20 error,
21 21 match as matchmod,
22 22 node,
23 23 pathutil,
24 24 pycompat,
25 25 util,
26 26 )
27 27
28 28 from .revlogutils import sidedata as sidedatamod
29 29
30 30 from .utils import stringutil
31 31
32 32
33 def _findlimit(repo, ctxa, ctxb):
34 """
35 Find the last revision that needs to be checked to ensure that a full
36 transitive closure for file copies can be properly calculated.
37 Generally, this means finding the earliest revision number that's an
38 ancestor of a or b but not both, except when a or b is a direct descendent
39 of the other, in which case we can return the minimum revnum of a and b.
40 """
41
42 # basic idea:
43 # - mark a and b with different sides
44 # - if a parent's children are all on the same side, the parent is
45 # on that side, otherwise it is on no side
46 # - walk the graph in topological order with the help of a heap;
47 # - add unseen parents to side map
48 # - clear side of any parent that has children on different sides
49 # - track number of interesting revs that might still be on a side
50 # - track the lowest interesting rev seen
51 # - quit when interesting revs is zero
52
53 cl = repo.changelog
54 wdirparents = None
55 a = ctxa.rev()
56 b = ctxb.rev()
57 if a is None:
58 wdirparents = (ctxa.p1(), ctxa.p2())
59 a = node.wdirrev
60 if b is None:
61 assert not wdirparents
62 wdirparents = (ctxb.p1(), ctxb.p2())
63 b = node.wdirrev
64
65 side = {a: -1, b: 1}
66 visit = [-a, -b]
67 heapq.heapify(visit)
68 interesting = len(visit)
69 limit = node.wdirrev
70
71 while interesting:
72 r = -(heapq.heappop(visit))
73 if r == node.wdirrev:
74 parents = [pctx.rev() for pctx in wdirparents]
75 else:
76 parents = cl.parentrevs(r)
77 if parents[1] == node.nullrev:
78 parents = parents[:1]
79 for p in parents:
80 if p not in side:
81 # first time we see p; add it to visit
82 side[p] = side[r]
83 if side[p]:
84 interesting += 1
85 heapq.heappush(visit, -p)
86 elif side[p] and side[p] != side[r]:
87 # p was interesting but now we know better
88 side[p] = 0
89 interesting -= 1
90 if side[r]:
91 limit = r # lowest rev visited
92 interesting -= 1
93
94 # Consider the following flow (see test-commit-amend.t under issue4405):
95 # 1/ File 'a0' committed
96 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
97 # 3/ Move back to first commit
98 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
99 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
100 #
101 # During the amend in step five, we will be in this state:
102 #
103 # @ 3 temporary amend commit for a1-amend
104 # |
105 # o 2 a1-amend
106 # |
107 # | o 1 a1
108 # |/
109 # o 0 a0
110 #
111 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
112 # yet the filelog has the copy information in rev 1 and we will not look
113 # back far enough unless we also look at the a and b as candidates.
114 # This only occurs when a is a descendent of b or visa-versa.
115 return min(limit, a, b)
116
117
118 33 def _filter(src, dst, t):
119 34 """filters out invalid copies after chaining"""
120 35
121 36 # When _chain()'ing copies in 'a' (from 'src' via some other commit 'mid')
122 37 # with copies in 'b' (from 'mid' to 'dst'), we can get the different cases
123 38 # in the following table (not including trivial cases). For example, case 2
124 39 # is where a file existed in 'src' and remained under that name in 'mid' and
125 40 # then was renamed between 'mid' and 'dst'.
126 41 #
127 42 # case src mid dst result
128 43 # 1 x y - -
129 44 # 2 x y y x->y
130 45 # 3 x y x -
131 46 # 4 x y z x->z
132 47 # 5 - x y -
133 48 # 6 x x y x->y
134 49 #
135 50 # _chain() takes care of chaining the copies in 'a' and 'b', but it
136 51 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
137 52 # between 5 and 6, so it includes all cases in its result.
138 53 # Cases 1, 3, and 5 are then removed by _filter().
139 54
140 55 for k, v in list(t.items()):
141 56 # remove copies from files that didn't exist
142 57 if v not in src:
143 58 del t[k]
144 59 # remove criss-crossed copies
145 60 elif k in src and v in dst:
146 61 del t[k]
147 62 # remove copies to files that were then removed
148 63 elif k not in dst:
149 64 del t[k]
150 65
151 66
152 67 def _chain(a, b):
153 68 """chain two sets of copies 'a' and 'b'"""
154 69 t = a.copy()
155 70 for k, v in pycompat.iteritems(b):
156 71 if v in t:
157 72 t[k] = t[v]
158 73 else:
159 74 t[k] = v
160 75 return t
161 76
162 77
163 def _tracefile(fctx, am, basemf, limit):
78 def _tracefile(fctx, am, basemf):
164 79 """return file context that is the ancestor of fctx present in ancestor
165 80 manifest am
166 81
167 82 Note: we used to try and stop after a given limit, however checking if that
168 83 limit is reached turned out to be very expensive. we are better off
169 84 disabling that feature."""
170 85
171 86 for f in fctx.ancestors():
172 87 path = f.path()
173 88 if am.get(path, None) == f.filenode():
174 89 return path
175 90 if basemf and basemf.get(path, None) == f.filenode():
176 91 return path
177 92
178 93
179 94 def _dirstatecopies(repo, match=None):
180 95 ds = repo.dirstate
181 96 c = ds.copies().copy()
182 97 for k in list(c):
183 98 if ds[k] not in b'anm' or (match and not match(k)):
184 99 del c[k]
185 100 return c
186 101
187 102
188 103 def _computeforwardmissing(a, b, match=None):
189 104 """Computes which files are in b but not a.
190 105 This is its own function so extensions can easily wrap this call to see what
191 106 files _forwardcopies is about to process.
192 107 """
193 108 ma = a.manifest()
194 109 mb = b.manifest()
195 110 return mb.filesnotin(ma, match=match)
196 111
197 112
198 113 def usechangesetcentricalgo(repo):
199 114 """Checks if we should use changeset-centric copy algorithms"""
200 115 if repo.filecopiesmode == b'changeset-sidedata':
201 116 return True
202 117 readfrom = repo.ui.config(b'experimental', b'copies.read-from')
203 118 changesetsource = (b'changeset-only', b'compatibility')
204 119 return readfrom in changesetsource
205 120
206 121
207 122 def _committedforwardcopies(a, b, base, match):
208 123 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
209 124 # files might have to be traced back to the fctx parent of the last
210 125 # one-side-only changeset, but not further back than that
211 126 repo = a._repo
212 127
213 128 if usechangesetcentricalgo(repo):
214 129 return _changesetforwardcopies(a, b, match)
215 130
216 131 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
217 132 dbg = repo.ui.debug
218 133 if debug:
219 134 dbg(b'debug.copies: looking into rename from %s to %s\n' % (a, b))
220 limit = _findlimit(repo, a, b)
221 if debug:
222 dbg(b'debug.copies: search limit: %d\n' % limit)
223 135 am = a.manifest()
224 136 basemf = None if base is None else base.manifest()
225 137
226 138 # find where new files came from
227 139 # we currently don't try to find where old files went, too expensive
228 140 # this means we can miss a case like 'hg rm b; hg cp a b'
229 141 cm = {}
230 142
231 143 # Computing the forward missing is quite expensive on large manifests, since
232 144 # it compares the entire manifests. We can optimize it in the common use
233 145 # case of computing what copies are in a commit versus its parent (like
234 146 # during a rebase or histedit). Note, we exclude merge commits from this
235 147 # optimization, since the ctx.files() for a merge commit is not correct for
236 148 # this comparison.
237 149 forwardmissingmatch = match
238 150 if b.p1() == a and b.p2().node() == node.nullid:
239 151 filesmatcher = matchmod.exact(b.files())
240 152 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
241 153 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
242 154
243 155 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
244 156
245 157 if debug:
246 158 dbg(b'debug.copies: missing files to search: %d\n' % len(missing))
247 159
248 160 for f in sorted(missing):
249 161 if debug:
250 162 dbg(b'debug.copies: tracing file: %s\n' % f)
251 163 fctx = b[f]
252 164 fctx._ancestrycontext = ancestrycontext
253 165
254 166 if debug:
255 167 start = util.timer()
256 opath = _tracefile(fctx, am, basemf, limit)
168 opath = _tracefile(fctx, am, basemf)
257 169 if opath:
258 170 if debug:
259 171 dbg(b'debug.copies: rename of: %s\n' % opath)
260 172 cm[f] = opath
261 173 if debug:
262 174 dbg(
263 175 b'debug.copies: time: %f seconds\n'
264 176 % (util.timer() - start)
265 177 )
266 178 return cm
267 179
268 180
269 181 def _changesetforwardcopies(a, b, match):
270 182 if a.rev() in (node.nullrev, b.rev()):
271 183 return {}
272 184
273 185 repo = a.repo()
274 186 children = {}
275 187 cl = repo.changelog
276 188 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
277 189 for r in missingrevs:
278 190 for p in cl.parentrevs(r):
279 191 if p == node.nullrev:
280 192 continue
281 193 if p not in children:
282 194 children[p] = [r]
283 195 else:
284 196 children[p].append(r)
285 197
286 198 roots = set(children) - set(missingrevs)
287 199 # 'work' contains 3-tuples of a (revision number, parent number, copies).
288 200 # The parent number is only used for knowing which parent the copies dict
289 201 # came from.
290 202 # NOTE: To reduce costly copying the 'copies' dicts, we reuse the same
291 203 # instance for *one* of the child nodes (the last one). Once an instance
292 204 # has been put on the queue, it is thus no longer safe to modify it.
293 205 # Conversely, it *is* safe to modify an instance popped off the queue.
294 206 work = [(r, 1, {}) for r in roots]
295 207 heapq.heapify(work)
296 208 alwaysmatch = match.always()
297 209 while work:
298 210 r, i1, copies = heapq.heappop(work)
299 211 if work and work[0][0] == r:
300 212 # We are tracing copies from both parents
301 213 r, i2, copies2 = heapq.heappop(work)
302 214 for dst, src in copies2.items():
303 215 # Unlike when copies are stored in the filelog, we consider
304 216 # it a copy even if the destination already existed on the
305 217 # other branch. It's simply too expensive to check if the
306 218 # file existed in the manifest.
307 219 if dst not in copies:
308 220 # If it was copied on the p1 side, leave it as copied from
309 221 # that side, even if it was also copied on the p2 side.
310 222 copies[dst] = copies2[dst]
311 223 if r == b.rev():
312 224 return copies
313 225 for i, c in enumerate(children[r]):
314 226 childctx = repo[c]
315 227 if r == childctx.p1().rev():
316 228 parent = 1
317 229 childcopies = childctx.p1copies()
318 230 else:
319 231 assert r == childctx.p2().rev()
320 232 parent = 2
321 233 childcopies = childctx.p2copies()
322 234 if not alwaysmatch:
323 235 childcopies = {
324 236 dst: src for dst, src in childcopies.items() if match(dst)
325 237 }
326 238 # Copy the dict only if later iterations will also need it
327 239 if i != len(children[r]) - 1:
328 240 newcopies = copies.copy()
329 241 else:
330 242 newcopies = copies
331 243 if childcopies:
332 244 newcopies = _chain(newcopies, childcopies)
333 245 for f in childctx.filesremoved():
334 246 if f in newcopies:
335 247 del newcopies[f]
336 248 heapq.heappush(work, (c, parent, newcopies))
337 249 assert False
338 250
339 251
340 252 def _forwardcopies(a, b, base=None, match=None):
341 253 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
342 254
343 255 if base is None:
344 256 base = a
345 257 match = a.repo().narrowmatch(match)
346 258 # check for working copy
347 259 if b.rev() is None:
348 260 cm = _committedforwardcopies(a, b.p1(), base, match)
349 261 # combine copies from dirstate if necessary
350 262 copies = _chain(cm, _dirstatecopies(b._repo, match))
351 263 else:
352 264 copies = _committedforwardcopies(a, b, base, match)
353 265 return copies
354 266
355 267
356 268 def _backwardrenames(a, b, match):
357 269 if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
358 270 return {}
359 271
360 272 # Even though we're not taking copies into account, 1:n rename situations
361 273 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
362 274 # arbitrarily pick one of the renames.
363 275 # We don't want to pass in "match" here, since that would filter
364 276 # the destination by it. Since we're reversing the copies, we want
365 277 # to filter the source instead.
366 278 f = _forwardcopies(b, a)
367 279 r = {}
368 280 for k, v in sorted(pycompat.iteritems(f)):
369 281 if match and not match(v):
370 282 continue
371 283 # remove copies
372 284 if v in a:
373 285 continue
374 286 r[v] = k
375 287 return r
376 288
377 289
378 290 def pathcopies(x, y, match=None):
379 291 """find {dst@y: src@x} copy mapping for directed compare"""
380 292 repo = x._repo
381 293 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
382 294 if debug:
383 295 repo.ui.debug(
384 296 b'debug.copies: searching copies from %s to %s\n' % (x, y)
385 297 )
386 298 if x == y or not x or not y:
387 299 return {}
388 300 a = y.ancestor(x)
389 301 if a == x:
390 302 if debug:
391 303 repo.ui.debug(b'debug.copies: search mode: forward\n')
392 304 if y.rev() is None and x == y.p1():
393 305 # short-circuit to avoid issues with merge states
394 306 return _dirstatecopies(repo, match)
395 307 copies = _forwardcopies(x, y, match=match)
396 308 elif a == y:
397 309 if debug:
398 310 repo.ui.debug(b'debug.copies: search mode: backward\n')
399 311 copies = _backwardrenames(x, y, match=match)
400 312 else:
401 313 if debug:
402 314 repo.ui.debug(b'debug.copies: search mode: combined\n')
403 315 base = None
404 316 if a.rev() != node.nullrev:
405 317 base = x
406 318 copies = _chain(
407 319 _backwardrenames(x, a, match=match),
408 320 _forwardcopies(a, y, base, match=match),
409 321 )
410 322 _filter(x, y, copies)
411 323 return copies
412 324
413 325
414 326 def mergecopies(repo, c1, c2, base):
415 327 """
416 328 Finds moves and copies between context c1 and c2 that are relevant for
417 329 merging. 'base' will be used as the merge base.
418 330
419 331 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
420 332 files that were moved/ copied in one merge parent and modified in another.
421 333 For example:
422 334
423 335 o ---> 4 another commit
424 336 |
425 337 | o ---> 3 commit that modifies a.txt
426 338 | /
427 339 o / ---> 2 commit that moves a.txt to b.txt
428 340 |/
429 341 o ---> 1 merge base
430 342
431 343 If we try to rebase revision 3 on revision 4, since there is no a.txt in
432 344 revision 4, and if user have copytrace disabled, we prints the following
433 345 message:
434 346
435 347 ```other changed <file> which local deleted```
436 348
437 349 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
438 350 "dirmove".
439 351
440 352 "copy" is a mapping from destination name -> source name,
441 353 where source is in c1 and destination is in c2 or vice-versa.
442 354
443 355 "movewithdir" is a mapping from source name -> destination name,
444 356 where the file at source present in one context but not the other
445 357 needs to be moved to destination by the merge process, because the
446 358 other context moved the directory it is in.
447 359
448 360 "diverge" is a mapping of source name -> list of destination names
449 361 for divergent renames.
450 362
451 363 "renamedelete" is a mapping of source name -> list of destination
452 364 names for files deleted in c1 that were renamed in c2 or vice-versa.
453 365
454 366 "dirmove" is a mapping of detected source dir -> destination dir renames.
455 367 This is needed for handling changes to new files previously grafted into
456 368 renamed directories.
457 369
458 370 This function calls different copytracing algorithms based on config.
459 371 """
460 372 # avoid silly behavior for update from empty dir
461 373 if not c1 or not c2 or c1 == c2:
462 374 return {}, {}, {}, {}, {}
463 375
464 376 narrowmatch = c1.repo().narrowmatch()
465 377
466 378 # avoid silly behavior for parent -> working dir
467 379 if c2.node() is None and c1.node() == repo.dirstate.p1():
468 380 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
469 381
470 382 copytracing = repo.ui.config(b'experimental', b'copytrace')
471 383 if stringutil.parsebool(copytracing) is False:
472 384 # stringutil.parsebool() returns None when it is unable to parse the
473 385 # value, so we should rely on making sure copytracing is on such cases
474 386 return {}, {}, {}, {}, {}
475 387
476 388 if usechangesetcentricalgo(repo):
477 389 # The heuristics don't make sense when we need changeset-centric algos
478 390 return _fullcopytracing(repo, c1, c2, base)
479 391
480 392 # Copy trace disabling is explicitly below the node == p1 logic above
481 393 # because the logic above is required for a simple copy to be kept across a
482 394 # rebase.
483 395 if copytracing == b'heuristics':
484 396 # Do full copytracing if only non-public revisions are involved as
485 397 # that will be fast enough and will also cover the copies which could
486 398 # be missed by heuristics
487 399 if _isfullcopytraceable(repo, c1, base):
488 400 return _fullcopytracing(repo, c1, c2, base)
489 401 return _heuristicscopytracing(repo, c1, c2, base)
490 402 else:
491 403 return _fullcopytracing(repo, c1, c2, base)
492 404
493 405
494 406 def _isfullcopytraceable(repo, c1, base):
495 407 """ Checks that if base, source and destination are all no-public branches,
496 408 if yes let's use the full copytrace algorithm for increased capabilities
497 409 since it will be fast enough.
498 410
499 411 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
500 412 number of changesets from c1 to base such that if number of changesets are
501 413 more than the limit, full copytracing algorithm won't be used.
502 414 """
503 415 if c1.rev() is None:
504 416 c1 = c1.p1()
505 417 if c1.mutable() and base.mutable():
506 418 sourcecommitlimit = repo.ui.configint(
507 419 b'experimental', b'copytrace.sourcecommitlimit'
508 420 )
509 421 commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
510 422 return commits < sourcecommitlimit
511 423 return False
512 424
513 425
514 426 def _checksinglesidecopies(
515 427 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
516 428 ):
517 429 if src not in m2:
518 430 # deleted on side 2
519 431 if src not in m1:
520 432 # renamed on side 1, deleted on side 2
521 433 renamedelete[src] = dsts1
522 434 elif m2[src] != mb[src]:
523 435 if not _related(c2[src], base[src]):
524 436 return
525 437 # modified on side 2
526 438 for dst in dsts1:
527 439 if dst not in m2:
528 440 # dst not added on side 2 (handle as regular
529 441 # "both created" case in manifestmerge otherwise)
530 442 copy[dst] = src
531 443
532 444
533 445 def _fullcopytracing(repo, c1, c2, base):
534 446 """ The full copytracing algorithm which finds all the new files that were
535 447 added from merge base up to the top commit and for each file it checks if
536 448 this file was copied from another file.
537 449
538 450 This is pretty slow when a lot of changesets are involved but will track all
539 451 the copies.
540 452 """
541 453 m1 = c1.manifest()
542 454 m2 = c2.manifest()
543 455 mb = base.manifest()
544 456
545 457 copies1 = pathcopies(base, c1)
546 458 copies2 = pathcopies(base, c2)
547 459
548 460 inversecopies1 = {}
549 461 inversecopies2 = {}
550 462 for dst, src in copies1.items():
551 463 inversecopies1.setdefault(src, []).append(dst)
552 464 for dst, src in copies2.items():
553 465 inversecopies2.setdefault(src, []).append(dst)
554 466
555 467 copy = {}
556 468 diverge = {}
557 469 renamedelete = {}
558 470 allsources = set(inversecopies1) | set(inversecopies2)
559 471 for src in allsources:
560 472 dsts1 = inversecopies1.get(src)
561 473 dsts2 = inversecopies2.get(src)
562 474 if dsts1 and dsts2:
563 475 # copied/renamed on both sides
564 476 if src not in m1 and src not in m2:
565 477 # renamed on both sides
566 478 dsts1 = set(dsts1)
567 479 dsts2 = set(dsts2)
568 480 # If there's some overlap in the rename destinations, we
569 481 # consider it not divergent. For example, if side 1 copies 'a'
570 482 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
571 483 # and 'd' and deletes 'a'.
572 484 if dsts1 & dsts2:
573 485 for dst in dsts1 & dsts2:
574 486 copy[dst] = src
575 487 else:
576 488 diverge[src] = sorted(dsts1 | dsts2)
577 489 elif src in m1 and src in m2:
578 490 # copied on both sides
579 491 dsts1 = set(dsts1)
580 492 dsts2 = set(dsts2)
581 493 for dst in dsts1 & dsts2:
582 494 copy[dst] = src
583 495 # TODO: Handle cases where it was renamed on one side and copied
584 496 # on the other side
585 497 elif dsts1:
586 498 # copied/renamed only on side 1
587 499 _checksinglesidecopies(
588 500 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
589 501 )
590 502 elif dsts2:
591 503 # copied/renamed only on side 2
592 504 _checksinglesidecopies(
593 505 src, dsts2, m2, m1, mb, c1, base, copy, renamedelete
594 506 )
595 507
596 508 renamedeleteset = set()
597 509 divergeset = set()
598 510 for dsts in diverge.values():
599 511 divergeset.update(dsts)
600 512 for dsts in renamedelete.values():
601 513 renamedeleteset.update(dsts)
602 514
603 515 # find interesting file sets from manifests
604 516 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
605 517 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
606 518 u1 = sorted(addedinm1 - addedinm2)
607 519 u2 = sorted(addedinm2 - addedinm1)
608 520
609 521 header = b" unmatched files in %s"
610 522 if u1:
611 523 repo.ui.debug(b"%s:\n %s\n" % (header % b'local', b"\n ".join(u1)))
612 524 if u2:
613 525 repo.ui.debug(b"%s:\n %s\n" % (header % b'other', b"\n ".join(u2)))
614 526
615 527 fullcopy = copies1.copy()
616 528 fullcopy.update(copies2)
617 529 if not fullcopy:
618 530 return copy, {}, diverge, renamedelete, {}
619 531
620 532 if repo.ui.debugflag:
621 533 repo.ui.debug(
622 534 b" all copies found (* = to merge, ! = divergent, "
623 535 b"% = renamed and deleted):\n"
624 536 )
625 537 for f in sorted(fullcopy):
626 538 note = b""
627 539 if f in copy:
628 540 note += b"*"
629 541 if f in divergeset:
630 542 note += b"!"
631 543 if f in renamedeleteset:
632 544 note += b"%"
633 545 repo.ui.debug(
634 546 b" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f, note)
635 547 )
636 548 del divergeset
637 549
638 550 repo.ui.debug(b" checking for directory renames\n")
639 551
640 552 # generate a directory move map
641 553 d1, d2 = c1.dirs(), c2.dirs()
642 554 invalid = set()
643 555 dirmove = {}
644 556
645 557 # examine each file copy for a potential directory move, which is
646 558 # when all the files in a directory are moved to a new directory
647 559 for dst, src in pycompat.iteritems(fullcopy):
648 560 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
649 561 if dsrc in invalid:
650 562 # already seen to be uninteresting
651 563 continue
652 564 elif dsrc in d1 and ddst in d1:
653 565 # directory wasn't entirely moved locally
654 566 invalid.add(dsrc)
655 567 elif dsrc in d2 and ddst in d2:
656 568 # directory wasn't entirely moved remotely
657 569 invalid.add(dsrc)
658 570 elif dsrc in dirmove and dirmove[dsrc] != ddst:
659 571 # files from the same directory moved to two different places
660 572 invalid.add(dsrc)
661 573 else:
662 574 # looks good so far
663 575 dirmove[dsrc] = ddst
664 576
665 577 for i in invalid:
666 578 if i in dirmove:
667 579 del dirmove[i]
668 580 del d1, d2, invalid
669 581
670 582 if not dirmove:
671 583 return copy, {}, diverge, renamedelete, {}
672 584
673 585 dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
674 586
675 587 for d in dirmove:
676 588 repo.ui.debug(
677 589 b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
678 590 )
679 591
680 592 movewithdir = {}
681 593 # check unaccounted nonoverlapping files against directory moves
682 594 for f in u1 + u2:
683 595 if f not in fullcopy:
684 596 for d in dirmove:
685 597 if f.startswith(d):
686 598 # new file added in a directory that was moved, move it
687 599 df = dirmove[d] + f[len(d) :]
688 600 if df not in copy:
689 601 movewithdir[f] = df
690 602 repo.ui.debug(
691 603 b" pending file src: '%s' -> dst: '%s'\n"
692 604 % (f, df)
693 605 )
694 606 break
695 607
696 608 return copy, movewithdir, diverge, renamedelete, dirmove
697 609
698 610
699 611 def _heuristicscopytracing(repo, c1, c2, base):
700 612 """ Fast copytracing using filename heuristics
701 613
702 614 Assumes that moves or renames are of following two types:
703 615
704 616 1) Inside a directory only (same directory name but different filenames)
705 617 2) Move from one directory to another
706 618 (same filenames but different directory names)
707 619
708 620 Works only when there are no merge commits in the "source branch".
709 621 Source branch is commits from base up to c2 not including base.
710 622
711 623 If merge is involved it fallbacks to _fullcopytracing().
712 624
713 625 Can be used by setting the following config:
714 626
715 627 [experimental]
716 628 copytrace = heuristics
717 629
718 630 In some cases the copy/move candidates found by heuristics can be very large
719 631 in number and that will make the algorithm slow. The number of possible
720 632 candidates to check can be limited by using the config
721 633 `experimental.copytrace.movecandidateslimit` which defaults to 100.
722 634 """
723 635
724 636 if c1.rev() is None:
725 637 c1 = c1.p1()
726 638 if c2.rev() is None:
727 639 c2 = c2.p1()
728 640
729 641 copies = {}
730 642
731 643 changedfiles = set()
732 644 m1 = c1.manifest()
733 645 if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
734 646 # If base is not in c2 branch, we switch to fullcopytracing
735 647 repo.ui.debug(
736 648 b"switching to full copytracing as base is not "
737 649 b"an ancestor of c2\n"
738 650 )
739 651 return _fullcopytracing(repo, c1, c2, base)
740 652
741 653 ctx = c2
742 654 while ctx != base:
743 655 if len(ctx.parents()) == 2:
744 656 # To keep things simple let's not handle merges
745 657 repo.ui.debug(b"switching to full copytracing because of merges\n")
746 658 return _fullcopytracing(repo, c1, c2, base)
747 659 changedfiles.update(ctx.files())
748 660 ctx = ctx.p1()
749 661
750 662 cp = _forwardcopies(base, c2)
751 663 for dst, src in pycompat.iteritems(cp):
752 664 if src in m1:
753 665 copies[dst] = src
754 666
755 667 # file is missing if it isn't present in the destination, but is present in
756 668 # the base and present in the source.
757 669 # Presence in the base is important to exclude added files, presence in the
758 670 # source is important to exclude removed files.
759 671 filt = lambda f: f not in m1 and f in base and f in c2
760 672 missingfiles = [f for f in changedfiles if filt(f)]
761 673
762 674 if missingfiles:
763 675 basenametofilename = collections.defaultdict(list)
764 676 dirnametofilename = collections.defaultdict(list)
765 677
766 678 for f in m1.filesnotin(base.manifest()):
767 679 basename = os.path.basename(f)
768 680 dirname = os.path.dirname(f)
769 681 basenametofilename[basename].append(f)
770 682 dirnametofilename[dirname].append(f)
771 683
772 684 for f in missingfiles:
773 685 basename = os.path.basename(f)
774 686 dirname = os.path.dirname(f)
775 687 samebasename = basenametofilename[basename]
776 688 samedirname = dirnametofilename[dirname]
777 689 movecandidates = samebasename + samedirname
778 690 # f is guaranteed to be present in c2, that's why
779 691 # c2.filectx(f) won't fail
780 692 f2 = c2.filectx(f)
781 693 # we can have a lot of candidates which can slow down the heuristics
782 694 # config value to limit the number of candidates moves to check
783 695 maxcandidates = repo.ui.configint(
784 696 b'experimental', b'copytrace.movecandidateslimit'
785 697 )
786 698
787 699 if len(movecandidates) > maxcandidates:
788 700 repo.ui.status(
789 701 _(
790 702 b"skipping copytracing for '%s', more "
791 703 b"candidates than the limit: %d\n"
792 704 )
793 705 % (f, len(movecandidates))
794 706 )
795 707 continue
796 708
797 709 for candidate in movecandidates:
798 710 f1 = c1.filectx(candidate)
799 711 if _related(f1, f2):
800 712 # if there are a few related copies then we'll merge
801 713 # changes into all of them. This matches the behaviour
802 714 # of upstream copytracing
803 715 copies[candidate] = f
804 716
805 717 return copies, {}, {}, {}, {}
806 718
807 719
808 720 def _related(f1, f2):
809 721 """return True if f1 and f2 filectx have a common ancestor
810 722
811 723 Walk back to common ancestor to see if the two files originate
812 724 from the same file. Since workingfilectx's rev() is None it messes
813 725 up the integer comparison logic, hence the pre-step check for
814 726 None (f1 and f2 can only be workingfilectx's initially).
815 727 """
816 728
817 729 if f1 == f2:
818 730 return True # a match
819 731
820 732 g1, g2 = f1.ancestors(), f2.ancestors()
821 733 try:
822 734 f1r, f2r = f1.linkrev(), f2.linkrev()
823 735
824 736 if f1r is None:
825 737 f1 = next(g1)
826 738 if f2r is None:
827 739 f2 = next(g2)
828 740
829 741 while True:
830 742 f1r, f2r = f1.linkrev(), f2.linkrev()
831 743 if f1r > f2r:
832 744 f1 = next(g1)
833 745 elif f2r > f1r:
834 746 f2 = next(g2)
835 747 else: # f1 and f2 point to files in the same linkrev
836 748 return f1 == f2 # true if they point to the same file
837 749 except StopIteration:
838 750 return False
839 751
840 752
841 753 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
842 754 """reproduce copies from fromrev to rev in the dirstate
843 755
844 756 If skiprev is specified, it's a revision that should be used to
845 757 filter copy records. Any copies that occur between fromrev and
846 758 skiprev will not be duplicated, even if they appear in the set of
847 759 copies between fromrev and rev.
848 760 """
849 761 exclude = {}
850 762 ctraceconfig = repo.ui.config(b'experimental', b'copytrace')
851 763 bctrace = stringutil.parsebool(ctraceconfig)
852 764 if skiprev is not None and (
853 765 ctraceconfig == b'heuristics' or bctrace or bctrace is None
854 766 ):
855 767 # copytrace='off' skips this line, but not the entire function because
856 768 # the line below is O(size of the repo) during a rebase, while the rest
857 769 # of the function is much faster (and is required for carrying copy
858 770 # metadata across the rebase anyway).
859 771 exclude = pathcopies(repo[fromrev], repo[skiprev])
860 772 for dst, src in pycompat.iteritems(pathcopies(repo[fromrev], repo[rev])):
861 773 if dst in exclude:
862 774 continue
863 775 if dst in wctx:
864 776 wctx[dst].markcopied(src)
865 777
866 778
867 779 def computechangesetfilesadded(ctx):
868 780 """return the list of files added in a changeset
869 781 """
870 782 added = []
871 783 for f in ctx.files():
872 784 if not any(f in p for p in ctx.parents()):
873 785 added.append(f)
874 786 return added
875 787
876 788
877 789 def computechangesetfilesremoved(ctx):
878 790 """return the list of files removed in a changeset
879 791 """
880 792 removed = []
881 793 for f in ctx.files():
882 794 if f not in ctx:
883 795 removed.append(f)
884 796 return removed
885 797
886 798
887 799 def computechangesetcopies(ctx):
888 800 """return the copies data for a changeset
889 801
890 802 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
891 803
892 804 Each dictionnary are in the form: `{newname: oldname}`
893 805 """
894 806 p1copies = {}
895 807 p2copies = {}
896 808 p1 = ctx.p1()
897 809 p2 = ctx.p2()
898 810 narrowmatch = ctx._repo.narrowmatch()
899 811 for dst in ctx.files():
900 812 if not narrowmatch(dst) or dst not in ctx:
901 813 continue
902 814 copied = ctx[dst].renamed()
903 815 if not copied:
904 816 continue
905 817 src, srcnode = copied
906 818 if src in p1 and p1[src].filenode() == srcnode:
907 819 p1copies[dst] = src
908 820 elif src in p2 and p2[src].filenode() == srcnode:
909 821 p2copies[dst] = src
910 822 return p1copies, p2copies
911 823
912 824
913 825 def encodecopies(files, copies):
914 826 items = []
915 827 for i, dst in enumerate(files):
916 828 if dst in copies:
917 829 items.append(b'%d\0%s' % (i, copies[dst]))
918 830 if len(items) != len(copies):
919 831 raise error.ProgrammingError(
920 832 b'some copy targets missing from file list'
921 833 )
922 834 return b"\n".join(items)
923 835
924 836
925 837 def decodecopies(files, data):
926 838 try:
927 839 copies = {}
928 840 if not data:
929 841 return copies
930 842 for l in data.split(b'\n'):
931 843 strindex, src = l.split(b'\0')
932 844 i = int(strindex)
933 845 dst = files[i]
934 846 copies[dst] = src
935 847 return copies
936 848 except (ValueError, IndexError):
937 849 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
938 850 # used different syntax for the value.
939 851 return None
940 852
941 853
942 854 def encodefileindices(files, subset):
943 855 subset = set(subset)
944 856 indices = []
945 857 for i, f in enumerate(files):
946 858 if f in subset:
947 859 indices.append(b'%d' % i)
948 860 return b'\n'.join(indices)
949 861
950 862
951 863 def decodefileindices(files, data):
952 864 try:
953 865 subset = []
954 866 if not data:
955 867 return subset
956 868 for strindex in data.split(b'\n'):
957 869 i = int(strindex)
958 870 if i < 0 or i >= len(files):
959 871 return None
960 872 subset.append(files[i])
961 873 return subset
962 874 except (ValueError, IndexError):
963 875 # Perhaps someone had chosen the same key name (e.g. "added") and
964 876 # used different syntax for the value.
965 877 return None
966 878
967 879
968 880 def _getsidedata(srcrepo, rev):
969 881 ctx = srcrepo[rev]
970 882 filescopies = computechangesetcopies(ctx)
971 883 filesadded = computechangesetfilesadded(ctx)
972 884 filesremoved = computechangesetfilesremoved(ctx)
973 885 sidedata = {}
974 886 if any([filescopies, filesadded, filesremoved]):
975 887 sortedfiles = sorted(ctx.files())
976 888 p1copies, p2copies = filescopies
977 889 p1copies = encodecopies(sortedfiles, p1copies)
978 890 p2copies = encodecopies(sortedfiles, p2copies)
979 891 filesadded = encodefileindices(sortedfiles, filesadded)
980 892 filesremoved = encodefileindices(sortedfiles, filesremoved)
981 893 sidedata[sidedatamod.SD_P1COPIES] = p1copies
982 894 sidedata[sidedatamod.SD_P2COPIES] = p2copies
983 895 sidedata[sidedatamod.SD_FILESADDED] = filesadded
984 896 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
985 897 return sidedata
986 898
987 899
988 900 def getsidedataadder(srcrepo, destrepo):
989 901 def sidedatacompanion(revlog, rev):
990 902 sidedata = {}
991 903 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
992 904 sidedata = _getsidedata(srcrepo, rev)
993 905 return False, (), sidedata
994 906
995 907 return sidedatacompanion
996 908
997 909
998 910 def getsidedataremover(srcrepo, destrepo):
999 911 def sidedatacompanion(revlog, rev):
1000 912 f = ()
1001 913 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
1002 914 if revlog.flags(rev) & REVIDX_SIDEDATA:
1003 915 f = (
1004 916 sidedatamod.SD_P1COPIES,
1005 917 sidedatamod.SD_P2COPIES,
1006 918 sidedatamod.SD_FILESADDED,
1007 919 sidedatamod.SD_FILESREMOVED,
1008 920 )
1009 921 return False, f, {}
1010 922
1011 923 return sidedatacompanion
@@ -1,1695 +1,1694 b''
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 rename because we want to track renames. It is also important that the
1628 1628 faulty linkrev is not only the "start" commit to ensure the linkrev will be
1629 1629 used.
1630 1630
1631 1631 $ hg mv f renamed
1632 1632 $ hg ci -m renamed
1633 1633
1634 1634 Make a second branch, we use a named branch to create a simple commit
1635 1635 that does not touch f.
1636 1636
1637 1637 $ hg up -qr 'desc(empty)'
1638 1638 $ hg branch -q dev
1639 1639 $ hg ci -Aqm dev
1640 1640
1641 1641 Graft the initial change and the rename. As f was untouched, we reuse the same
1642 1642 entry and the linkrev point to the older branch.
1643 1643
1644 1644 $ hg graft -q 'desc(change)'
1645 1645 $ hg graft -q 'desc(renamed)'
1646 1646
1647 1647 $ hg log -G -T '{rev} {desc}'
1648 1648 @ 5 renamed
1649 1649 |
1650 1650 o 4 change f
1651 1651 |
1652 1652 o 3 dev
1653 1653 |
1654 1654 | o 2 renamed
1655 1655 | |
1656 1656 | o 1 change f
1657 1657 |/
1658 1658 o 0 empty f
1659 1659
1660 1660
1661 1661 The copy tracking should still reach rev 3 (branch creation).
1662 1662 accessing the parent of 5 (renamed) should not jump use to revision 1.
1663 1663
1664 1664 $ hg diff --git -r 'desc(dev)' -r .
1665 1665 diff --git a/f b/renamed
1666 1666 rename from f
1667 1667 rename to renamed
1668 1668 --- a/f
1669 1669 +++ b/renamed
1670 1670 @@ -0,0 +1,1 @@
1671 1671 +change
1672 1672
1673 1673 Check debug output for copy tracing
1674 1674
1675 1675 $ hg status --copies --rev 'desc(dev)' --rev . --config devel.debug.copies=yes --debug
1676 1676 debug.copies: searching copies from a51f36ab1704 to 1f4aa1fd627b
1677 1677 debug.copies: search mode: forward
1678 1678 debug.copies: looking into rename from a51f36ab1704 to 1f4aa1fd627b
1679 debug.copies: search limit: 3
1680 1679 debug.copies: missing files to search: 1
1681 1680 debug.copies: tracing file: renamed
1682 1681 debug.copies: rename of: f
1683 1682 debug.copies: time: * seconds (glob)
1684 1683 A renamed
1685 1684 f
1686 1685 R f
1687 1686
1688 1687 Check that merging across the rename works
1689 1688
1690 1689 $ echo modified >> renamed
1691 1690 $ hg co -m 4
1692 1691 merging renamed and f to f
1693 1692 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1694 1693
1695 1694 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now