#!/bin/bash set -e # stg-cvs - helper script to manage a mixed cvs/stgit working copy. # Allows quick synchronization of a cvs mirror branch (does not try to # reconstruct patchsets, creates "jumbo" commits), and commits stgit # patches to CVS. # Copyright (c) 2007 Yann Dirson # Subject to the GNU GPL, version 2. # NOTES # - you want to add a "CVS" line to .git/info/exclude # - you may want to add a ".git" line to the top .cvsignore # LIMITATIONS # - this is only a proof-of-concept prototype # - lacks an "init" command # - "commit" does not ensure the base is uptodate before trying to # commit (but hey, it's CVS ;) # - "commit" can only commit a single patch # - not much robustness here # - still bad support for files removed in cvs (should catch "no # longer in the repository" message) # - while fetching, if a file change was not git-update-index'd when # cvs-update'd (eg. because of a stg-cvs bug), it is not seen on further # fetches until it changes again, since we scan "cvs update" output. # This yields possible inconsistencies with CVS. # - similarly, any conflict while cvs-updating (whether due to illegal # changes to the cvs-mirror-branch, or due to files added to cvs but # already-existing in working copy, or to directory moves inside the # cvs repository, or ) has to be dealt with by hand (although # the situation is better here: cvs sees the conflict on subsequent tries) # - this only deals with CVS but could surely be extended to any other # VCS # - bad/no support for cvsutils: # - stg push/pop operations confuse cvsu because of timestamp changes # - cvspurge/cvsco would nuke .git => does not make it easy to ensure # synchronisation # - should use a separate workspace for cvs branch like tailor does # - lacks synchronisation of .cvsignore <-> .gitignore # - no support for filenames with spaces (stg lacks --zero output format) # - git-commit is too chatty when it finds nothing to commit # - lacks a "quick cvs commit" feature # - confused by cvs keyword substitution usage() { [ "$#" = 0 ] || echo "ERROR: $*" echo "Usage: $(basename $0) " echo " commands: $(do_commands)" exit 1 } do_commands() { echo $(grep '^[a-z-]*)' $0 | cut -d')' -f1) } do_fetch() { local return=0 local path local parent="$1" local branch="$2" # record changes from cvs into index stg branch "$parent" || exit $? cvs -fq update -dP | grep -v '^\? ' | tee /dev/tty | while read status path; do if [ -e "$path" ]; then git update-index --add "$path" || exit $? else git update-index --remove "$path" || exit $? fi # cvs update: `FELIN1_PEP/src/java/com/sagem/felin/ui/widget/PEPRifStateIcon.java' is no longer in the repository done # create commit if git commit -m "stg-cvs sync"; then : else return=$? fi # back to branch stg branch "$branch" || exit $? return $return } cvs_add_dir() { local parent=$(dirname "$1") if [ ! -e "$parent/CVS" ]; then cvs_add_dir "$parent" fi cvs add "$1" } # get context branch=$(stg branch) parent=$(git-repo-config "branch.${branch}.merge") || usage "no declared parent for '$branch' - set branch.${branch}.merge" # extract command [ "$#" -ge 1 ] || usage command="$1" shift case "$command" in fetch) do_fetch "$parent" "$branch" ;; pull) if do_fetch "$parent" "$branch"; then # update # --merged stg rebase "$parent" stg clean --applied fi ;; commit) # sanity asserts [ $(stg applied | wc -l) = 1 ] || usage "you don't have exactly one patch applied" # context patch=$(stg top) # adds stg files | grep ^A | cut -c3- | while read file; do parent=$(dirname "$file") if [ ! -e "$parent/CVS" ]; then cvs_add_dir "$parent" fi cvs -f add "$file" done # removes stg files | grep ^D | cut -c3- | xargs -r cvs -f remove # commit stg files --bare | xargs -r cvs -fq commit \ -F ".git/patches/$branch/patches/$patch/description" # sync the parent branch stg branch "$parent" git-cherry-pick "patches/${branch}/${patch}" stg branch "${branch}" # update # --merged stg rebase "$parent" stg clean --applied ;; _commands) # hint for bash-completion people :) do_commands ;; *) usage "unknown command '$command'" ;; esac