fu: Close-up of Fu, bringing a scoop of water to her mouth (Default)
fu ([personal profile] fu) wrote in [site community profile] changelog2012-05-01 07:59 am

[dw-free] move vcv to local repo. Header is updated to note where it came from

[commit: http://hg.dwscoalition.org/dw-free/rev/08ee07f4ef06]

move vcv to local repo. Header is updated to note where it came from

Files modified:
  • bin/vcv
  • cvs/multicvs.conf
--------------------------------------------------------------------------------
diff -r 523e5dd6e9b6 -r 08ee07f4ef06 bin/vcv
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/vcv	Tue May 01 16:00:07 2012 +0800
@@ -0,0 +1,858 @@
+#!/usr/bin/perl
+#
+# This code was originally imported from:
+#
+#     http://code.sixapart.com/svn/vcv/trunk
+#
+# We have copied this module locally to modify it for use in the Dreamwidth project.
+# Original copyright is presumably owned by Six Apart, Ltd.  Modifications are
+# copyright (C) 2008-2012 by Dreamwidth Studios, LLC.
+#
+
+use strict;
+use Getopt::Long;
+use Cwd;
+
+$| = 1;
+
+my $help = 0;
+my $sync = 0;
+my $diff = 0;
+my $cvsonly = 0;
+my $liveonly = 0;
+my $newonly = 0;
+my $init = 0;
+my $conf;
+my $opt_update;
+my $opt_stable;
+my $opt_justfiles;
+my $opt_checkout;
+my $opt_print_branches;
+my $opt_print_current_branches;
+my $opt_print_vars;
+my $opt_print_repos;
+my $opt_ignore_space;
+my $these_flag;
+my $opt_livelist;
+my $opt_which;
+my $opt_all;
+my $opt_map;
+my $opt_headserver;  # blank or "host:port", connect and returns lines of "repo\s+HEADREVNUM\n";
+
+my $opt_svk = 0;
+
+exit 1 unless GetOptions('conf=s' => \$conf,
+                         'help|h' => \$help,
+                         's|sync' => \$sync,
+                         'diff' => \$diff,
+                         'cvsonly|c' => \$cvsonly,
+                         'liveonly|l' => \$liveonly,
+                         'newonly|n' => \$newonly,
+                         'init' => \$init,
+                         'update' => \$opt_update,
+                         'justfiles|1' => \$opt_justfiles,
+                         'print-branches|pb' => \$opt_print_branches,
+                         'print-current-branches|pcb' => \$opt_print_current_branches,
+                         'print-vars|pv' => \$opt_print_vars,
+                         'print-repos|pr' => \$opt_print_repos,
+                         'no-space-changes|b|w' => \$opt_ignore_space,
+                         'these|t' => \$these_flag,
+                         'all' => \$opt_all,  # cancels --these
+                         'livelist|ll' => \$opt_livelist,
+                         'checkout|co' => \$opt_checkout,
+                         'which' => \$opt_which,
+                         'map'   => \$opt_map,
+                         'headserver|hs=s' => \$opt_headserver,
+                         'svk=s'   => \$opt_svk,
+                         'stable'  => \$opt_stable,
+                         );
+
+
+
+if ($help or not defined $conf) {
+    die "Usage: vcv --conf=/path/to/vcv.conf [opts] [files]\n" .
+        "    --help          Get this help\n" .
+        "    --sync          Put files where they need to go.\n" .
+        "                    All files, unless you specify which ones.\n".
+        "    --diff          Show diffs of changed files.\n".
+        "    --cvsonly       Don't consider files changed in live dirs.\n".
+        "    --liveonly      Don't consider files changed in the CVS dirs.\n".
+        "    --newonly       Only show files that seem to be new.\n".
+        "    --init          Copy all files from cvs to main, unconditionally.\n" .
+        "    --update        Updates files in the CVS dirs from the cvs repositories.\n".
+        "    --stable        Restrict --update to the most recent stable DW release.\n".
+        "    --justfiles -1  Only output files, not the old -> new arrow. (good for xargs)\n".
+        "    --livelist -ll  Output the list of all accounted-for files.\n".
+        "    --which         Output the source of the given file\n".
+        "    --these -t      Refuse to --sync if no files are specified.\n".
+        "    --map           Like --livelist and --which combined.\n".
+        "    --all           Overrides --these, allowing --sync to run with no args.\n".
+        "    --print-current-branches -pcb    Print repositories and their current branches\n".
+        "    --print-branches -pb     Print repositories and their branches.\n".
+        "    --print-repos -pr        Print repositories and their resource URLs.\n".
+        "    --print-vars -pv         Print configuration variables specified in .conf files.\n".
+        "    --no-space-changes -b    Do not display whitespace differences.\n";
+}
+
+if ($init) {
+    $sync = 1;
+    die "Can't set --liveonly or --cvsonly with --init\n"
+        if $cvsonly or $liveonly;
+    $diff = 0;
+}
+
+unless (-e $conf) {
+    die "Specified conf file doesn't exist: $conf\n";
+}
+
+my %REPO_URLS     = (); # repo => url
+my %REPO_BRANCHES = (); # repo => [ branch1, branch2, ... ]
+my %REPO_REV      = (); # repo => revnum
+
+my %CACHE = ();         # dom => { key => val }
+
+our $SVK;
+
+if($opt_svk || $ENV{VCV_SVK}) {
+    $SVK = VCV::SVK->new;
+    $SVK->set_depot($ENV{VCV_SVK} || $opt_svk);
+}
+
+my ($DIR_LIVE, $DIR_CVS);
+
+my @paths;
+my $headcache;  # undef or hashref of { repo => latest rev }
+
+my $read_conf = sub
+{
+    my $file = shift;
+    my $main = shift;
+
+    my $repopattern = '\(([\w\-]+)\)\s*=\s*(\S+)\s*(?:@([a-z0-9]+))?';
+    open (C, $file) or die "Error opening conf file.\n";
+    while (<C>)
+    {
+        s/\#.*//;
+        next unless /\S/;
+        s/^\s+//;
+        s/\s+$//;
+        s/\$(\w+)/$ENV{$1} or die "Environment variable \$$1 not set.\n"/ge;
+
+        if (/(\w+)\s*=\s*(.+)/) {
+            my ($k, $v) = ($1, $2);
+            unless ($main) {
+                die "Included config files can't set variables such as $k.\n";
+            }
+            if ($k eq "LIVEDIR") { $DIR_LIVE = $v; }
+            elsif ($k eq "CVSDIR") { $DIR_CVS = $v; }
+            else { die "Unknown option $k = $v\n"; }
+            next;
+        }
+
+        if (/^SVN$repopattern/) {
+            die "SVN declaration without CVSDIR declared\n" unless $DIR_CVS;
+            my ($ldir, $src, $rev) = ($1, $2, $3);
+
+            my @urlparts = split(/\//, $src);
+            my $url = join("/", @urlparts[0..$#urlparts-1]);
+            $REPO_URLS{$ldir} = $url;
+            $REPO_REV{$ldir}  = $rev;
+
+            my $full_ldir = "$DIR_CVS/$ldir";
+            my $full_ldir_svn = "$DIR_CVS/$ldir/.svn";
+            if ($SVK) {
+                $SVK->add_repo($ldir, $src);
+                unless (-d $full_ldir) {
+                    unless ($SVK->check_repo($ldir)) {
+                        unless ($opt_checkout) {
+                            die "SVK directory '$ldir' doesn't exist under $DIR_CVS. Re-run vcv with --checkout for me to automaticall sync it and check it out.\n";
+                        }
+                        $SVK->checkout_repo($ldir);
+                    }
+                }
+                next;
+            }
+
+            unless (-d $full_ldir && -d $full_ldir_svn) {
+                unless ($opt_checkout) {
+                    die "Subversion directory '$ldir' doesn't exist under $DIR_CVS.  " .
+                        "Re-run vcv with --checkout for me to automatically get it for you.\n";
+                }
+                if (-d $full_ldir) {
+                    if (-d "$full_ldir/CVS") {
+                        rename $full_ldir, "$full_ldir-oldcvs"
+                            or die "Failed renaming $full_ldir to oldcvs-$full_ldir: $!";
+                    } else {
+                        die "You have a $full_ldir directory but it's not CVS or Subversion; confused.";
+                    }
+                }
+                my @opts;
+                if ($rev) { push @opts, "-r", $rev; }
+                system("svn", "co", @opts, $src, $full_ldir) and
+                    die "Failed to run svn:  is it installed?";
+            }
+
+        } elsif (/^HG$repopattern/) {
+            die "HG declaration without CVSDIR declared\n" unless $DIR_CVS;
+            my ($ldir, $src, $rev) = ($1, $2, $3);
+
+            # stable check
+            if ( defined $rev && $rev eq 'stable' ) {
+                undef $rev unless $opt_stable;
+            }
+
+            my @urlparts = split(/\//, $src);
+            my $url = join("/", @urlparts[0..$#urlparts-1]);
+            $REPO_URLS{$ldir} = $url;
+            $REPO_REV{$ldir}  = $rev;
+
+            my $full_ldir = "$DIR_CVS/$ldir";
+            my $full_ldir_svn = "$DIR_CVS/$ldir/.hg";
+
+            unless (-d $full_ldir && -d $full_ldir_svn) {
+                unless ($opt_checkout) {
+                    die "Mercurial directory '$ldir' doesn't exist under $DIR_CVS.  " .
+                        "Re-run vcv with --checkout for me to automatically get it for you.\n";
+                }
+                my @opts;
+                if ($rev) { push @opts, "-r", $rev; }
+                system("hg", "clone", @opts, $src, $full_ldir) and
+                    die "Failed to run hg:  is it installed?";
+            }
+        } elsif(/^GIT$repopattern/) {
+            die "GIT declaration without CVSDIR declared\n" unless $DIR_CVS;
+            my ( $ldir, $src, $rev ) = ( $1, $2, $3 );
+
+            my @urlparts = split(/\//, $src);
+            my $url = join("/", @urlparts[0..$#urlparts-1]);
+            $REPO_URLS{$ldir} = $url;
+            $REPO_REV{$ldir}  = $rev;
+
+            my $full_ldir = "$DIR_CVS/$ldir";
+            my $full_ldir_svn = "$DIR_CVS/$ldir/.git";
+
+            unless (-d $full_ldir && -d $full_ldir_svn) {
+                unless ($opt_checkout) {
+                    die "Git directory '$ldir' doesn't exist under $DIR_CVS.  " .
+                        "Re-run vcv with --checkout for me to automatically get it for you.\n";
+                }
+                my @opts;
+                if ($rev) { push @opts, "-r", $rev; }
+                system("git", "clone", @opts, $src, $full_ldir) and
+                    die "Failed to run git:  is it installed?";
+            }
+        } elsif (/(\S+)\s+(.+)/) {
+            my ($from, $to) = ($1, $2);
+            my $maybe = 0;
+            if ($from =~ s/\?$//) { $maybe = 1; }
+            push @paths, {
+                'from' => $from,
+                'to' => $to,
+                'maybe' => $maybe,
+            };
+
+        } else {
+            die "Bogus line: $_\n";
+        }
+    }
+    close C;
+};
+$read_conf->($conf, 1);
+
+my $origcwd = getcwd;
+chdir( $DIR_LIVE ) or die "Could not change directory to $DIR_LIVE";
+
+if ($conf =~ /^(.+)(multicvs\.conf)$/) {
+    my $localconf = "$1multicvs-local.conf";
+    $read_conf->($localconf) if -e $localconf;
+
+    my $privconf = "$1multicvs-private.conf";
+    $read_conf->($privconf) if -e $privconf;
+}
+
+# after reading config, but before iterating over file paths, perform
+# any requested branch management, which is generally exclusive of
+# file operations
+
+if ($opt_print_vars) {
+    print_vars();
+    exit 0;
+}
+
+if ($opt_print_repos) {
+    print_repos();
+    exit 0;
+}
+
+
+# did the user ask for a list of branches to be printed?
+if ($opt_print_branches) {
+    print_branches();
+    exit 0;
+}
+
+if ($opt_print_current_branches) {
+    print_current_branches();
+    exit 0;
+}
+
+# now config has been read, need to iterate over files in registered
+# paths and do any copying
+
+my %cvspath;  # live path -> cvs path
+my %have_updated;
+my %newfile_dirs; # directories to scan for new files
+
+foreach my $p (@paths)
+{
+    unless (-e "$DIR_CVS/$p->{'from'}") {
+        warn "WARNING: $p->{'from'} doesn't exist under $DIR_CVS\n"
+            unless $p->{'maybe'};
+        next;
+    }
+
+    if ($opt_update) {
+        my $root = $p->{'from'};
+        $root =~ s!/.*!!;
+        my $dir = "$DIR_CVS/$root";
+
+        if (-l $dir) {
+            my $to = readlink $dir;
+            if ($to && $to =~ /^[\w\-]+$/) {
+                $root = $to;
+                $dir = "$DIR_CVS/$root";
+            }
+        }
+
+        if (-d $dir && ! $have_updated{$dir}) {
+            chdir $dir or die "Can't cd to $dir\n";
+            my $extra = $REPO_REV{$root} ? "(to r$REPO_REV{$root}) " : "";
+            print "Updating CVS dir $dir '$root' $extra...\n";
+            if (-d ".svn") {
+                my $svninfo = `svn info`;
+                $svninfo =~ /^Revision: (\d+)/m;
+                my $ourrev = $1 || undef;
+                unless (at_wanted_rev($root, $ourrev)) {
+                    my @opts;
+                    if ($REPO_REV{$root}) { push @opts, "-r", $REPO_REV{$root}; }
+                    system("svn", "update", @opts)
+                        and die "Failed to update svn for: $dir\n";
+                }
+            } elsif (-d "CVSA") {
+                system("cvs", "-q", "update", "-dP")
+                    and die "Failed to update cvs for: $dir\n";
+            } elsif (-d ".hg" ) {
+                my @opts;
+                push @opts, "-r", $REPO_REV{$root} if $REPO_REV{$root};
+                system("hg", "pull");
+                system("hg", "update", @opts);
+            } elsif (-d ".git" ) {
+                system("git", "pull");
+            }elsif ($SVK) {
+                my $info = `svk info $dir`;
+                $info =~ /Mirrored From(.*)Rev. (\d+)/;
+                my $ourrev = $2 || undef;
+                unless (svn_rev_is_latest($root, $ourrev)) {
+                    system("svk", "pull", $dir);
+                    $SVK->update_repo($dir);
+                };
+            }
+
+            $have_updated{$dir} = 1;
+        }
+    }
+
+    if (-f "$DIR_CVS/$p->{'from'}") {
+        $cvspath{$p->{'to'}} = $p->{'from'};
+        next;
+    }
+
+    $p->{'to'} =~ s!/$!!;
+    my $to_prefix = "$p->{'to'}/";
+    $to_prefix =~ s!^\./!!;
+
+    my @dirs = ($p->{'from'});
+    while (@dirs)
+    {
+        my $dir = shift @dirs;
+        my $fulldir = "$DIR_CVS/$dir";
+
+        opendir (MD, $fulldir) or die "Can't open $fulldir.";
+        while (my $file = readdir(MD)) {
+            next if $file =~ /~$/;     # ignore emacs files
+            next if $file =~ /^\.\#/;  # ignore CVS archived versions
+            next if $file =~ /\bCVS\b/;
+            next if $file eq ".svn";
+            next if $file eq ".hg";
+            next if $file eq ".git";
+            next if $file eq 'var';
+            next if $file eq "." or $file eq "..";
+            if (-d "$fulldir/$file") {
+                next if $file eq "blib";
+                unshift @dirs, "$dir/$file";
+            } elsif (-f "$fulldir/$file") {
+                my $to = "$dir/$file";
+                $to =~ s!^$p->{'from'}/!!;
+
+                my $fn = "$to_prefix$to";
+                $cvspath{$fn} = "$dir/$file";
+
+                $fn =~ s#/[^/]+$##;
+                $newfile_dirs{$fn} = 1;
+            }
+        }
+        close MD;
+    }
+}
+
+# If the user has specified that there must be arguments, require @ARGV to
+# contain something.
+die "These what?\n\nWith --these specified, you must provide at least one file to sync.\n"
+    if ($these_flag && ! $opt_all) && $sync && !@ARGV;
+
+if ($opt_which && @ARGV != 1) {
+    die "--which requires exactly one argument";
+}
+
+my @files = scalar(@ARGV) ? @ARGV : sort keys %cvspath;
+foreach my $relfile (@files)
+{
+    my $status;
+    next unless exists $cvspath{$relfile};
+    my $root = $cvspath{$relfile};
+    $root =~ s!/.*!!;
+
+    my ($from, $to);  # if set, do action (diff and/or sync)
+
+    my $lfile = "$DIR_LIVE/$relfile";
+    my $cfile = "$DIR_CVS/$cvspath{$relfile}";
+
+    if ($opt_map) {
+        print "$relfile\t$cvspath{$relfile}\n";
+        next;
+    }
+
+    if ($opt_which) {
+        print "$cvspath{$relfile}\n";
+        next;
+    }
+
+    if ($opt_livelist) {
+        print "$relfile\n";
+        next;
+    }
+
+    if ($init) {
+        $status = "main <- $root";
+        ($from, $to) = ($cfile, $lfile);
+    } else {
+        my $ltime = mtime($lfile);
+        my $ctime = mtime($cfile);
+        next if $ltime == $ctime;
+        if ($ltime > $ctime && ! $cvsonly && ! $newonly) {
+            $status = "main -> $root";
+            ($from, $to) = ($lfile, $cfile);
+        }
+        if ($ctime > $ltime && ! $liveonly && ! $newonly) {
+            $status = "main <- $root";
+            ($from, $to) = ($cfile, $lfile);
+        }
+    }
+
+    next unless $status;
+
+    my $the_diff;
+    if ($diff && -e $from && -e $to) {
+        my $opt = "";
+        $opt = '-b' if $opt_ignore_space;
+        $the_diff = `diff -u $opt $to $from`; # getting from destination to source
+        if ($the_diff) {
+            # fix the -p level to be -p0
+            my $slashes = ($DIR_LIVE =~ tr!/!/!);
+            $the_diff =~ s/((^|\n)[\-\+]{3,3} )\/([^\/]+?\/){$slashes,$slashes}/$1/g;
+        } else {
+            # don't touch the files that don't have a diff if we're ignoring spaces
+            # as there might really be one and we just don't see it
+            next if $opt_ignore_space;
+
+            # no real change (just touched/copied?), so copy
+            # cvs one on top to fix times up.
+            copy($from, $to);
+            next;
+        }
+    }
+    if ($sync) {
+        make_dirs($relfile);
+        copy($from, $to);
+    }
+
+    if ($opt_justfiles) {
+        print "$relfile\n";
+    } else {
+        printf "%-25s %s\n", $status, $relfile;
+        print $the_diff;
+    }
+}
+
+# now print out new files that don't seem to exist in CVS anywhere
+my %checked;
+my @dirs = sort keys %newfile_dirs;
+@dirs = () if $cvsonly || $liveonly || $sync;
+while ( @dirs ) {
+    my $path = shift @dirs;
+
+    next if $path eq 'cvs' || $path eq 'etc' || $path eq 'logs';
+    next if $checked{$path}++ > 0;
+    next unless -d $path;
+
+    opendir (MD, $path) or die "Can't open $path.";
+    while (my $file = readdir(MD)) {
+        next if $file =~ /~$/;     # ignore emacs files
+        next if $file =~ /^\.\#/;  # ignore CVS archived versions
+        next if $file =~ /\bCVS\b/;
+        next if $file eq ".svn";
+        next if $file eq ".hg";
+        next if $file eq ".git";
+        next if $file eq "." or $file eq "..";
+        next if $file =~ /\.(?:rej|orig|swp)$/;
+
+        next if $cvspath{"$path/$file"};
+
+        if (-d "$path/$file") {
+            next if $file eq "blib";
+            unshift @dirs, "$path/$file";
+        } elsif (-f "$path/$file") {
+
+            next if @ARGV && ! grep { "$path/$file" eq $_ } @ARGV;
+
+            if ( $opt_justfiles ) {
+                print "$path/$file\n";
+            } else {
+                printf "%-25s %s\n", "main -> new-file", "$path/$file";
+            }
+
+            if ( $diff ) {
+                my $opt = $opt_ignore_space ? '-b' : '';
+                my $the_diff = `diff -u $opt /dev/null $path/$file`;
+                print $the_diff;
+            }
+        }
+    }
+}
+
+chdir( $origcwd );
+
+sub read_cache {
+    return 1 if %CACHE;
+
+    my $file = "$DIR_CVS/.vcv_cache";
+    unless (-e $file) {
+        system("touch", $file);
+    }
+
+    open my $fh, "<$file"
+        or die "unable to open $file";
+
+    while (my $line = <$fh>) {
+        next if $line =~ /^\#/;
+
+        my ($dom_key, $val) = split /\s+/, $line;
+        $CACHE{"$dom_key"} = $val;
+    }
+
+    # set a key in %CACHE even if the file had no data
+    # to indicate that we've already tried to read
+    $CACHE{_read_time} = time();
+
+    return close $fh ? 1 : 0;
+}
+
+sub write_cache {
+    open my $fh, ">$DIR_CVS/.vcv_cache"
+        or die "unable to open $DIR_CVS/.vcv_cache";
+
+    foreach my $key (sort keys %CACHE) {
+        next if $key =~ /^_/;
+        print $fh "$key $CACHE{$key}\n";
+    }
+
+    return close $fh ? 1 : 0;
+}
+
+sub get_cache {
+    my ($dom, @keys) = @_;
+
+    read_cache();
+
+    # only one key requested?
+    return $CACHE{"$dom-$keys[0]"} if @keys == 1;
+
+    # otherwise, return a hashref of k => v
+    my %ret = ();
+    foreach (@keys) {
+        $ret{$_} = $CACHE{"$dom-$_"} if exists $CACHE{"$dom-$_"};
+    }
+
+    return \%ret;
+}
+
+sub set_cache {
+    my $dom = shift;
+
+    while (my ($key, $val) = splice(@_, 0, 2)) {
+        $CACHE{"$dom-$key"} = $val;
+    }
+
+    write_cache();
+    return 1;
+}
+
+sub at_wanted_rev {
+    my ($repo, $have) = @_;
+    return 0 unless $opt_headserver;
+
+    my $wanted = $REPO_REV{$repo} || svn_latest_rev($repo);
+    return $wanted == $have;
+}
+
+sub svn_rev_is_latest {
+    my ($repo, $have) = @_;
+    return 0 unless $opt_headserver;
+
+    return svn_latest_rev($repo) == $have;
+}
+
+sub svn_latest_rev {
+    my $repo = shift;
+    return 0 unless $opt_headserver;
+
+    unless ($headcache) {
+        require IO::Socket::INET;
+        $headcache = {};
+        my $sock = IO::Socket::INET->new(PeerAddr => $opt_headserver)
+            or return 0;
+        while (<$sock>) {
+            /^(\S+)\s+(\d+)/ or next;
+            $headcache->{$1} = $2;
+        }
+    }
+
+    return $headcache->{$repo};
+}
+
+# returns { repo => [ branch1, branch2, ... ] }
+sub get_branches {
+    my %repo_cache_rev = (); # repo => rev num
+
+    # make sure %REPO_BRANCHES is populated from cache
+    my $cache = get_cache("repo_branches", keys %REPO_URLS);
+    while (my ($repo, $branch_info) = each %$cache) {
+        $REPO_BRANCHES{$repo} ||= [];
+        next unless $branch_info;
+
+        my ($rev, @branches) = split(/\s*,\s*/, $branch_info);
+        $repo_cache_rev{$repo} = $rev;
+
+        push @{$REPO_BRANCHES{$repo}}, @branches;
+    }
+
+    # now loop and verify each branch according to revision time
+    my $dirty;
+    foreach my $repo (sort keys %REPO_URLS) {
+
+        my $dir = "$DIR_CVS/$repo";
+        chdir $dir or die "Can't cd to $dir\n";
+
+        die "Branching functionality requires SVN.\n"
+            unless -d ".svn";
+
+        # when was this cache made?  is it still the latest revision?
+        my $ourrev = $repo_cache_rev{$repo};
+        unless (svn_rev_is_latest($repo, $ourrev)) {
+            my $rv = `svn ls $REPO_URLS{$repo} $REPO_URLS{$repo}/branches 2>&1`;
+            $rv =~ s/^\s*svn:.+\n//g;
+
+            @{$REPO_BRANCHES{$repo}} =
+                (grep { ! /^(branches|tags)$/ }
+                 grep { s/\/$// }
+                 split /\s+/, $rv);
+
+            $dirty = 1;
+        }
+
+        # this repo's branch information is already up to date
+    }
+
+    # if anything was updated, update the file cache
+    if ($dirty) {
+        set_cache("repo_branches",
+                  map { $_ => join(",", svn_latest_rev($_), @{$REPO_BRANCHES{$_}}) }
+                  keys %REPO_BRANCHES);
+    }
+
+    return \%REPO_BRANCHES;
+}
+
+sub print_current_branches {
+    foreach my $repo (sort keys %REPO_URLS) {
+        my $dir = "$DIR_CVS/$repo";
+        chdir $dir or die "Can't cd to $dir\n";
+        die "Branching functionality requires SVN.\n"
+            unless -d ".svn";
+
+        my @lines = `svn info`;
+        chomp @lines;
+
+        foreach my $line (@lines) {
+            next unless $line =~ /^URL:\s+(\S+)\s*$/;
+            my $url = $1;
+            my $branch = (split("/", $url))[-1];
+            printf "%-25s %2s\n", $repo, $branch;
+        }
+    }
+}
+
+sub print_branches {
+    my $branches = get_branches() || {};
+    foreach my $repo (sort keys %$branches) {
+        foreach my $branch (@{$branches->{$repo}}) {
+            printf "%-25s %2s\n", $repo, $branch;
+        }
+    }
+}
+
+sub print_vars {
+
+    # to be extended later as new vars are added
+    my %conf = (
+                DIR_LIVE => \$DIR_LIVE,
+                DIR_CVS  => \$DIR_CVS,
+                );
+
+    foreach my $k (@_ ? @_ : sort keys %conf) {
+        my $v = $conf{$k};
+
+        printf "%-25s %s\n", $k, ref $v eq 'ARRAY' ? join(", ", @$v) : $$v;
+    }
+}
+
+sub print_repos {
+    my @repos = shift || sort keys %REPO_URLS;
+
+    foreach my $repo (@repos) {
+        printf "%-25s %s\n", $repo, $REPO_URLS{$repo};
+    }
+}
+
+sub mtime
+{
+    my $file = shift;
+    return (stat($file))[9];
+}
+
+my %MADE_DIR;
+sub make_dirs
+{
+    my $file = shift;
+    return 1 unless $file =~ s!/[^/]*$!!;
+    return 1 if $MADE_DIR{$file};
+    my @dirs = split(m!/!, $file);
+    for (my $i=0; $i<scalar(@dirs); $i++) {
+        my $sd = join("/", @dirs[0..$i]);
+        my $makedir = "$DIR_LIVE/$sd";
+        unless (-d $makedir) {
+            mkdir $makedir, 0755
+                or die "Couldn't make directory $makedir\n";
+        }
+    }
+    $MADE_DIR{$file} = 1;
+}
+
+# was using perl's File::Copy, but I want to preserve the file time.
+sub copy
+{
+    my ($src, $dest) = @_;
+    my $ret = system("cp", "-p", $src, $dest);
+    return ($ret == 0);
+}
+
+package VCV::SVK;
+use strict;
+
+sub new {
+    my $class = shift;
+    my $self  = bless {}, $class;
+
+    return $self;
+}
+
+sub add_repo {
+    my $self      = shift;
+    my $local_dir = shift;
+    my $repo_src  = shift;
+    $self->{repo}->{$local_dir} = {
+        remote => $repo_src,
+        svk    => "/$self->{depot}/mirror/$local_dir/trunk",
+        co     => "$DIR_CVS/$local_dir",
+        };
+}
+
+sub check_repo {
+    my $self = shift;
+    my $local_dir = shift;
+    my $foo = `svk st $self->{repo}->{$local_dir}->{co}`;
+    return unless $foo;
+}
+
+sub checkout_repo {
+    my $self = shift;
+    my $local_dir = shift;
+
+
+
+    my $svk_path = "$self->{repo}->{$local_dir}->{svk}";
+    $svk_path =~ s!/trunk!!;
+
+    my $is_mirror = `svk ls $svk_path`;
+    if ($?) {
+        my $remote = "$self->{repo}->{$local_dir}->{remote}";
+        $remote =~ s!/trunk!!;
+        warn ("svk mirror $remote $svk_path");
+        system("svk","mirror", $remote, $svk_path) and die;
+    }
+    warn "sync $svk_path";
+    my $sync = system("svk sync $svk_path");
+    die $sync if $?;
+    if (! -e $self->{repo}->{$local_dir}->{co}) {
+        warn "svk co $self->{repo}->{$local_dir}->{svk}  $self->{repo}->{$local_dir}->{co}\n";
+        my $checkout = `svk co $self->{repo}->{$local_dir}->{svk}  $self->{repo}->{$local_dir}->{co}`;
+        die $checkout if $?;
+    } else {
+        warn "svk update $self->{repo}->{$local_dir}->{co}";
+        my $update = `svk update $self->{repo}->{$local_dir}->{co}`;
+        die $update if $?;
+    }
+}
+
+sub update_repo {
+
+}
+
+sub set_depot {
+    my $self = shift;
+    my $depot = shift;
+    die "Alread have depot set to '$self->{depot}'" if $self->{depot};
+
+    my $found_depot = 0;
+    foreach my $line (split "\n", `svk depotmap --list`) {
+        $found_depot = 1 if($line =~ m!^/$depot/!);
+    }
+    if ($found_depot) {
+        $self->{depot} = $depot;
+    } else {
+        die "Cannot find specified depot '$depot'";
+    }
+}
+
+
+__END__
diff -r 523e5dd6e9b6 -r 08ee07f4ef06 cvs/multicvs.conf
--- a/cvs/multicvs.conf	Tue May 01 15:54:48 2012 +0800
+++ b/cvs/multicvs.conf	Tue May 01 16:00:07 2012 +0800
@@ -12,7 +12,6 @@
 
 # DreamWidth repositories
 HG(dw-free)               = http://hg.dwscoalition.org/dw-free @stable
-HG(vcv)                   = http://hg.dwscoalition.org/vcv
 HG(bml)                   = http://hg.dwscoalition.org/bml
 HG(js)                    = http://hg.dwscoalition.org/js
 HG(s2)                    = http://hg.dwscoalition.org/s2
@@ -39,7 +38,6 @@
 #openid/perl/Net-OpenID-Consumer/lib           cgi-bin
 #openid/perl/Net-OpenID-Server/lib             cgi-bin
 #openid/perl/Net-OpenID-Common/lib             cgi-bin
-vcv/bin/vcv                                   bin/vcv
 TheSchwartz/bin/schwartzmon                   bin/schwartzmon
 
 js/                                           htdocs/js
--------------------------------------------------------------------------------