[Python-Dev] SSH problems getting into SourceForge's CVS?

Barry Warsaw barry at python.org
Thu Apr 29 10:26:17 EDT 2004


On Thu, 2004-04-29 at 10:20, Guido van Rossum wrote:
> When I tried "cvs update" today, I got an error from ssh:

Same thing happens to me when I use cvs.python.sf.net:/cvsroot/python
but not when I use cvs.sf.net:/cvsroot/python.  I think the latter is
the current approved way of connecting to the cvs repository (I've been
using that form with Mailman for a while and also have no problems
there).

I'll attach a nice little cvs_chroot script that Greg Ward sent me a
long time ago.  I'm sure he won't mind.  cd to your python directory and
type:

% cvs_chroot cvs.sf.net:/cvsroot/python

Then do a cvs up and all should be golden.

-Barry

-------------- next part --------------
#!/usr/bin/env python
"""cvs_chroot

Change the repository directory of an existing CVS working directory
(actually, a whole working tree).  Very handy when you're moving
repositories around and don't want to re-check-out the whole world; nearly
essential when you have a heavily customized working directory (lots of
uncommited changes, a forest of symlinks to make everything work, etc.).

Using "cvs_chroot" is simple: just put yourself in the top of the working
directory that needs to be adjusted, and run

    cvs_chroot <new_repository_root>

For example, if you've just moved your CVS repository from /home/cvs to
/cvs, then you would go to some working directory and run

    cvs_chroot /cvs

Or if you've just uploaded a local project to a remote CVS server, so that
all the net may share in its development, you might do something like this:

    cvs_chroot :pserver:anonymous at cvs.python.sourceforge.net:/cvsroot/python

(assuming that the project in question is Python, and the remote CVS server
is one of SourceForge's).  Of course, this also applies if you just happen
to have a working tree of the project in question -- you don't have to be
the one who uploaded it to SourceForge (or wherever).

If you're paranoid and/or curious, just run "chroot" with the "-n" option
-- it'll tell you what it would do, without actually doing anything.
"""

# created 2000/05/12 (Greg Ward)
# documented 2000/05/17

__revision__ = "$Id: cvs_chroot,v 2.3 2002/09/28 16:06:08 barry Exp $"

import sys, os, string
import getopt
import errno

USAGE = "usage: %s [-n] newroot"


"""If you're curious about how it works, here's the whole story:
"cvs_chroot" walks a directory tree, finding all CVS directories under it,
and adjusting the "Root" and "Repository" file in each CVS directory.  The
terminology is a bit wonky here (blame CVS), so pay attention: the "Root"
files contain the location of the repository -- ie. the directory named in
the CVSROOT environment variable or specified in the CVS "-d" option.  For
example, if you check something out as follows:

    CVSROOT=/home/cvs cvs checkout project

(or cvs -d /home/cvs checkout project; the two are equivalent), then every
"CVS/Root" file under "project" will contain the sole line "/home/cvs".
Every "CVS/Repository" file will contain the full path to the repository
directory for that particular working directory, or a path which is
relative to the root (CVS supports both forms).  For example, if "project"
is laid out like:

    project --+ src ---+ module1
              |        | module2
              | doc

then the file "project/src/CVS/Repository" would contain either
"/home/cvs/project/src" or "project/src", and the file
"project/src/module1/CVS/Repository" would contain either
"/home/cvs/project/src/module1" or "project/src/module1".

If you're dealing with a remote repository, then things are a bit more
complex: the "Root" files are still all the same and contain the value of
CVSROOT or the "-d" option when you checked the project out, but the
Repository files leave off the remote bit (everything up to the last
colon).  For example, if you checkout "project" from a remote server:

   cvs -d :pserver:anon at cvs.somewhere.net:/home/cvs checkout project

then the "CVS/Root" files under "project" will all contain
":pserver:anon at cvs.somewhere.net:/home/cvs", but the "CVS/Repository" files
will be the same as before -- i.e., they will start with "/home/cvs".  This
time, of course, that's "/home/cvs" on the host cvs.somewhere.net.

Thus, cvs_chroot has to do the following for each CVS directory it finds:
  * replace the contents of "Root" with the new repository root
  * chop off the old repository root fragment (directory only, no
    host information) from the Repository file, and replace
    it with the new repository root fragment

If cvs_chroot finds any inconsistencies in the Root or Repository files of
your working directory, it prints a warning and skips the afflicted
directory.  (This can happen if you have a subtree of your working tree
checked out differently, eg. from a different repository.  In that case,
you'll have to run "cvs_chroot" on each such oddball subtree.)
"""

def warn (msg):
    lines = string.split(msg, "\n")
    sys.stderr.write("warning: " + lines[0] + "\n")
    for l in lines[1:]:
        sys.stderr.write ("         " + l + "\n")

def find_cvs_dirs (start_dir):
    def callback(arg, directory, files):
        for file in files:
	    if os.path.isdir(file) and file == "CVS":
                arg.append(os.path.join(directory, file))
    dirs = []
    os.path.walk(start_dir, callback, dirs)
    return dirs

