diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -1410,12 +1410,15 @@ def copy(ui, repo, pats, opts, rename=False): + check_incompatible_arguments(opts, b'forget', [b'dry_run']) + # called with the repo lock held # # hgsep => pathname that uses "/" to separate directories # ossep => pathname that uses os.sep to separate directories cwd = repo.getcwd() targets = {} + forget = opts.get(b"forget") after = opts.get(b"after") dryrun = opts.get(b"dry_run") wctx = repo[None] @@ -1423,6 +1426,24 @@ uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) + if forget: + match = scmutil.match(wctx, pats, opts) + + current_copies = wctx.p1copies() + current_copies.update(wctx.p2copies()) + + for f in wctx.walk(match): + if f in current_copies: + wctx[f].markcopied(None) + elif match.exact(f): + ui.warn( + _( + b'%s: not unmarking as copy - file is not marked as copied\n' + ) + % uipathfn(f) + ) + return + def walkpat(pat): srcs = [] m = scmutil.match(wctx, [pat], opts, globbed=True) diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -2309,6 +2309,7 @@ @command( b'copy|cp', [ + (b'', b'forget', None, _(b'unmark a file as copied')), (b'A', b'after', None, _(b'record a copy that has already occurred')), ( b'f', @@ -2333,8 +2334,11 @@ exist in the working directory. If invoked with -A/--after, the operation is recorded, but no copying is performed. - This command takes effect with the next commit. To undo a copy - before that, see :hg:`revert`. + To undo marking a file as copied, use --forget. With that option, + both SOURCE and DEST are interpreted as destinations. The destination + file(s) will be left in place (still tracked). + + This command takes effect with the next commit. Returns 0 on success, 1 if errors are encountered. """ diff --git a/relnotes/next b/relnotes/next --- a/relnotes/next +++ b/relnotes/next @@ -12,6 +12,8 @@ commits that are being merged, when there are conflicts. Also works for conflicts caused by e.g. `hg graft`. + * `hg copy --forget` can be used to unmark a file as copied. + == New Experimental Features == diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -257,7 +257,7 @@ commit: addremove, close-branch, amend, secret, edit, force-close-branch, interactive, include, exclude, message, logfile, date, user, subrepos config: untrusted, edit, local, global, template continue: dry-run - copy: after, force, include, exclude, dry-run + copy: forget, after, force, include, exclude, dry-run debugancestor: debugapplystreamclonebundle: debugbuilddag: mergeable-file, overwritten-file, new-file diff --git a/tests/test-copy.t b/tests/test-copy.t --- a/tests/test-copy.t +++ b/tests/test-copy.t @@ -262,5 +262,62 @@ xyzzy: not overwriting - file exists ('hg copy --after' to record the copy) [1] + $ hg co -qC . + $ rm baz xyzzy + + +Test unmarking copy of a single file + +# Set up by creating a copy + $ hg cp bar baz +# Test uncopying a non-existent file + $ hg copy --forget non-existent + non-existent: $ENOENT$ +# Test uncopying an tracked but unrelated file + $ hg copy --forget foo + foo: not unmarking as copy - file is not marked as copied +# Test uncopying a copy source + $ hg copy --forget bar + bar: not unmarking as copy - file is not marked as copied +# baz should still be marked as a copy + $ hg st -C + A baz + bar +# Test the normal case + $ hg copy --forget baz + $ hg st -C + A baz +# Test uncopy with matching an non-matching patterns + $ hg cp bar baz --after + $ hg copy --forget bar baz + bar: not unmarking as copy - file is not marked as copied + $ hg st -C + A baz +# Test uncopy with no exact matches + $ hg cp bar baz --after + $ hg copy --forget . + $ hg st -C + A baz + $ hg forget baz + $ rm baz + +Test unmarking copy of a directory + + $ mkdir dir + $ echo foo > dir/foo + $ echo bar > dir/bar + $ hg add dir + adding dir/bar + adding dir/foo + $ hg ci -m 'add dir/' + $ hg cp dir dir2 + copying dir/bar to dir2/bar + copying dir/foo to dir2/foo + $ touch dir2/untracked + $ hg copy --forget dir2 + $ hg st -C + A dir2/bar + A dir2/foo + ? dir2/untracked $ cd ..