#!/usr/bin/guile \ -e main -s !# ;;;; change-cvsroot --- change the repository of a CVS working directory ;;;; Jim Blandy --- July 1998 ;;; Usage: change-cvsroot WORKING-DIR NEW-ROOT [NEW-MODULE] ;;; ;;; Change the repository of the CVS working directory WORKING-DIR to ;;; NEW-ROOT. This is useful if the repository has moved, and you ;;; have working directories containing uncommitted changes, or don't ;;; want to check the whole working directory out afresh. This script ;;; edits the information in the `CVS' directories of WORKING-DIR. ;;; ;;; Note that the base revisions of the working files must be ;;; identical in the old and new repositories, or else chaos will ;;; result. This command is really only useful after copying a ;;; repository; it is not useful for reconciling results between ;;; independent repositories of the same project. ;;; ;;; NEW-ROOT should be a CVS root specification; it may ;;; include a full access method, like this: ;;; :ext:jimb@egcs.cygnus.com:/egcs/carton/cvsfiles ;;; ;;; This command can also shift a working directory within a ;;; repository. The optional third argument NEW-MODULE gives the ;;; relative directory within NEW-ROOT that WORKING-DIR should ;;; correspond to. If omitted, the working directory's position ;;; within the repository is left unchanged. (use-modules (ice-9 string-fun)) ;;;; Utility functions, which should probably be in some library somewhere. ;;; Traverse the directory tree at ROOT, applying F to the name of ;;; each file in the tree, including ROOT itself. For a subdirectory ;;; SUB, if (F SUB) is true, we recurse into SUB. Do not follow ;;; symlinks. (define (for-each-file f root) ;; A "hard directory" is a path that denotes a directory and is not a ;; symlink. (define (file-is-hard-directory? filename) (eq? (stat:type (lstat filename)) 'directory)) (let visit ((root root)) (let ((should-recur (f root))) (if (and should-recur (file-is-hard-directory? root)) (let ((dir (opendir root))) (let loop () (let ((entry (readdir dir))) (cond ((eof-object? entry) #f) ((or (string=? entry ".") (string=? entry "..")) (loop)) (else (visit (string-append root "/" entry)) (loop)))))))))) ;;; Return the contents of FILE, as a string. (define (file-contents file) (let* ((port (open-input-file file)) (contents (read-delimited "" port))) (close-port port) contents)) ;;; Set the contents of FILE, in its entirety, to STRING. (define (set-file-contents! file string) (let ((port (open-output-file file))) (display string port) (close-port port) #f)) (define (display-line . args) (for-each display args) (newline)) ;;;; CVS helper functions ;;; Apply F to the name of each directory in the CVS working tree at ;;; WD. This function does not apply F to the `CVS' directories, ;;; and does not recurse into directories that don't seem to be ;;; controlled by CVS (i.e., directories that lack CVS subdirs). (define (for-each-cvs-directory f wd) (define (is-cvs-dir? dir) (let ((ctrl-dir (string-append dir "/CVS"))) (and (file-exists? ctrl-dir) (file-is-directory? ctrl-dir)))) (for-each-file (lambda (file) (and (is-cvs-dir? file) (begin (f file) #t))) wd)) ;;; Make sure all CVS control files in the working tree WD are ;;; readable and writable. (define (check-all-permissions wd) (for-each-cvs-directory (lambda (dir) (if (not (and (access? (string-append dir "/CVS/Root") (logior R_OK W_OK)) (access? (string-append dir "/CVS/Repository") (logior R_OK W_OK)))) (error "CVS control files not readable and writable:" dir))) wd)) ;;; Split a CVS root into its access method and the repository path. ;;; (split-root ROOT string-append) => ROOT. (define (split-root root k) (split-after-char-last #\: root k)) ;;; Tail-call K with the root and module of the CVS working directory ;;; WD. The module is the relative path from the repository root to ;;; the directory corresponding to WD. (define (get-repository wd k) (define (ctrl part) (sans-surrounding-whitespace (file-contents (string-append wd "/CVS/" part)))) ;; CVS stores the repository path as an absolute pathname, not ;; relative to the Root, so the local path to the repository appears ;; both in Root and Repository. We want to cut out the duplicated ;; information, and just give the path of the repository directory ;; relative to the top of the repository. (let ((root (ctrl "Root")) (absolute-module (ctrl "Repository"))) (split-root root (lambda (method repo) (let ((repo (string-append repo "/"))) (or (string-prefix=? repo absolute-module) (error "error: Root and Repository files do not agree:" wd)) (k root (substring absolute-module (string-length repo)))))))) ;;; Set the working directory WD to correspond to the subdirectory ;;; MODULE of the repository ROOT. For example, ;;; (set-repository! ":ext:you@host:/repository" "foo") ;;; would set WD to correspond to :ext:you@host:/repository/foo. (define (set-repository! wd root module) (define (set-ctrl! file contents) (set-file-contents! (string-append wd "/CVS/" file) (string-append contents "\n"))) (set-ctrl! "Root" root) (split-root root (lambda (method repo) (set-ctrl! "Repository" (string-append repo "/" module))))) ;;; (display-line "Redefining set-repository!") ;;; (define (set-repository! wd root module) ;;; (display-line "Changing repository: " wd) ;;; (display-line "to " root " and " module)) ;;; Change the CVS working directory CVS to use the new root ROOT. ;;; Also, if NEW-PREFIX is true and the directory within the ;;; repository used to be OLD-PREFIX/MUMBLE, change it to be ;;; NEW-PREFIX/MUMBLE. (define (change-one-root cvs new-cvsroot old-prefix new-prefix) (define (change-prefix string old-prefix new-prefix) (if (string-prefix=? old-prefix string) (string-append new-prefix (substring string (string-length old-prefix))) string)) (split-root new-cvsroot (lambda (new-method new-repo) (get-repository cvs (lambda (old-root old-module) ;; Perhaps substitute the new module prefix for the old one. (let ((new-module (if new-prefix (change-prefix old-module old-prefix new-prefix) old-module))) (set-repository! cvs new-cvsroot new-module))))))) ;;; Change the CVS files on the entire working tree WD to use NEW-ROOT. (define (change-cvsroot wd new-root new-module) (check-all-permissions wd) (get-repository wd (lambda (root old-module) (for-each-cvs-directory (lambda (dir) (change-one-root dir new-root old-module new-module)) wd)))) ;;;; Entry point, and argument processing. (define (main args) (let ((args (cdr args))) (or (= (length args) 2) (= (length args) 3) (error "usage: change-cvsroot WORKING-DIR NEW-ROOT NEW-MODULE")) (let ((working-dir (car args)) (new-root (cadr args)) (new-module (if (= (length args) 3) (caddr args) #f))) (change-cvsroot working-dir new-root new-module))))