| #!/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 |
| |
| # BRANCH INIT |
| # - ensure the cvs wc is clean (eg. with "cvsco") |
| # $ git init |
| # $ echo CVS >> .git/info/exclude |
| # $ git add . |
| # $ git commit -m "Initial import." |
| # $ git branch -m master cvs |
| # $ stg branch -c master cvs |
| # $ git config branch.master.stgit.parentbranch cvs (0.12.1 and earlier only) |
| # $ git config branch.cvs.description "CVS $(cat CVS/Root) $(cat CVS/Repository) $(cat CVS/Tag 2>/dev/null | echo HEAD)" |
| # $ git config branch.master.description "Changes for $(cat CVS/Repository) $(cat CVS/Tag 2>/dev/null | echo HEAD)" |
| |
| # LIMITATIONS |
| # - this is only a proof-of-concept prototype |
| # - lacks an "init" command (see above) |
| # - "commit" does not ensure the base is uptodate before trying to |
| # commit (but hey, it's CVS ;): better "stg-cvs pull" first |
| # - "commit" can only commit a single patch |
| # - not much robustness here |
| # - still no support for files removed in cvs (should catch "no |
| # longer in the repository" message) |
| # - this only deals with CVS but could surely be extended to any other |
| # VCS |
| # - 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 |
| |
| # DESIGN FLAWS |
| # - 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) |
| # - 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 |
| # - 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-config "branch.${branch}.stgit.parentbranch") || |
| usage "no declared parent for '$branch' - set branch.${branch}.stgit.parentbranch" |
| |
| # 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 |