]> thyrsus.com Git - sccs2rcs.git/commitdiff
Python translation isn't finished, but it's far enough along to check in.
authorEric S. Raymond <esr@thyrsus.com>
Fri, 13 Jul 2007 11:51:55 +0000 (11:51 +0000)
committerEric S. Raymond <esr@thyrsus.com>
Fri, 13 Jul 2007 11:51:55 +0000 (11:51 +0000)
sccs2rcs

index fd8a09c8b1d42f9291f2c07870a984c905efdc7e..adb60a05c3d5484f19220dc18e0870999e1b7a92 100755 (executable)
--- a/sccs2rcs
+++ b/sccs2rcs
@@ -1,19 +1,37 @@
-#!/bin/csh -f
+#!/usr/bin/env python
 #
-# Sccs2rcs is a script to convert an existing SCCS
-# history into an RCS history without losing any of
-# the information contained therein.
+# sccs2rcs is a script to convert an existing SCCS history into an RCS
+# history without losing any of the information contained therein.
 #
-# Things to note:
-#   + The -b (batch) option can be used to tell this script to run
-#     non-interactively.  In this mode, it assumes that you do not
-#     want to enter descriptions on initial checkins, nor change
-#     the default keyword substitution
+# This tool has a complicated history for its size.  It began life as a 
+# script in C-shell written by a hacker named Ken Cox in 1991.
+#
+# In 1993 I applied some minor fixes for newer RCS versions and it 
+# shipped as part of one of Rich Morin's Prime Time Freeware releases.
+#
+# At some undetermined time after 1993, Brian Berliner hacked Ken
+# Cox's original in a slightly different direction than mine, adding
+# more SCCS keyword translations and an improvement in dealing with 
+# branches.
+#
+# In 2002 I fixed it not to require the Berkeley sccs(1) front end.
+# In 2003 I folded in a minor bug fix from Roger Pilkey.
+# In 2007 I merged in the missing features from Brian Berliner's version
+# and sent it to the GNU RCS project hoping they'd take it off my hands.
+# 
+# Then it started to bother me that I was casting the thing loose in a
+# dying language, so I decided to port it to Python and clean it up.
+# I added error checking, added the comments-file processing, added
+# auto-checkout of RCS workfiles replacing SCCS ones, and removed
+# no-longer-needed options. Finally I made it able to parse tag data from
+# snapshots made by Emacs VC mode.
 #
-#   + The -u option will cause a copy of the file to be checked out
-#     after the conversion, so if you started with a workfile in place,
-#     you'll end up with one there.
+# Ken said:
+# "This file may be copied, processed, hacked, mutilated, and
+# even destroyed as long as you don't tell anyone you wrote it."
+# Those were more innocent days.  This version is released under MIT terms. 
 #
+# Things to note:
 #   + It will NOT delete or alter your ./SCCS history under any circumstances.
 #
 #   + Run in a directory where ./SCCS exists and where you can create ./RCS
 #     the date of a delta to be in sync, then run "sccs admin -z"
 #     on the thing.
 #
-#   + After everything finishes, ./SCCS will be moved to ./old-SCCS.
-#
-# This file may be copied, processed, hacked, mutilated, and
-# even destroyed as long as you don't tell anyone you wrote it.
-#
-# Ken Cox
-# Viewlogic Systems, Inc.
-# kenstir@viewlogic.com
-# ...!harvard!cg-atla!viewlog!kenstir
-#
 # 1.1: I added some portability fixes and the -u option.  More importantly,
 # I removed the dependencies on the BSD/SV `sccs' utility; this script
 # will now work on any system that has the base SCCS/RCS tools available.
 # of this script, the one distributed with CVS.  That and 4 SCCS 
 # keyword substitutions were we only thing it did that this didn't.
 #
+# 1.4: Python rewrite describe aboved.  Interface cleanup.  Auto-checkout
+# of RCS workfiles corresponding to SCCS workfiles present when started.
+#
 # Eric S. Raymond <esr@thyrsus.com>
 #
 # $Id$
 
+import os, sys, getopt, re, commands
 
