+#!/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 <ydirson@altern.org>
+# 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 <fill here>) 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) <command>"
+ 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