comparison mercurial/cmdutil.py @ 44367:9dab3fa64325

copy: add experimental support for marking committed copies The simplest way I'm aware of to mark a file as copied/moved after committing is this: hg uncommit --keep <src> <dest> # <src> needed for move, but not copy hg mv --after <src> <dest> hg amend This patch teaches `hg copy` a `--at-rev` argument to simplify that into: hg copy --after --at-rev . <src> <dest> In addition to being simpler, it doesn't touch the working copy, so it can easily be used even if the destination file has been modified in the working copy. Differential Revision: https://phab.mercurial-scm.org/D8035
author Martin von Zweigbergk <martinvonz@google.com>
date Fri, 20 Dec 2019 13:24:46 -0800
parents d8b49bf6cfec
children f5c006621f07
comparison
equal deleted inserted replaced
44366:d8b49bf6cfec 44367:9dab3fa64325
1419 cwd = repo.getcwd() 1419 cwd = repo.getcwd()
1420 targets = {} 1420 targets = {}
1421 forget = opts.get(b"forget") 1421 forget = opts.get(b"forget")
1422 after = opts.get(b"after") 1422 after = opts.get(b"after")
1423 dryrun = opts.get(b"dry_run") 1423 dryrun = opts.get(b"dry_run")
1424 ctx = repo[None] 1424 rev = opts.get(b'at_rev')
1425 if rev:
1426 if not forget and not after:
1427 # TODO: Remove this restriction and make it also create the copy
1428 # targets (and remove the rename source if rename==True).
1429 raise error.Abort(_(b'--at-rev requires --after'))
1430 ctx = scmutil.revsingle(repo, rev)
1431 if len(ctx.parents()) > 1:
1432 raise error.Abort(_(b'cannot mark/unmark copy in merge commit'))
1433 else:
1434 ctx = repo[None]
1435
1425 pctx = ctx.p1() 1436 pctx = ctx.p1()
1426 1437
1427 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) 1438 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1428 1439
1429 if forget: 1440 if forget:
1430 rev = opts[b'at_rev']
1431 if rev:
1432 ctx = scmutil.revsingle(repo, rev)
1433 else:
1434 ctx = repo[None]
1435 if ctx.rev() is None: 1441 if ctx.rev() is None:
1436 new_ctx = ctx 1442 new_ctx = ctx
1437 else: 1443 else:
1438 if len(ctx.parents()) > 1: 1444 if len(ctx.parents()) > 1:
1439 raise error.Abort(_(b'cannot unmark copy in merge commit')) 1445 raise error.Abort(_(b'cannot unmark copy in merge commit'))
1481 if not pats: 1487 if not pats:
1482 raise error.Abort(_(b'no source or destination specified')) 1488 raise error.Abort(_(b'no source or destination specified'))
1483 if len(pats) == 1: 1489 if len(pats) == 1:
1484 raise error.Abort(_(b'no destination specified')) 1490 raise error.Abort(_(b'no destination specified'))
1485 dest = pats.pop() 1491 dest = pats.pop()
1486
1487 if opts.get(b'at_rev'):
1488 raise error.Abort(_("--at-rev is only supported with --forget"))
1489 1492
1490 def walkpat(pat): 1493 def walkpat(pat):
1491 srcs = [] 1494 srcs = []
1492 m = scmutil.match(ctx, [pat], opts, globbed=True) 1495 m = scmutil.match(ctx, [pat], opts, globbed=True)
1493 for abs in ctx.walk(m): 1496 for abs in ctx.walk(m):
1514 1517
1515 # abs: hgsep 1518 # abs: hgsep
1516 # rel: ossep 1519 # rel: ossep
1517 srcs.append((abs, rel, exact)) 1520 srcs.append((abs, rel, exact))
1518 return srcs 1521 return srcs
1522
1523 if ctx.rev() is not None:
1524 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1525 absdest = pathutil.canonpath(repo.root, cwd, dest)
1526 if ctx.hasdir(absdest):
1527 raise error.Abort(
1528 _(b'%s: --at-rev does not support a directory as destination')
1529 % uipathfn(absdest)
1530 )
1531 if absdest not in ctx:
1532 raise error.Abort(
1533 _(b'%s: copy destination does not exist in %s')
1534 % (uipathfn(absdest), ctx)
1535 )
1536
1537 # avoid cycle context -> subrepo -> cmdutil
1538 from . import context
1539
1540 copylist = []
1541 for pat in pats:
1542 srcs = walkpat(pat)
1543 if not srcs:
1544 continue
1545 for abs, rel, exact in srcs:
1546 copylist.append(abs)
1547
1548 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1549 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1550 # existing functions below.
1551 if len(copylist) != 1:
1552 raise error.Abort(_(b'--at-rev requires a single source'))
1553
1554 new_ctx = context.overlayworkingctx(repo)
1555 new_ctx.setbase(ctx.p1())
1556 mergemod.graft(repo, ctx, wctx=new_ctx)
1557
1558 new_ctx.markcopied(absdest, copylist[0])
1559
1560 with repo.lock():
1561 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1562 new_node = mem_ctx.commit()
1563
1564 if repo.dirstate.p1() == ctx.node():
1565 with repo.dirstate.parentchange():
1566 scmutil.movedirstate(repo, repo[new_node])
1567 replacements = {ctx.node(): [new_node]}
1568 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1569
1570 return
1519 1571
1520 # abssrc: hgsep 1572 # abssrc: hgsep
1521 # relsrc: ossep 1573 # relsrc: ossep
1522 # otarget: ossep 1574 # otarget: ossep
1523 def copyfile(abssrc, relsrc, otarget, exact): 1575 def copyfile(abssrc, relsrc, otarget, exact):