From 2cdc93b9ec4d9e767d8f06b2565721b54863d3be Mon Sep 17 00:00:00 2001 From: "Eric S. Raymond" Date: Thu, 11 Nov 2010 11:45:35 -0500 Subject: [PATCH] Implement preservation of committer and author metadata. --- rcs-fast-import | 104 +++++++++++++++++++++++++++----------------- rcs-fast-import.xml | 7 ++- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/rcs-fast-import b/rcs-fast-import index 74154fb..642532c 100755 --- a/rcs-fast-import +++ b/rcs-fast-import @@ -10,7 +10,7 @@ # * Branch structure! # """ -Usage: rcs-fast-import [-v] [-u] [-l] [-d] [-n] [-V] +Usage: rcs-fast-import [-v] [-p] [-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. @@ -18,6 +18,10 @@ master files in RCS subdirectories of the corresponding directory structure. With the -v option, emit a twirling-baton prompt during stream read, and more progress messages during RCS writes. +Normally, committer and author data are written into each comment as +RFC-822 mail headers, followed by a blank line. The -p option suppresses +this, producing a plain RCS comment. + 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. @@ -35,17 +39,18 @@ 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. +If D, R, or C operations are present, the conversion will not +round-trip properly (that is, the file structure after rcs-fast-export +| rcs-fast-import won't be an exact copy) with differences near +deleted, renamed, and copied files. Warning: All tag metadata other than the name will be lost. The date imputed to commits is from the author metadata if that exists, otherwise -from the committer metadata. Committer and author information is lost. +from the committer metadata. """ import sys, os, getopt, re, cStringIO, subprocess, shutil -import time, calendar, copy, shlex, email.utils +import time, calendar, copy, shlex, email.message, email.utils version="0.1" @@ -158,6 +163,10 @@ class Attribution: self.date = Date(person.strip().split("|")[2]) except (ValueError, IndexError): raise FatalException("malformed attribution %s" % person) + def email_out(self, msg, hdr): + "Update an RC822 message object with a representation of this." + msg[hdr] = self.name + " <" + self.email + ">" + msg[hdr + "-Date"] = self.date.rfc822() class Blob: "Represent a detached blob of data referenced by a mark." @@ -176,6 +185,16 @@ class Tag: self.committish = committish self.tagger = tagger self.comment = content + def email_out(self, eventnum): + "Enable do_mailbox_out() to report these." + msg = email.message.Message() + msg.set_unixfrom(None) + msg["Event-Number"] = str(eventnum+1) + msg["Tag-Name"] = self.name + if self.tagger: + self.tagger.email_out(msg, "Tagger") + msg.set_payload(self.comment) + return str(msg) class Reset: "Represents a branch creation." @@ -215,6 +234,30 @@ class Commit: self.fileops = [] # blob and file operation list self.properties = {} # commit properties (extension) self.taglist = [] # Tags that point at this commit + def email_out(self): + "Enable do_mailbox_out() to report these." + msg = email.message.Message() + msg.set_unixfrom(None) + if self.authors: + self.authors[0].email_out(msg, "Author") + for (i, coauthor) in enumerate(self.authors[1:]): + coauthor.email_out(msg, "Author" + `2+i`) + self.committer.email_out(msg, "Committer") + empty_properties = [] + propkeys = self.properties.keys() + propkeys.sort() + for name in propkeys: + value = self.properties[name] + if value in (True, False): + if value: + empty_properties.append(name) + else: + hdr = "-".join(map(lambda s: s.capitalize(), name.split("-"))) + msg["Property-" + hdr] = value + if empty_properties: + msg["Empty-Properties"] = ",".join(empty_properties) + msg.set_payload(self.comment) + return str(msg) class Passthrough: "Represents a passthrough line." @@ -491,42 +534,14 @@ def complain(msg): def announce(msg): print "rcs-fast-import:", msg -class popen_or_die: - "Read or write from a subordinate process." - def __init__(self, cmd, legend="", mode="r"): - assert mode in ("r", "w") - self.cmd = cmd - self.legend = legend - self.mode = mode - if self.legend: - self.legend = " " + self.legend - def __enter__(self): - if verbose >= DEBUG_COMMANDS: - if self.mode == "r": - announce("reading from '%s'%s" % (self.cmd, self.legend)) - else: - announce("writing to '%s'%s" % (self.cmd, self.legend)) - if dryrun: - self.fp = file("/dev/null", self.mode) - return self.fp - try: - self.fp = os.popen(self.cmd, self.mode) - return self.fp - except OSError, e: - raise FatalException("execution of %s%s failed: %s" \ - % (self.cmd, self.legend, e)) - def __exit__(self, type, value, traceback): - if self.fp.close() is not None: - raise FatalException("%s%s returned error." \ - % (self.cmd, self.legend)) - return False - def do_or_die(cmd, legend=""): "Either execute a command or raise a fatal exception." if legend: legend = " " + legend if verbose >= DEBUG_COMMANDS: announce("executing '%s'%s" % (cmd, legend)) + if not verbose: + cmd += ">/dev/null 2>&1" if dryrun: return try: @@ -564,7 +579,7 @@ class RCS: for name in files: res.append(os.path.join(root, name)) return res - def commit(self, event, committables): + def commit(self, event, committables, roundtrip): "Perform RCS file checkins and modifications," for (path, mode, copyfile) in committables: if mode == "160000": @@ -585,13 +600,17 @@ class RCS: except OSError: raise FatalException("refusing to step on %s." % path) responsible = event.authors[0] or event.committer - command = "ci -q -l" + if roundtrip: + comment = event.email_out() + else: + comment = event.comment + command = "ci -l" # If we were going to try to match committer or author # email addresses to local names, this would be the place # to do it. Not a whole lot of point in trying, though. #command += " -w%s" % responsible.email command += " -d'%s'" % responsible.date.rfc822() - command += ' -m"%s"' % event.comment.replace('"', '\\"') + command += ' -m"%s"' % comment.replace('"', '\\"') command += " " + path + " " + RCS.master(path) do_or_die(command, "checkin") if verbose >= DEBUG_SHUFFLE: @@ -621,15 +640,18 @@ class RCS: do_or_die("co -u %s" % (" ".join(RCS.rcsfiles()),)) if __name__ == '__main__': - (options, arguments) = getopt.getopt(sys.argv[1:], "dlnuvV?") + (options, arguments) = getopt.getopt(sys.argv[1:], "dlnpuvV?") do_unlocked_checkouts = False checkout_mode = None + roundtrip = True for (switch, val) in options: if (switch == '-?'): print __doc__ raise SystemExit, 0 elif switch == '-d': do_deletes = True + elif switch == '-p': + roundtrip = False elif switch in ('-l', '-u'): checkout_mode = switch elif (switch == '-n'): @@ -672,7 +694,7 @@ if __name__ == '__main__': vcs.copy(ops.source, op.target) else: raise FatalException("unknown op type %s" % op.op) - vcs.commit(event, committables) + vcs.commit(event, committables, roundtrip) vcs.checkout(checkout_mode) raise SystemExit, 0 except FatalException, e: diff --git a/rcs-fast-import.xml b/rcs-fast-import.xml index 9bd4729..a9d5184 100644 --- a/rcs-fast-import.xml +++ b/rcs-fast-import.xml @@ -23,6 +23,7 @@ -u -l -d + -p -n -V @@ -38,6 +39,10 @@ corresponding directory structure. will terminate with error rather than stepping on files that already exist in the current directory. +Normally, committer and author data are written into each comment as +RFC-822 mail headers, followed by a blank line. The -p option suppresses +this, producing a plain RCS comment. + With the -v option, emit a twirling-baton prompt during stream read, and more progress messages during RCS writes. @@ -65,7 +70,7 @@ differences near deleted, renamed, and copied files. Warning: All tag metadata other than the name will be lost. The date imputed to commits is from the author metadata if that exists, otherwise -from the committer metadata. Committer and author information is lost. +from the committer metadata. ERROR RETURNS -- 2.43.0