]> thyrsus.com Git - rcs-fast-import.git/commitdiff
Runs to completion in dryrun mode.
authorEric S. Raymond <esr@thyrsus.com>
Wed, 10 Nov 2010 18:41:33 +0000 (13:41 -0500)
committerEric S. Raymond <esr@thyrsus.com>
Wed, 10 Nov 2010 18:41:33 +0000 (13:41 -0500)
rcs-fast-import

index 98cc9dca8ed4b516c7111c06403928522c844708..d8d53025ba849a4d0a308e5cf3e884719c2bae96 100755 (executable)
@@ -6,12 +6,47 @@
 #
 # Requires Python 2.5 or newer.
 #
-import sys, os, cStringIO, subprocess
-import time, calendar, re, signal, copy, shlex
-import email.utils
+"""
+Usage: rcs-fast-import [-v] [-u] [-l] [-d] [-n] [-V] 
+
+Break the content of a git-fast-import stream on stdin into individual RCS
+master files in RCS subdirectories of the corresponding directory structure.
+
+With the -v option, emit a twirling-baton prompt during stream read, and
+progress messages during RCS writes.
+
+With the -l option, check out and lock all resulting files at their latest
+revisions; with the -u option, check out but do not lock.
+
+The -V option displays the program version number and exits. -? displays
+this help and exits.
+
+The -d option enables delete operations in the the input
+stream. Normally, delete operations in the input stream are ignored so
+as to maximize access to the unpacked history.
+
+The -n option sets dry-run mode; parse the import stream, then go through
+all logic other than actually creating files.  Useful for debugging in
+combination with -v.
+
+R file operations rename corresponding RCS master files; C file operations
+copy them.
+
+If D, R, or C operations are present, the conversion will not round-trip
+properly (that is, rcs-fast-export | rcs-fast-import won't be an exact
+copy) with differences near deleted, renamed, and copied files.
+
+"""
+
+import sys, os, getopt, re, cStringIO, subprocess, shutil
+import time, calendar, copy, shlex, email.utils
 
 version="0.1"
 
+#
+# Generic import-stream machinery swiped from reposurgeon begins here.
+#
+
 verbose         = 0
 DEBUG_SHUFFLE   = 2    # Debug directory handling
 DEBUG_DELETE    = 2    # Debug canonicalization after deletes
@@ -167,11 +202,6 @@ class Reset:
     def __init__(self):
         self.ref = None
         self.committish = None
-    def __str__(self):
-        st = "reset %s\n" % self.ref
-        if self.committish:
-            st += "from %s\n\n" % self.committish
-        return st
 
 class FileOp:
     "Represent a primitive operation on a file."
@@ -191,19 +221,6 @@ class FileOp:
             self.op = "deleteall"
         else:
             raise FatalException("unexpected fileop %s while parsing" % opline)
-    def paths(self):
-        "return the set of all paths touched by this file op."
-        if self.op in ("M", "D"):
-            return set([self.path])
-        elif self.op in ("R", "C"):
-            return set([self.source, self.target])
-    def relevant(self, other):
-        "Do two fileops touch the same file(s)?"
-        if self.op == "deleteall" or other.op == "deleteall":
-            return True
-        else:
-            return self.paths() & other.paths()
-        return st
 
 class Commit:
     "Generic commit object."
@@ -223,15 +240,6 @@ class Commit:
     def parents(self):
         "Get a list of this commit's parents."
         return filter(lambda e:e.mark in self.parent_marks,self.repo.commits()) 
-    def cliques(self):
-        "Return a dictionary mapping filenames to associated M cliques."
-        cliques = {}
-        for (i, fileop) in enumerate(self.fileops):
-            if fileop.op == "M":
-                if fileop.path not in cliques:
-                    cliques[fileop.path] = []
-                cliques[fileop.path].append(i)
-        return cliques
 
 class Passthrough:
     "Represents a passthrough line."
@@ -503,6 +511,10 @@ def complain(msg):
 def announce(msg):
     print "rcs-fast-import:", msg
 
