From: Eric S. Raymond Date: Wed, 10 Nov 2010 18:41:33 +0000 (-0500) Subject: Runs to completion in dryrun mode. X-Git-Tag: 0.1~77 X-Git-Url: http://thyrsus.com/gitweb/?a=commitdiff_plain;h=fff59564fe8c706cdf27cc55da8509e107b2e310;p=rcs-fast-import.git Runs to completion in dryrun mode. --- diff --git a/rcs-fast-import b/rcs-fast-import index 98cc9dc..d8d5302 100755 --- a/rcs-fast-import +++ b/rcs-fast-import @@ -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