def split_root (root):
    lastcolon = string.rfind(root, ":")
    if lastcolon != -1:
        root_host = root[0:lastcolon]
        root_dir = root[lastcolon+1:]
    else:
        root_host = None
        root_dir = root
    return (root_host, root_dir)

def repos_filenames (dir):
    return (os.path.join(dir, "Root"),
            os.path.join(dir, "Repository"))

def read_repos (dir):
    (root_fn, repos_fn) = repos_filenames(dir)
    root = open(root_fn).readline()[0:-1]
    repos = open(repos_fn).readline()[0:-1]
    return (root, repos)

def write_repos (dir, root, repos):
    (root_fn, repos_fn) = repos_filenames(dir)
    root_bk = root_fn + "~"
    repos_bk = repos_fn + "~"
    if os.path.exists(root_bk):
        os.remove(root_bk)
    if os.path.exists(repos_bk):
        os.remove(repos_bk)
    try:
        os.rename(root_fn, root_bk)
        os.rename(repos_fn, repos_bk)
        open(root_fn, "w").write(root + "\n")
        open(repos_fn, "w").write(repos + "\n")
    except (IOError, os.error):
        try:
            os.rename(root_bk, root_fn)
            os.rename(repos_bk, repos_fn)
        except (IOError, os.error):
            pass
        

def main (prog, args):
    usage = USAGE % os.path.basename(prog)

    dry_run = None
    help = None
    try:
        (opts, args) = getopt.getopt(args, "nh")
        for (opt, _) in opts:
            if opt == "-n":
                dry_run = 1
            elif opt == "-h":
                help = 1
    except getopt.error, msg:
        raise SystemExit, usage + "\n" + str(msg)

    if help:
        print __doc__
        print usage
        sys.exit(0)

    if len(args) != 1:
        raise SystemExit, \
              usage + "\nWrong number of arguments"

    new_root = args[0]
    (new_root_host, new_root_dir) = split_root(new_root)

    (top_root, top_repos) = read_repos ("CVS")

    sys.stdout.write("Finding CVS directories..."); sys.stdout.flush()
    cvsdirs = find_cvs_dirs (".")
    sys.stdout.write("found %d of them\n" % len(cvsdirs))
    
    for dir in cvsdirs:
        try:
            (root, repos) = read_repos(dir)
        except IOError, e:
            if e.errno <> errno.ENOTDIR: raise
            continue
        (root_host, root_dir) = split_root(root)
        orig_repos = repos              # so we can tweak 'repos' but still
                                        # report the original value

        # The CVS/Root file must be consistent throughout the entire
        # working tree; skip any directory where it's not the same as
        # the top-level CVS/Root file.
        if root != top_root:
            warn(("root in %s (%s) doesn't match\n" 
                  "top-level root (%s) (skipping)")
                  % (dir, root, top_root))
            continue

        # Checking the Repository file needs to know if it, and the
        # top-level repository, are absolute or relative.
        repos_abs = (repos[0] == '/')
        top_repos_abs = (top_repos[0] == '/')

        # If the CVS/Repository file is absolute (which happens under
        # older versions of RCS -- at least 1.10.6 has relative
        # Repository files), then the prefix of the repository directory
        # must match the directory portion of the root.
        if repos_abs and repos[0:len(root_dir)] != root_dir:
            warn(("repository at %s (%s) not under "
                  "root dir (%s) (skipping)")
                 % (dir, repos, root_dir))
            continue

        # If the top-level repository is absolute, but the current one
        # is not, then force the current repository into compliance with
        # the top-level repository -- i.e. make it absolute.
        if top_repos_abs and not repos_abs:
            repos = root_dir + '/' + repos
            repos_abs = 1

        # Other way round: make the current repository relative to match
        # the top-level repository.
        elif repos_abs and not top_repos_abs:
            repos = repos[len(root_dir)+1:]
            repos_abs = 0

        # Now we can make sure that the current repository is valid,
        # ie. is some descendant of the top-level repository.
        if repos[0:len(top_repos)] != top_repos:
            warn(("repository at %s (%s) not under\n"
                  "top-level repository (%s) (skipping)")
                  % (dir, repos, top_repos))
            continue

        # Now, at last we can generate a new repository directory, which
        # is the point of the whole exercise.  It will be absolute if
        # the top-level repository (not necessarily the current
        # repository) is absolute, and relative if the top-level
        # repository is relative.
        if repos[0] == '/':
            new_repos = new_root_dir + repos[len(root_dir):]
        else:
            new_repos = repos

        print dir + ":"
        print "  root: %s -> %s" % (root, new_root)
        print "  repos: %s -> %s" % (orig_repos, new_repos)

        if not dry_run:
            write_repos (dir, new_root, new_repos)

    # for dir in cvsdirs
    
# main ()    


if __name__ == "__main__":
    (prog, args) = (sys.argv[0], sys.argv[1:])
    main (prog, args)


More information about the Python-Dev mailing list