+def master(filename):
+    "Return the master file corresponding to a specified filename."
+    return "RCS/%s,v" % filename
+
 def do_or_die(cmd, legend=""):
     "Either execute a command or raise a fatal exception."
     if legend:
@@ -518,8 +530,101 @@ def do_or_die(cmd, legend=""):
     except OSError, e:
         raise FatalException("execution of %s%s failed: %s" % (cmd, legend, e))
 
+#
+# All RCS-specific code is beyond this point.
+# It wouldn't be difficult to adapt this for SCCS, if there were any need.
+#
+
+def modify_file(path, mode, copy):
+    "Perform RCS file modifications,"
+    rcsdir = os.path.join(os.path.dirname(path), "RCS")
+    if not os.path.exists(rcsdir):
+        try:
+            os.mkdir(rcsdir)
+        except OSError:
+            raise FatalException("can't create %s." % rcsdir)
+    # FIXME
+
 if __name__ == '__main__':
+    (options, arguments) = getopt.getopt(sys.argv[1:], "dlnuvV?")
+    do_deletes = do_locked_checkouts = do_unlocked_checkouts = False
+    dryrun = False
+    for (switch, val) in options:
+       if (switch == '-?'):
+           print __doc__
+            raise SystemExit, 0
+        elif (switch == '-d'):
+            do_deletes = True
+        elif (switch == '-l'):
+            do_locked_checkouts = True
+        elif (switch == '-n'):
+            dryrun = True
+        elif (switch == 'u'):
+            do_unlocked_checkouts = True
+       elif (switch == '-v'):
+           verbose += 1
+       elif (switch == '-V'):
+           print "rcs-fast-import version %s" % version
+
     repo = Repository()
     repo.fast_import(sys.stdin)
 
+    try:
+        for (i, event) in enumerate(repo.events):
+            if isinstance(event, Blob) or isinstance(event, Reset) or isinstance(event, Passthrough):
+                continue
+            elif isinstance(event, Tag):
+                # Tagger and comment info is lost.
+                if verbose:
+                    announce("Tagging %s with %s" % (event.committish, event.name))
+            elif isinstance(event, Commit):
+                for op in event.fileops:
+                    if op.op == "M":
+                        if verbose:
+                            announce("%5d M %s" % (i+1, op.path))
+                        if not dryrun:
+                            modify_file(op.path, op.mode, op.copyname)
+                    elif op.op in ("D", "deleteall"):
+                        masterfile = master(op.path)
+                        # If we delete masters, we lose ability to
+                        # retrieve old versions.
+                        if do_deletes:
+                            if verbose:
+                                announce("%5d D %s" % (i+1, op.path))
+                            if not dryrun:
+                                os.remove(masterfile)
+                        elif verbose:
+                            announce("%5d D %s ignored" % (i+1, masterfile))
+                    elif op.op == "R":
+                        # Dodgy, but since user is more likely to be
+                        # interested in later versions this is better
+                        # than doing nothing.
+                        if verbose:
+                            announce("%5d R '%s' '%s'" % (i+1, op.source, op.target))
+                        if not dryrun:
+                            os.rename(master(op.source), master(op.target))
+                    elif op.op == "C":
+                        # The copy will get the original's history.
+                        # There's a case that what we ought to do instead
+                        # is check out the tip rev and use it to initialize
+                        # a new master.
+                        if verbose:
+                            announce("%d C '%s' '%s'" % (i+1, op.source, op.target))
+                        if not dryrun:
+                            shutil.copy(master(op.source), master(op.target))
+                    else:
+                        raise FatalException("unknown op type %s" % op.op)
+        if do_locked_checkouts or do_unlocked_checkouts:
+            # FIXME
+            pass
+        raise SystemExit, 0
+    except FatalException, e:
+        complain(e.msg)
+        repo.cleanup()
+        raise SystemExit, 1
+    except OSError, e:
+        complain(e)
+        repo.cleanup()
+        raise SystemExit, 1
+
 # end