diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -787,14 +787,9 @@ def commit(ui, repo, *pats, **opts): raise util.Abort(str(inst)) def docopy(ui, repo, pats, opts): - if not pats: - raise util.Abort(_('no source or destination specified')) - elif len(pats) == 1: - raise util.Abort(_('no destination specified')) - pats = list(pats) - dest = pats.pop() - sources = [] - dir2dir = len(pats) == 1 and os.path.isdir(pats[0]) + cwd = repo.getcwd() + errors = 0 + copied = [] def okaytocopy(abs, rel, exact): reasons = {'?': _('is not managed'), @@ -805,74 +800,68 @@ def docopy(ui, repo, pats, opts): else: return True - for src, abs, rel, exact in walk(repo, pats, opts): - if okaytocopy(abs, rel, exact): - sources.append((abs, rel, exact)) - if not sources: - raise util.Abort(_('no files to copy')) - - cwd = repo.getcwd() - absdest = util.canonpath(repo.root, cwd, dest) - reldest = util.pathto(cwd, absdest) - if os.path.exists(reldest): - destisfile = not os.path.isdir(reldest) - else: - destisfile = not dir2dir and (len(sources) == 1 - or repo.dirstate.state(absdest) != '?') - - if destisfile and len(sources) > 1: - raise util.Abort(_('with multiple sources, destination must be a ' - 'directory')) - - srcpfxlen = 0 - if dir2dir: - srcpfx = util.pathto(cwd, util.canonpath(repo.root, cwd, pats[0])) - if os.path.exists(reldest): - srcpfx = os.path.split(srcpfx)[0] - if srcpfx: - srcpfx += os.sep - srcpfxlen = len(srcpfx) - - errs, copied = 0, [] - for abs, rel, exact in sources: - if destisfile: - mydest = reldest - elif dir2dir: - mydest = os.path.join(dest, rel[srcpfxlen:]) - else: - mydest = os.path.join(dest, os.path.basename(rel)) - myabsdest = util.canonpath(repo.root, cwd, mydest) - myreldest = util.pathto(cwd, myabsdest) - if not opts['force'] and repo.dirstate.state(myabsdest) not in 'a?': - ui.warn(_('%s: not overwriting - file already managed\n') % myreldest) - continue - mydestdir = os.path.dirname(myreldest) or '.' + def copy(abssrc, relsrc, target, exact): + abstarget = util.canonpath(repo.root, cwd, target) + reltarget = util.pathto(cwd, abstarget) + if not opts['force'] and repo.dirstate.state(abstarget) not in 'a?': + ui.warn(_('%s: not overwriting - file already managed\n') % + reltarget) + return + if ui.verbose or not exact: + ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) if not opts['after']: + targetdir = os.path.dirname(reltarget) or '.' + if not os.path.isdir(targetdir): + os.makedirs(targetdir) try: - if dir2dir: os.makedirs(mydestdir) - elif not destisfile: os.mkdir(mydestdir) - except OSError, inst: - if inst.errno != errno.EEXIST: raise - if ui.verbose or not exact: - ui.status(_('copying %s to %s\n') % (rel, myreldest)) - if not opts['after']: - try: - shutil.copyfile(rel, myreldest) - shutil.copymode(rel, myreldest) + shutil.copyfile(relsrc, reltarget) + shutil.copymode(relsrc, reltarget) except shutil.Error, inst: raise util.Abort(str(inst)) except IOError, inst: if inst.errno == errno.ENOENT: - ui.warn(_('%s: deleted in working copy\n') % rel) + ui.warn(_('%s: deleted in working copy\n') % relsrc) else: - ui.warn(_('%s: cannot copy - %s\n') % (rel, inst.strerror)) - errs += 1 - continue - repo.copy(abs, myabsdest) - copied.append((abs, rel, exact)) - if errs: + ui.warn(_('%s: cannot copy - %s\n') % + (relsrc, inst.strerror)) + errors += 1 + return + repo.copy(abssrc, abstarget) + copied.append((abssrc, relsrc, exact)) + + pats = list(pats) + if not pats: + raise util.Abort(_('no source or destination specified')) + if len(pats) == 1: + raise util.Abort(_('no destination specified')) + dest = pats.pop() + destdirexists = os.path.isdir(dest) + if (len(pats) > 1 or not os.path.exists(pats[0])) and not destdirexists: + raise util.Abort(_('with multiple sources, destination must be an ' + 'existing directory')) + + for pat in pats: + if os.path.isdir(pat): + if destdirexists: + striplen = len(os.path.split(pat)[0]) + else: + striplen = len(pat) + if striplen: + striplen += len(os.sep) + targetpath = lambda p: os.path.join(dest, p[striplen:]) + elif destdirexists: + targetpath = lambda p: os.path.join(dest, os.path.basename(p)) + else: + targetpath = lambda p: dest + for tag, abssrc, relsrc, exact in walk(repo, [pat], opts): + if okaytocopy(abssrc, relsrc, exact): + copy(abssrc, relsrc, targetpath(abssrc), exact) + + if errors: ui.warn(_('(consider using --after)\n')) - return errs, copied + if len(copied) == 0: + raise util.Abort(_('no files to copy')) + return errors, copied def copy(ui, repo, *pats, **opts): """mark files as copied for the next commit diff --git a/tests/test-rename b/tests/test-rename new file mode 100644 --- /dev/null +++ b/tests/test-rename @@ -0,0 +1,60 @@ +#!/bin/sh + +hg init +mkdir d1 d1/d11 d2 +echo d1/a > d1/a +echo d1/ba > d1/ba +echo d1/a1 > d1/d11/a1 +echo d1/b > d1/b +echo d2/b > d2/b +hg add d1/a d1/b d1/ba d1/d11/a1 d2/b +hg commit -m "1" -d "0 0" + +echo "# rename a single file" +hg rename d1/d11/a1 d2/c +hg status +hg update -C + +echo "# move a single file to an existing directory" +hg rename d1/d11/a1 d2 +hg status +hg update -C + +echo "# rename directory d1 as d3" +hg rename d1 d3 +hg status +hg update -C + +echo "# move directory d1/d11 to an existing directory d2 (removes empty d1)" +hg rename d1/d11 d2 +hg status +hg update -C + +echo "# move directories d1 and d2 to a new directory d3" +mkdir d3 +hg rename d1 d2 d3 +hg status +hg update -C + +echo "# move everything under directory d1 to existing directory d2, do not" +echo "# overwrite existing files (d2/b)" +hg rename d1/* d2 +hg status +diff d1/b d2/b +hg update -C + +echo "# attempt to move potentially more than one file into a non-existent" +echo "# directory" +hg rename 'glob:d1/**' dx + +echo "# move every file under d1 to d2/d21 (glob)" +mkdir d2/d21 +hg rename 'glob:d1/**' d2/d21 +hg status +hg update -C + +echo "# move every file under d1 starting with an 'a' to d2/d21 (regexp)" +mkdir d2/d21 +hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21 +hg status +hg update -C diff --git a/tests/test-rename.out b/tests/test-rename.out new file mode 100644 --- /dev/null +++ b/tests/test-rename.out @@ -0,0 +1,93 @@ +# rename a single file +A d2/c +R d1/d11/a1 +# move a single file to an existing directory +A d2/a1 +R d1/d11/a1 +# rename directory d1 as d3 +copying d1/a to d3/a +copying d1/b to d3/b +copying d1/ba to d3/ba +copying d1/d11/a1 to d3/d11/a1 +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +A d3/a +A d3/b +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +# move directory d1/d11 to an existing directory d2 (removes empty d1) +copying d1/d11/a1 to d2/d11/a1 +removing d1/d11/a1 +A d2/d11/a1 +R d1/d11/a1 +# move directories d1 and d2 to a new directory d3 +copying d1/a to d3/d1/a +copying d1/b to d3/d1/b +copying d1/ba to d3/d1/ba +copying d1/d11/a1 to d3/d1/d11/a1 +copying d2/b to d3/d2/b +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +removing d2/b +A d3/d1/a +A d3/d1/b +A d3/d1/ba +A d3/d1/d11/a1 +A d3/d2/b +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +R d2/b +# move everything under directory d1 to existing directory d2, do not +# overwrite existing files (d2/b) +d2/b: not overwriting - file already managed +copying d1/d11/a1 to d2/d11/a1 +removing d1/d11/a1 +A d2/a +A d2/ba +A d2/d11/a1 +R d1/a +R d1/ba +R d1/d11/a1 +1c1 +< d1/b +--- +> d2/b +# attempt to move potentially more than one file into a non-existent +# directory +abort: with multiple sources, destination must be an existing directory +# move every file under d1 to d2/d21 (glob) +copying d1/a to d2/d21/a +copying d1/b to d2/d21/b +copying d1/ba to d2/d21/ba +copying d1/d11/a1 to d2/d21/a1 +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +A d2/d21/a +A d2/d21/a1 +A d2/d21/b +A d2/d21/ba +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +# move every file under d1 starting with an 'a' to d2/d21 (regexp) +copying d1/a to d2/d21/a +copying d1/d11/a1 to d2/d21/a1 +removing d1/a +removing d1/d11/a1 +A d2/d21/a +A d2/d21/a1 +R d1/a +R d1/d11/a1