[RFC] make copy & rename behave like cp -r & mv

Robin Farine robin.farine at terminus.org
Fri Sep 23 20:26:25 CDT 2005


Hi,

The patch below changes docopy(), the code common to the copy and
rename commands, so that these commands behave like the UNIX cp -r
and mv commands respectively. The patch also removes the commands'
--parents option whose functionality, I believe, can be reproduced
with the proposed modification.

If there is any interest in this patch, I am willing to provide test
cases for it.

Thanks,

Robin

# HG changeset patch
# User Robin Farine <robin.farine at terminus.org>
# Node ID df39d966d2a4a939ea475461aeb22244bb86a408
# Parent  8c094fb47b59234088c549a9bcf8deb9316c0d7b
Make copy & rename behave like cp -r & mv.

diff -r 8c094fb47b59 -r df39d966d2a4 doc/hg.1.txt
--- a/doc/hg.1.txt	Fri Sep 23 17:19:35 2005 -0700
+++ b/doc/hg.1.txt	Sat Sep 24 02:57:11 2005 +0200
@@ -201,7 +201,6 @@
     -I, --include <pat>   include names matching the given patterns
     -X, --exclude <pat>   exclude names matching the given patterns
     -f, --force           forcibly copy over an existing managed file
-    -p, --parents         append source path to dest
 
     aliases: cp
 
@@ -493,7 +492,6 @@
     Options:
     -A, --after        record a rename that has already occurred
     -f, --force        forcibly copy over an existing managed file
-    -p, --parents      append source path to dest
 
     aliases: mv
 
diff -r 8c094fb47b59 -r df39d966d2a4 mercurial/commands.py
--- a/mercurial/commands.py	Fri Sep 23 17:19:35 2005 -0700
+++ b/mercurial/commands.py	Sat Sep 24 02:57:11 2005 +0200
@@ -701,82 +701,90 @@
         raise util.Abort(str(inst))
 
 def docopy(ui, repo, pats, opts):
+    cwd = repo.getcwd()
+
+    def gensources(pats):
+        reasons = {'?': 'is not managed',
+                   'a': 'has been marked for add'}
+        roots, matchfn, anypats = matchpats(repo, cwd, pats, opts)
+        numsrcs = 0
+        srcmap = {}
+        for prefix in roots:
+            srcs = []
+            for src, fn in repo.walk(files=[prefix], match=matchfn):
+                rel = util.pathto(cwd, fn)
+                exact = fn == prefix
+                reason = reasons.get(repo.dirstate.state(fn))
+                if reason:
+                    if exact:
+                        ui.warn('%s: not copying - file %s\n' % (rel, reason))
+                else:
+                    numsrcs += 1
+                    srcs.append((fn, rel, exact))
+            if srcs:
+                srcmap[prefix] = srcs
+        return numsrcs, srcmap
+
+
     if not pats:
         raise util.Abort('no source or destination specified')
-    elif len(pats) == 1:
+    if len(pats) == 1:
         raise util.Abort('no destination specified')
     pats = list(pats)
     dest = pats.pop()
-    sources = []
-
-    def okaytocopy(abs, rel, exact):
-        reasons = {'?': 'is not managed',
-                   'a': 'has been marked for add'}
-        reason = reasons.get(repo.dirstate.state(abs))
-        if reason:
-            if exact: ui.warn('%s: not copying - file %s\n' % (rel, reason))
-        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 = len(sources) == 1 or repo.dirstate.state(absdest) != '?'
-
-    if destisfile:
-        if opts['parents']:
-            raise util.Abort('with --parents, destination must be a directory')
-        elif len(sources) > 1:
-            raise util.Abort('with multiple sources, destination must be a '
-                             'directory')
+    numsrcs, sources = gensources(pats)
+    destisdir = os.path.isdir(reldest)
+    if numsrcs == 0:
+        raise util.Abort('no files to copy')
+    if len(sources) != 1 and not destisdir:
+        raise util.Abort('with multiple sources, destination must be an '
+                         'existing directory')
+    destisfile = numsrcs == 1 and not destisdir
+
     errs, copied = 0, []
-    for abs, rel, exact in sources:
-        if opts['parents']:
-            mydest = os.path.join(dest, rel)
-        elif destisfile:
-            mydest = reldest
+    for srcpfx in sources.keys():
+        if os.path.isdir(util.pathto(cwd, srcpfx)) and not destisdir:
+            striplen = len(srcpfx)
         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 '.'
-        if not opts['after']:
-            try:
-                if opts['parents']: 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)
-                n = repo.manifest.tip()
-                mf = repo.manifest.readflags(n)
-                util.set_exec(myreldest, util.is_exec(rel, mf[abs]))
-            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)
-                else:
-                    ui.warn('%s: cannot copy - %s\n' % (rel, inst.strerror))
-                errs += 1
+            striplen = len(os.path.split(srcpfx)[0])
+        if striplen:
+            striplen += len(os.sep)
+        for abs, rel, exact in sources[srcpfx]:
+            if destisfile:
+                reltarget = reldest
+                abstarget = absdest
+            else:
+                suffix = abs[striplen:]
+                reltarget = os.path.join(reldest, suffix)
+                abstarget = os.path.join(absdest, suffix)
+            if not opts['force'] and repo.dirstate.state(abstarget) not in 'a?':
+                ui.warn('%s: not overwriting - file already managed\n' %
+                        reltarget)
                 continue
-        repo.copy(abs, myabsdest)
-        copied.append((abs, rel, exact))
+            if ui.verbose or not exact:
+                ui.status('copying %s to %s\n' % (rel, reltarget))
+            if not opts['after']:
+                targetdir = os.path.dirname(reltarget) or '.'
+                if not os.path.isdir(targetdir):
+                    os.makedirs(targetdir)
+                try:
+                    shutil.copyfile(rel, reltarget)
+                    n = repo.manifest.tip()
+                    mf = repo.manifest.readflags(n)
+                    util.set_exec(reltarget, util.is_exec(rel, mf[abs]))
+                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)
+                    else:
+                        ui.warn('%s: cannot copy - %s\n' % (rel, inst.strerror))
+                    errs += 1
+                    continue
+            repo.copy(abs, abstarget)
+            copied.append((abs, rel, exact))
     if errs:
         ui.warn('(consider using --after)\n')
     return errs, copied
@@ -1788,8 +1796,7 @@
              [('I', 'include', [], 'include path in search'),
               ('X', 'exclude', [], 'exclude path from search'),
               ('A', 'after', None, 'record a copy after it has happened'),
-              ('f', 'force', None, 'replace destination if it exists'),
-              ('p', 'parents', None, 'append source path to dest')],
+              ('f', 'force', None, 'replace destination if it exists')],
              'hg copy [OPTION]... [SOURCE]... DEST'),
     "debugancestor": (debugancestor, [], 'debugancestor INDEX REV1 REV2'),
     "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
@@ -1902,8 +1909,7 @@
                   [('I', 'include', [], 'include path in search'),
                    ('X', 'exclude', [], 'exclude path from search'),
                    ('A', 'after', None, 'record a copy after it has happened'),
-                   ('f', 'force', None, 'replace destination if it exists'),
-                   ('p', 'parents', None, 'append source path to dest')],
+                   ('f', 'force', None, 'replace destination if it exists')],
                   'hg rename [OPTION]... [SOURCE]... DEST'),
     "^revert":
         (revert,


More information about the Mercurial mailing list