[Zope3-checkins] CVS: Zope3/src/zope/fssync - copier.py:1.1 fsmerger.py:1.20 fssync.py:1.46 main.py:1.37 metadata.py:1.11

Fred L. Drake, Jr. fred at zope.com
Fri Sep 5 16:09:37 EDT 2003


Update of /cvs-repository/Zope3/src/zope/fssync
In directory cvs.zope.org:/tmp/cvs-serv18320

Modified Files:
	fsmerger.py fssync.py main.py metadata.py 
Added Files:
	copier.py 
Log Message:
Implement a "zsync copy" command that handles copying of objects, including
the extra and annotation data.


=== Added File Zope3/src/zope/fssync/copier.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Tree-copy helpers for 'zsync copy' and 'zbundle create'.

$Id: copier.py,v 1.1 2003/09/05 19:09:36 fdrake Exp $
"""

import os
import shutil

from zope.fssync import fsutil


class FileCopier:
    """Copy from a normal file tree into an fssync checkout."""

    def __init__(self, sync):
        self.sync = sync
        self.ignore = sync.fsmerger.ignore

    def copy(self, source, target, children=True):
        if os.path.isdir(source):
            os.mkdir(target)
            shutil.copymode(source, target)
            self.addEntry(source, target)
            if children:
                queue = self.listDirectory(source)
                while queue:
                    fn = queue.pop(0)
                    src = os.path.join(source, fn)
                    dst = os.path.join(target, fn)
                    if os.path.isdir(src):
                        os.mkdir(dst)
                        shutil.copymode(src, dst)
                        self.addEntry(src, dst)
                        queue.extend([os.path.join(fn, f)
                                      for f in self.listDirectory(src)])
                    else:
                        shutil.copy(src, dst)
                        self.addEntry(src, dst)
        else:
            shutil.copy(source, target)
            self.addEntry(source, target)

    def addEntry(self, source, target):
        self.sync.add(target)

    def listDirectory(self, dir):
        return [fn
                for fn in os.listdir(dir)
                if fn != "@@Zope"
                if not self.sync.fsmerger.ignore(fn)]


class ObjectCopier(FileCopier):
    """Copy objects from an fssync checkout into an fssync checkout."""

    def addEntry(self, source, target):
        type, factory = self.sync.metadata.gettypeinfo(source)
        self._syncadd(target, type, factory)
        self._copyspecials(source, target, fsutil.getextra)
        self._copyspecials(source, target, fsutil.getannotations)

    def _syncadd(self, target, type, factory):
        self.sync.add(target, type, factory)

    def _copyspecials(self, source, target, getwhat):
        src = getwhat(source)
        if os.path.isdir(src):
            dst = getwhat(target)
            fsutil.ensuredir(dst)
            copier = SpecialCopier(self.sync)
            for name in self.sync.metadata.getnames(src):
                # copy a single child
                copier.copy(os.path.join(src, name), os.path.join(dst, name))
            self.sync.metadata.flush()

    def listDirectory(self, dir):
        # We don't need to worry about fsmerger.ignore() since we're
        # only relying on metadata to generate the list of names.
        return self.sync.metadata.getnames(dir)


class SpecialCopier(ObjectCopier):
    """Copy extras and annotations as part of an object copy.

    This is a specialized copier that doesn't expect the original to
    have a path.
    """

    def _syncadd(self, target, type, factory):
        self.sync.basicadd(target, type, factory)


=== Zope3/src/zope/fssync/fsmerger.py 1.19 => 1.20 ===
--- Zope3/src/zope/fssync/fsmerger.py:1.19	Thu Sep  4 10:59:32 2003
+++ Zope3/src/zope/fssync/fsmerger.py	Fri Sep  5 15:09:36 2003
@@ -57,24 +57,25 @@
             # XXX probably for the best; we *don't* know the right
             # thing to do anyway
             return
-        self.merge_extra(local, remote)
-        self.merge_annotations(local, remote)
+        flag = self.metadata.getentry(local).get("flag")
+        self.merge_extra(local, remote, flag)
+        self.merge_annotations(local, remote, flag)
         if not exists(local) and not self.metadata.getentry(local):
             self.remove_special(local, "Extra")
             self.remove_special(local, "Annotations")
             self.remove_special(local, "Original")
 
-    def merge_extra(self, local, remote):
+    def merge_extra(self, local, remote, flag):
         """Helper to merge the Extra trees."""
         lextra = fsutil.getextra(local)
         rextra = fsutil.getextra(remote)
-        self.merge_dirs(lextra, rextra)
+        self.merge_dirs(lextra, rextra, flag=flag, special=True)
 
-    def merge_annotations(self, local, remote):
+    def merge_annotations(self, local, remote, flag):
         """Helper to merge the Anotations trees."""
         lannotations = fsutil.getannotations(local)
         rannotations = fsutil.getannotations(remote)
-        self.merge_dirs(lannotations, rannotations)
+        self.merge_dirs(lannotations, rannotations, flag=flag, special=True)
 
     def remove_special(self, local, what):
         """Helper to remove an Original, Extra or Annotations file/tree."""
@@ -86,7 +87,7 @@
             else:
                 # XXX when should this ever happen?
                 os.remove(target)
-        # remove the specials directory if it's empty
+        # remove the specials directory only if it's empty
         if isdir(dir):
             try:
                 os.rmdir(dir)
@@ -108,7 +109,7 @@
                                         action, state) or state
         self.reportaction(action, state, local)
 
-    def merge_dirs(self, localdir, remotedir):
+    def merge_dirs(self, localdir, remotedir, flag=None, special=False):
         """Merge remote directory into local directory."""
         lentrynames = self.metadata.getnames(localdir)
         rentrynames = self.metadata.getnames(remotedir)
@@ -119,7 +120,7 @@
 
             if not lentry:
                 if not rentry:
-                    if exists(localdir):
+                    if exists(localdir) and not special:
                         self.reportdir("?", localdir)
                 else:
                     if not exists(localdir):
@@ -152,11 +153,14 @@
                 self.clear_dir(localdir)
                 return
 
+        if not special:
+            flag = lentry.get("flag")
         if exists(localdir):
-            if lentry.get("flag") == "added":
+            if flag == "added":
                 if exists(remotedir):
                     self.reportdir("U", localdir)
-                    del lentry["flag"]
+                    if "flag" in lentry:
+                        del lentry["flag"]
                 else:
                     self.reportdir("A", localdir)
             else:
@@ -169,13 +173,13 @@
                         # remote versions are gone, unless there have
                         # been local changes.
                         self.merge(join(localdir, name), join(remotedir, name))
-                    self.clear_dir(localdir)
+                    if flag != "added":
+                        self.clear_dir(localdir)
                     return
 
             lnames = dict([(normcase(name), name)
                            for name in os.listdir(localdir)])
         else:
-            flag = lentry.get("flag")
             if flag == "removed":
                 self.reportdir("R", localdir)
                 return # There's no point in recursing down!


=== Zope3/src/zope/fssync/fssync.py 1.45 => 1.46 ===
--- Zope3/src/zope/fssync/fssync.py:1.45	Fri Aug 29 08:47:36 2003
+++ Zope3/src/zope/fssync/fssync.py	Fri Sep  5 15:09:36 2003
@@ -496,6 +496,8 @@
             # XXX how to recurse?
             self.dirrevert(target)
         self.metadata.flush()
+        if os.path.isdir(target):
+            target = join(target, "")
         self.reporter("Reverted " + target)
 
     def dirrevert(self, target):
@@ -587,6 +589,35 @@
         if factory:
             entry["factory"] = factory
         return entry
+
+    def copy(self, src, dst=None, children=True):
+        if not exists(src):
+            raise Error("%s does not exist" % src)
+        dst = dst or ''
+        if (not dst) or isdir(dst):
+            target_dir = dst
+            target_name = basename(os.path.abspath(src))
+        else:
+            target_dir, target_name = os.path.split(dst)
+            if target_dir:
+                if not exists(target_dir):
+                    raise Error("destination directory does not exist: %r"
+                                % target_dir)
+                if not isdir(target_dir):
+                    import errno
+                    err = IOError(errno.ENOTDIR, "Not a directory", target_dir)
+                    raise Error(str(err))
+        if not self.metadata.getentry(target_dir):
+            raise Error("nothing known about '%s'" % target_dir)
+        srcentry = self.metadata.getentry(src)
+        from zope.fssync import copier
+        if srcentry:
+            # already known to fssync; we need to deal with metadata,
+            # Extra, and Annotations
+            copier = copier.ObjectCopier(self)
+        else:
+            copier = copier.FileCopier(self)
+        copier.copy(src, join(target_dir, target_name), children)
 
     def mkdir(self, path):
         dir, name = split(path)


=== Zope3/src/zope/fssync/main.py 1.36 => 1.37 ===
--- Zope3/src/zope/fssync/main.py:1.36	Wed Aug 27 15:36:21 2003
+++ Zope3/src/zope/fssync/main.py	Fri Sep  5 15:09:36 2003
@@ -16,17 +16,20 @@
 
 Command line syntax summary:
 
-%(program)s help [COMMAND ...]
-%(program)s checkout [local_options] URL [TARGETDIR]
-%(program)s update [local_options] [TARGET ...]
-%(program)s commit [local_options] [TARGET ...]
-%(program)s diff [local_options] [TARGET ...]
-%(program)s status [local_options] [TARGET ...]
-%(program)s add [local_options] PATH ...
-%(program)s mkdir [local_options] PATH ...
-%(program)s remove [local_options] TARGET ...
-%(program)s resolve [local_options] PATH ...
-%(program)s checkin [local_options] URL [TARGETDIR]
+%(program)s add [options] PATH ...
+%(program)s checkin [options] URL [TARGETDIR]
+%(program)s checkout [options] URL [TARGETDIR]
+%(program)s commit [options] [TARGET ...]
+%(program)s copy [options] SOURCE [TARGET]
+%(program)s diff [options] [TARGET ...]
+%(program)s login [options] URL
+%(program)s logout [options] URL
+%(program)s mkdir PATH ...
+%(program)s remove [options] TARGET ...
+%(program)s resolve PATH ...
+%(program)s revert PATH ...
+%(program)s status [TARGET ...]
+%(program)s update [TARGET ...]
 
 ``%(program)s help'' prints the global help (this message)
 ``%(program)s help command'' prints the local help for the command
@@ -150,6 +153,36 @@
     for a in args:
         fs.add(a, type, factory)
 
+def copy(opts, args):
+    """%(program)s copy [-l | -R] SOURCE [TARGET]
+
+    """
+    recursive = None
+    for o, a in opts:
+        if o in ("-l", "--local"):
+            if recursive:
+                raise Usage("%r conflicts with %r" % (o, recursive))
+            recursive = False
+        elif o in ("-R", "--recursive"):
+            if recursive is False:
+                raise Usage("%r conflicts with -l" % o)
+            recursive = o
+    if not args:
+        raise Usage("copy requires at least one argument")
+    if len(args) > 2:
+        raise Usage("copy allows at most two arguments")
+    source = args[0]
+    if len(args) == 2:
+        target = args[1]
+    else:
+        target = None
+    if recursive is None:
+        recursive = True
+    else:
+        recursive = bool(recursive)
+    fs = FSSync()
+    fs.copy(source, target, children=recursive)
+
 def remove(opts, args):
     """%(program)s remove TARGET ...
 
@@ -321,6 +354,7 @@
     (checkin,  "",        "F:m:",       "file= message="),
     (checkout, "co",      "",           ""),
     (commit,   "ci",      "F:m:r",      "file= message= raise-on-conflicts"),
+    (copy,     "cp",      "lR",         "local recursive"),
     (diff,     "di",      "bBcC:iNuU:", "brief context= unified="),
     (login,    "",        "u:",         "user="),
     (logout,   "",        "u:",         "user="),


=== Zope3/src/zope/fssync/metadata.py 1.10 => 1.11 ===
--- Zope3/src/zope/fssync/metadata.py:1.10	Sun Aug 17 02:08:56 2003
+++ Zope3/src/zope/fssync/metadata.py	Fri Sep  5 15:09:36 2003
@@ -59,6 +59,10 @@
         dir, base = split(file)
         return self.getmanager(dir).getentry(base)
 
+    def gettypeinfo(self, path):
+        entry = self.getentry(path)
+        return entry.get("type"), entry.get("factory")
+
     def getmanager(self, dir):
         dir = realpath(dir)
         key = normcase(dir)




More information about the Zope3-Checkins mailing list