-# Note: newer versions of RCS require century digits on the -d option
-# of ci.  Losing SCCS doesn't supply them.  Kluge around this.
-#
-set century = 19
-
-############################################################
-# Option handling
-#
-set nodesc = 0
-set nohack = 0
-set regen  = 0
-while ($#argv)
+# Here are the translations for SCCS % keywords to RCS-style $ keywords.
+# The quotes surround the dollar signs are to fool RCS and later version-
+# control systems when the script is checked in.  In the unlikely event
+# that you need more keyword translations, add them to the end of this list.
+# The ones this misses are %[ABCDFHLMPQRSTYZ]%; the script will issue
+# warnings if it sees them after translation.
+conversions = [
+    ('%W%[ \t]*%G%',                   "$""Id""$"),
+    ('%W%[ \t]*%E%',                   "$""Id""$"),
+    ('%W%',                            "$""Id""$"),
+    ('%Z%%M%[ \t]*%I%[ \t]*%G%',       "$""SunId""$"),
+    ('%Z%%M%[ \t]*%I%[ \t]*%E%',       "$""SunId""$"),
+    ('%M%[ \t]*%I%[ \t]*%G%',          "$""Id""$"),
+    ('%M%[ \t]*%I%[ \t]*%E%',          "$""Id""$"),
+    ('%M%',                            "$""RCSfile""$"),
+    ('%I%',                            "$""Revision""$"),
+    ('%G%',                            "$""Date""$"),
+    ('%E%',                            "$""Date""$"),
+    ('%U%',                            ""),
+    ]
+conversions = map(lambda (s, t): (re.compile(s), t), conversions)
 
-    if ($argv[1] == "-b") then
-       set nodesc = 1
-       set nohack = 1    
-    endif
+months = ("Jan","Feb","Mar","Apr","May","Jun",
+          "Jul","Aug","Sep","Oct","Nov","Dec")
 
-    if ($argv[1] == "-u") then
-       set regen = 1
-    endif
+def writeable(f):
+    return os.access(f, os.W_OK)
 
-    shift
-end
+def sccsfile(f):
+    return os.path.join("SCCS", "s." + f)
 
-############################################################
-# Error checking
-#
-if (! -w .) then
-    echo "Error: ./ not writeable by you."
-    exit 1
-endif
-if (! -d SCCS) then
-    echo "Error: ./SCCS directory not found."
-    exit 1
-endif
-foreach f (*)
-    if (-f SCCS/s.$f && -w $f) then
-       echo "Error: $f is out for edit...clean up before converting."
-       exit 1
-    endif
-end
-if (-d RCS) then
-    echo "Warning: RCS directory exists"
-    if (`ls -a RCS | wc -l` > 2) then
-        echo "Error: RCS directory not empty"
-        exit 1
-    endif
-else
-    mkdir RCS
-endif
+def progress(msg):
+    global logtext
+    logtext += "sccs2rcs: " + msg + "\n"
+    print msg
 
-set months = (Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
+def get_output(command):
+    global logtext
+    logtext += "$ " + command
+    (st, out) = commands.getstatusoutput(command)
+    logtext += out + "\n"
+    return out + "\n"
 
-# Remove all files for which there exist SCCS masters
-foreach f (*)
-    if (-f SCCS/s.$f) then
-       rm $f
-    endif
-end
+def die(msg):
+    print >>sys.stderr, "sccs2rcs: fatal error, ", msg
+    for filename in os.listdir("."):
+        if os.path.exists(sccsfile(filename)) and writeable(filename):
+            get_output("unget " + sccsfile(filename))
+    print >>sys.stderr, """\
+Danger!  Danger!
+Incomplete history in ./RCS -- remove it.
+Original unchanged history in ./SCCS.
+"""
+    if logtext:
+        print "Log follows:"
+        print logtext
+        print "Log ends here."
+    sys.exit(1)
 
-set logfile = /tmp/sccs2rcs_$$_log
-rm -f $logfile
-set tmpfile = /tmp/sccs2rcs_$$_tmp
-rm -f $tmpfile
-set initialfile = /tmp/sccs2rcs_$$_init
-echo "Initial revision" > $initialfile
-set sedfile = /tmp/sccs2rcs_$$_sed
-rm -f $sedfile
+def warn(msg):
+    print >>sys.stderr, "sccs2rcs: warning,", msg
 
-# the quotes surround the dollar signs are to fool RCS when I check in this script
-set sccs_keywords = (\
-    '%W%[      ]*%G%'\
-    '%W%[      ]*%E%'\
-    '%W%'\
-    '%Z%%M%[   ]*%I%[  ]*%G%'\
-    '%Z%%M%[   ]*%I%[  ]*%E%'\
-    '%M%[      ]*%I%[  ]*%G%'\
-    '%M%[      ]*%I%[  ]*%E%'\
-    '%M%'\
-    '%I%'\
-    '%G%'\
-    '%E%'\
-    '%U%')
-set rcs_keywords = (\
-    '$'Id'$'\
-    '$'Id'$'\
-    '$'Id'$'\
-    '$'SunId'$'\
-    '$'SunId'$'\
-    '$'Id'$'\
-    '$'Id'$'\
-    '$'RCSfile'$'\
-    '$'Revision'$'\
-    '$'Date'$'\
-    '$'Date'$'\
-    '')
+def get_output_or_die(command):
+    global logtext
+    logtext += "$ " + command
+    (st, out) = commands.getstatusoutput(command)
+    if st != 0:
+        die("'%s' failed" % command)
+    logtext += out + "\n"
+    return out + "\n"
 
+if __name__ == '__main__':
+    comments = None
+    (options, arguments) = getopt.getopt(sys.argv[1:], "c:")
+    for (switch, val) in options:
+        if switch == '-c':
+            comments = val
 
-############################################################
-# Get some answers from user
-#
-if ($nodesc == 0) then
-    echo ""
-    echo "Do you want to be prompted for a description of each"
-    echo "file as it is checked in to RCS initially?"
-    echo -n "(y = prompt for description, n = null description) [y]? "
-    set ans = $<
-    if ((_$ans == _n) || (_$ans == _N)) then
-       set nodesc = 1
-    endif
-endif
+    logtext = "" 
 
-if ($nohack == 0) then
-    echo ""
-    echo "The default keyword substitutions are as follows and are"
-    echo "applied in the order specified:"
-    set i = 1
-    while ($i <= $#sccs_keywords)
-#      echo '  '\"$sccs_keywords[$i]\"'        ==>     '\"$rcs_keywords[$i]\"
-       echo "  $sccs_keywords[$i]      ==>     $rcs_keywords[$i]"
-       @ i = $i + 1
-    end
-    echo ""
-    echo -n "Do you want to change them [n]? "
-    set ans = $<
-    if ((_$ans != _) && (_$ans != _n) && (_$ans != _N)) then
-       echo "You can't always get what you want."
-       echo "Edit this script file and change the variables:"
-       echo '    $sccs_keywords'
-       echo '    $rcs_keywords'
-    else
-       echo "Good idea."
-    endif
-endif
+    # Sanity checks on the directory
+    if not writeable("."):
+        die("./ is not writeable by you.")
+    elif not os.path.isdir("SCCS"):
+        die("no SCCS directory to convert.")
+    for filename in os.listdir("SCCS"):
+        if filename.startswith("s.") and writeable(filename):
+            die(filename + " is locked for edit...unlock before converting.")
+    if os.path.isdir("RCS"):
+        warn("RCS directory already exists.")
+        #if len(os.listdir("RCS")) > 2:        # Allow . and ..
+        #       die("RCS directory is nonempty.")
+    else:
+        try:
+            os.mkdir("RCS")
+        except OSError:
+            die("failed while attempting to create RCS directory.")
 
-# create the sed script
-set i = 1
-while ($i <= $#sccs_keywords)
-    echo "s,$sccs_keywords[$i],$rcs_keywords[$i],g" >> $sedfile
-    @ i = $i + 1
-end
+    # User may have supplied a comments file
+    initstrings = {} 
+    if comments:
+        for line in open(comments):
+            try:
+                line = line.strip()
+                if not line:   # ignore blank lines
+                    continue
+                (filename, initstring) = line.split(":")
+            except ValueError:
+                die("ill-formed line '%s' in comments file" % line)
+            if filename in initstrings:
+                die("duplicate filename in initstrings")
+            initstrings[filename] = initstrings
 
-mkdir old-SCCS
+    # If the CSSC package is installed, the SCCS commands might be here.
+    # This assumption works under Debian/Ubuntu and will be harmless elsewhere.
+    os.environ["PATH"] += ":/usr/lib/cssc"
 
-############################################################
-# Loop over every s-file in SCCS dir
-#
-foreach sfile (SCCS/s.*)
-    # get rid of the "s." at the beginning of the name
-    set file = `echo $sfile:t | sed -e "s/^..//"`
+    # Loop over every s-file in SCCS dir
+    for sfile in filter(lambda f: f.startswith("s."), os.listdir("SCCS")):
+        file = sfile[2:]
+        firsttime = True
 
-    # work on each rev of that file in ascending order
-    set firsttime = 1
-    foreach rev (`prs SCCS/s.$file | grep "^D " | awk '{print $2}' | tac`)
-        if ($status != 0) goto ERROR
+        # Extract a list of revisions from the master 
+        revisions = []
+        try:
+            for line in os.popen("prs " + sccsfile(file), "r"):
+                if line.startswith("D "):
+                    revisions.append(line.split()[1])
+        except OSError:
+            die("open of master file %s failed" % sfile)
+        except IndexError:
+            die("garbled revision line in master file " + sfile)
+        revisions.reverse()
 
-        # get file into current dir and get stats
-        set date   = (`prs -d":Dd: :Dm: :Dy: :T: :P:" -r$rev SCCS/s.$file`)
-       set day    = $date[1]
-       set month  = $date[2]
-       if ($date[3] < 10) then
-               set next_century = (`expr $century + 1`)        
-               set year   = ${next_century}${date[3]}
-       else
-               set year   = ${century}${date[3]}
-       endif
-       set time   = $date[4]
-       set author = $date[5]
-       set date = "$day $months[$month] $year $time"
+        # Now use prs to run through the revision list extracting data
+        progress("%s revisions: %s\n" % (file, ", ".join(revisions)))
+        for rev in revisions:
+            cmd = 'prs -d":Dd: :Dm: :Dy: :T: :P:" -r%s SCCS/s.%s' % (rev, file)
+            date = get_output_or_die(cmd)
+            try:
+                (day, month, year, tod, author) = date.split()
+            except ValueError:
+                die("prs output %s from '%s'was ill-formed" % (`date`, cmd))
 
-        echo ""
-        echo "==> file $file, rev=$rev, date=$date, author=$author"
-        get -e -r$rev SCCS/s.$file >>& $logfile
-        if ($status != 0) goto ERROR
-        echo checked out of SCCS
+            # We assume here that no SCCS archives were made before 1970.
+            # If you're converting an SCCS archive made after 2070, you lose.
+            if int(year) < 70:
+                year = "20" + year
+            else:
+                year = "19" + year
 
-        # add RCS keywords in place of SCCS keywords
-        sed -f $sedfile $file > $tmpfile
-        if ($status != 0) goto ERROR
-        echo performed keyword substitutions
-        cp $tmpfile $file
+            rcsdate = day + " " + months[int(month)] + " " + year + " " + tod
+            progress("==> file %s, rev=%s, date=%s, author=%s" \
+                  % (file, rev, rcsdate, author)) 
+            get_output_or_die("get -e -r%s SCCS/s.%s" % (rev, file))
+            print "checked out of SCCS"
 
-        # check file into RCS
-        if ($firsttime) then
-            set firsttime = 0
-            if ($nodesc == 1) then
-                ci -f -r$rev -d"$date" -w$author -t/dev/null $file <$initialfile >>& $logfile
-                if ($status != 0) goto ERROR
-                echo initial rev checked into RCS without description
-            else
-                echo ""
-                echo Enter a brief description of the file $file \(end w/ Ctrl-D\):
-                cat > $tmpfile
-                ci -f -r$rev -d"$date" -w$author -t$tmpfile $file <$initialfile >>& $logfile
-                if ($status != 0) goto ERROR
-                echo initial rev checked into RCS
-            endif
-        else
-            # get RCS lock
-           set lckrev = `echo $rev | sed -e 's/\.[0-9]*$//'`
-           if ("$lckrev" =~ [0-9]*.*) then
-               # need to lock the brach -- it is OK if the lock fails
-               rcs -l$lckrev $file >>& $logfile
-           else
-               # need to lock the trunk -- must succeed
-               rcs -l $file >>& $logfile
-               if ($status != 0) goto ERROR
-           endif
-            echo got lock
-            prs -r$rev SCCS/s.$file | grep "." > $tmpfile
-            # it's OK if grep fails here and gives status == 1
-            # put the delta message in $tmpfile
-            ed $tmpfile >>& $logfile <<EOF
-/COMMENTS
-1,.d
-w
-q
-EOF
-            ci -f -r$rev -d"$date" -w$author $file < $tmpfile >>& $logfile
-            if ($status != 0) goto ERROR
-            echo checked into RCS
-        endif
-        unget SCCS/s.$file >>& $logfile
-        if ($status != 0) goto ERROR
-    end
-    rm -f $file
-    mv $sfile old-SCCS
-    if ($regen == 1) then
-       co $file
-    endif
-end
+            # replace SCCS keywords with RCS keywords
+            try:
+                rfp = open(file, "r")
+                filetext = rfp.read()
+                rfp.close()
+                for (regexp, rcskey) in conversions:
+                    filetext = regexp.sub(rcskey, filetext)
+                wfp = open(file, "w")
+                wfp.write(filetext)
+                wfp.close()
+            except OSError:
+                    die("keyword substitution failed")
+            progress("performed keyword substitutions")
 
+            # check file into RCS
+            if firsttime:
+                firsttime = False
+                if file in initstrings:
+                    cmd = 'ci -f -r%s -d"%s" -w%s -t-%s %s' \
+                          % (rev, date, author, initstrings[file], file)
+                else:
+                    if initstrings:
+                        warn("no description for %s in comments file" % file)
+                    cmd = 'ci -f -r%s -d"%s" -w%s -t/dev/null %s' \
+                          % (rev, date, author, file)
+                cmd = "echo 'Initial revision.' | " + cmd
+                get_output_or_die(cmd)
+            else:
+                # get RCS lock    
+                revparts = rev.split(".")
+                lckrev = ".".join(revparts[:-1])
+                if re.match(r"[0-9]+\."):
+                    # need to lock the branch -- it is OK if the lock fails
+                    get_output("rcs -l%s %s" % (lckrev, file))
+                    progress("got branch lock")
+                else:
+                    # need to lock the trunk -- must succeed
+                    get_output_or_die("rcs -l %s" % (file,))
+                    progress("got trunk lock")
+                # Extract the delta message for this revision
+                deltamsg = get_output_or_die("prs -r$rev SCCS/s.$file")
+                deltalines = deltamsg.split("\n")
+                while deltalines:
+                    deltalines.pop(0)
+                    if deltalines[0].startswith("COMMENTS"):
+                        break
+                deltamsg = deltalines.join("\n")
+                cmd = 'ci -f -r%s -d"%s" -w%s %s' % (rev, date, author, file)
+                # FIXME: output of this command isn't logged
+                progress("$ " + cmd)
+                try:
+                    cfp = os.popen(cmd, "w")
+                    cfp.write(deltamsg)
+                    cfp.close()
+                except OSError:
+                    die("RCS checkin of delta failed")
+        # We're done
+       logtext += get_output_or_die("unget " + sccsfile(file))
 
-############################################################
-# Clean up
-#
-echo cleaning up...
-rm -f $tmpfile $initialfile $sedfile
-echo ===================================================
-echo "Conversion of `pwd` OK"
-echo ===================================================
-exit 0
+    # Replace all files for which there exist SCCS masters,
+    progress("replacing workfiles")
+    for filename in os.listdir("."):
+        if os.exists(sccsfile(filename)):
+            os.remove(filename)
+            os.system("co " + file)
+    print progress("done.")
 
-ERROR:
-foreach f (*)
-    if (-f SCCS/s.$f && -w $f) then
-       unget SCCS/s.$f
-    endif
-end
-echo ""
-echo ""
-echo Danger\!  Danger\!
-echo Some command exited with a non-zero exit status.
-echo Log file exists in $logfile.
-echo ""
-echo Incomplete history in ./RCS -- remove it
-echo Original unchanged history in ./SCCS
-exit 1
+# sccs2rcs ends here