generalize copy/rename to handle more than one source directory
authorRobin Farine <robin.farine@terminus.org>
Tue, 08 Nov 2005 10:35:05 -0800
changeset 1512 53ad6ee6ede4
parent 1511 a91bfbbe88d3
child 1513 5c3b93b244aa
generalize copy/rename to handle more than one source directory
mercurial/commands.py
tests/test-rename
tests/test-rename.out
--- a/mercurial/commands.py	Tue Nov 08 10:35:00 2005 -0800
+++ b/mercurial/commands.py	Tue Nov 08 10:35:05 2005 -0800
@@ -787,14 +787,9 @@
         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 @@
         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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rename	Tue Nov 08 10:35:05 2005 -0800
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rename.out	Tue Nov 08 10:35:05 2005 -0800
@@ -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