# push-mirror.sh: update a read-only repository mirror
# 
################################################################
# Copyright (C) 2001, 2002 Tom Lord
# 
# See the file "COPYING" for further information about
# the copyright and warranty status of this work.
# 

set -e 
command_line="$*"

################################################################
# special options
# 
# Some options are special:
# 
#       --version | -V
#       --help | -h
# 
if test $# -ne 0 ; then

  for opt in "$@" ; do
    case $opt in

      --version|-V) exec larch --version
                    ;;


      --help|-h)
                printf "update a read-only repository mirror\\n"
                printf "usage: push-mirror [options] from-archive to-archive [LIMITS ...]\\n"
                printf "\\n"
                printf " -V --version                  print version info\\n"
                printf " -h --help                     display help\\n"
                printf "\\n"
                printf " --silent                      no output (except odd errors)\\n"
                printf " --quiet                       brief output\\n"
                printf " --report                      default output\\n"
                printf " --verbose                     maximal output\\n"
                printf " --debug                       debugging output\\n"
                printf "\\n"
                printf " --pull                        pull, don't push\\n"
                printf "\\n"
                printf "Install all categories, branches, versions and revisions in FROM-ARCHIVE\\n"
                printf "in TO-ARCHIVE if they are not already there.\\n"
                printf "\\n"
                printf "The \"=meta-info\" directory of TO-ARCHIVE must contain a file named\\n"
                printf "\"mirror\" which contains the name of FROM-ARCHIVE.\\n"
                printf "\\n"
                printf "The optional argument LIMIT may be a category, branch name, version name,\\n"
                printf "or revision name, in which case, \"puts-mirror\" limits its attention to\\n"
                printf "just that part of the archive.\\n"
                printf "\\n"
                printf "\"push-mirror\" will issue an error message and exit with non-0 status if\\n"
                printf "instructed (via LIMIT arguments) to install revisions out-of-order.\\n"
                printf "\\n"
                printf "The option \"--pull\" optimizes FTP sessions for the case where the \\n"
                printf "FROM-ARCHIVE is remote and TO-ARCHIVE local.\\n"
                printf "\\n"
                printf "\\n"
                exit 0
                ;;

      *)
                ;;
    esac
  done
fi

################################################################
# Ordinary Options
# 
# 

quiet=--quiet
report=--report
verbose=
silent_opt=
quiet_opt=
report_opt=
verbose_opt=
debug_opt=
__restart=
pull=

while test $# -ne 0 ; do

  case "$1" in 

    --pull)     shift
                pull=--pull
                ;;

    ----restart) shift
                 __restart=----restart
                 ;;

    --silent)   shift
                quiet=
                report=
                verbose=
                silent_opt=--silent
                quiet_opt=
                report_opt=
                verbose_opt=
                ;;

    --quiet)    shift
                quiet=--quiet
                report=
                verbose=
                silent_opt=
                quiet_opt=--quiet
                report_opt=
                verbose_opt=
                ;;

    --report)   shift
                quiet=--quiet
                report=--report
                verbose=
                silent_opt=
                quiet_opt=
                report_opt=--report
                verbose_opt=
                ;;

    --verbose)  shift
                quiet=--quiet
                report=--report
                verbose=--verbose
                silent_opt=
                quiet_opt=
                report_opt=
                verbose_opt=--verbose
                ;;

    --debug)    shift
                larch heading "push-mirror: debugging output enabled\\n"
                set -x
                debug_opt=--debug
                ;;

    --)                 shift
                        break
                        ;;
                        
    -*)                 printf "push-mirror: unrecognized option (%s)\\n" "$1" 1>&2
                        printf "try --help\\n" 1>&2
                        exit 1
                        ;;

    *)                  break
                        ;;
  esac

done



################################################################
# Ordinary Arguments
# 

if test $# -lt 2 ; then 
  printf "usage: push-mirror [options] from-archive to-archive [LIMITS ...]\\n" 1>&2
  printf "try --help\\n" 1>&2
  exit 1
fi

from_archive="$1"
shift

to_archive="$1"
shift

################################################################
# Sanity Check and Process Defaults
# 

larch valid-archive-name -e push-mirror "$from_archive"
larch valid-archive-name -e push-mirror "$to_archive"

if test $# -ne 0 ; then

  while test $# -ne 0 ; do

    item="$1"
    shift

    larch valid-package-name -e push-mirror --tolerant "$item"
    a="`larch parse-package-name -A \"$from_archive\" --arch \"$item\"`"
    if test "$a" != "$from_archive" ; then
      printf "\\n" 1>&2
      printf "push-mirror: limit specification from wrong archive\\n" 1>&2
      printf "  source archive: %s\\n" "$from_archive" 1>&2
      printf "  limit archive: %s\\n" "$a" 1>&2
      printf "  limit spec: %s\\n" "$item" 1>&2
      printf "\\n" 1>&2
      exit 1
    fi

    limits="$limits $item"

  done

fi


################################################################
# Greetings
# 

if test "(" -z "$__restart" -a ! -z "$quiet" ")" -o ! -z "$report" ; then
  larch heading "push-mirror\\n"
  printf "arguments: %s\\n" "$command_line" | fold -w 60 | larch body-indent
  larch heading --sub "push-mirror start time: %s\\n" "`date`"
  if test ! -z "$report" ; then 
    larch heading --sub "from archive: %s\\n" "$from_archive"
    larch heading --sub "to archive: %s\\n" "$to_archive"
  fi
fi

################################################################
# Ensure that We Have an Archive Connection 
# 

if test -z "$pull" ; then

  if test "$WITHARCHIVE" != "$to_archive" ; then

    if test ! -z "$quiet" ; then
      larch heading --sub "restarting with archive connection\\n"
    fi

    exec larch with-archive -A "$to_archive"  \
      larch push-mirror \
                        $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
                        ----restart \
                        "$from_archive" "$to_archive" $limits
  fi

else

  if test "$WITHARCHIVE" != "$from_archive" ; then

    if test ! -z "$quiet" ; then
      larch heading --sub "restarting with archive connection\\n"
    fi

    exec larch with-archive -A "$from_archive"  \
      larch push-mirror \
                        $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
                        ----restart \
                        --pull \
                        "$from_archive" "$to_archive" $limits
  fi

fi

################################################################
# Sanity Check and Process Defaults (2)
# 

if test -z "$limits" ; then

  limits="`larch categories -A \"$from_archive\"`"

  if test -z "$limits" ; then
    printf "\\n" 1>&2
    printf "push-mirror: source archive is empty\\n" 1>&2
    printf "  archive: %s\\n" "$from_archive" 1>&2
    printf "\\n" 1>&2
    exit 1
  fi
fi



################################################################
# Make a Temp Directory
# 

here="`pwd`"
tmpdir="$here/,,push-mirror.$$"
bail()
{
  cd "$here"
  rm -rf "$tmpdir"
  exit 1
}

trap "printf \"push-mirror: interrupted -- cleaning up\\n\" 1>&2 ; bail" INT
rm -rf "$tmpdir"
mkdir "$tmpdir"

################################################################
# Handy Functions
# 

copy_optional_file()
{
  # if the file "$1" is present in "$category/$branch/$version/$lvl"
  # of "$from_archive", then copy it to the current directory.
  #

  if test ! -z "`printf '%s\n' \"$1\" | join -o 1.1 -1 1 -2 1 - ,tmp`" ; then
    larch with-archive -A "$from_archive" wftp-get "$category/$branch/$version/$lvl/$1" > "$1"
  fi
}


copy_revision_files()
{
  # copy all the critical files (except the lock) from
  # "$category/$branch/$version/$lvl" of "$from_archive"
  # to the current directory.
  # 
  larch with-archive -A "$from_archive" wftp-ls "$category/$branch/$version/$lvl" > ,tmp2
  sort ,tmp2 > ,tmp
  rm -f ,tmp2

  copy_optional_file CONTINUATION
  copy_optional_file log
  copy_optional_file "$version--$lvl.patches.tar.gz"
  copy_optional_file "$version--$lvl.src.tar.gz"
  copy_optional_file "$version--$lvl.tar.gz"

  rm ,tmp
}

update_readme()
{
  # ensure that the =README file in the directory "$1"
  # is the same in both archives.
  #
 # 
 #   if test ! -e ,,readmes-done ; then
 #     touch ,,readmes-done
 #   fi
 # 
 #   dir="$1"
 #   shift
 # 
 #   if test -z "`printf '%s\n' \"$dir\" | join -o 1.1 -1 1 -2 1 - ,,readmes-done`" ; then
 # 
 #     if test ! -z "$quiet" ; then
 #       larch heading --sub --sub "updating =README in %s\\n" "$dir"
 #     fi
 # 
 #     rm -f ,readme
 #     if larch with-archive -A "$from_archive" wftp-get "$dir/=README" > ,readme 2> /dev/null ; then
 #       larch with-archive -A "$to_archive" wftp-delete "$dir/=README~" 2> /dev/null || true
 #       larch with-archive -A "$to_archive" wftp-rename "$dir/=README" "$dir/=README~" 2> /dev/null || true
 #       larch with-archive -A "$to_archive" wftp-put "$dir/=README" < ,readme
 #     fi
 #     rm -f ,tmp
 #     printf "%s\\n" "$dir" > ,tmp
 #     cat ,,readmes-done >> ,tmp
 #     rm ,,readmes-done
 #     sort ,tmp > ,,readmes-done
 #   fi
}

ensure_category()
{
  # make sure that $category exists in the "$to_archive"
  # creating it if necessary. 
  #
  # $tmpdir/,,to-categories is a list of categories known
  # to be on the remote archive when we started
  # 

  if test ! -z "`printf '%s\n' \"$category\" | join -o 1.1 -1 1 -2 1 - ,,to-categories`" ; then

    if test ! -z "$quiet" ; then
      larch heading --sub --sub "category already there: %s\\n" "$category"
    fi

  else

    if test ! -z "$quiet" ; then
      larch heading --sub --sub "adding category %s\\n" "$category"
    fi

    rm -rf ,,cat
    mkdir ,,cat

    larch category-readme -A "$from_archive" "$category" > ,,cat/=README

    if ! larch with-archive -A "$to_archive" larch putdir -e push-mirror -A "$to_archive" ",,cat" "$category" ; then
      printf "\\n" 1>&2
      printf "push-mirror: unable to create category\\n" 1>&2
      printf "\\n" 1>&2
      rm -rf ,,cat
      exit 1
    fi

    rm -rf ,,cat

    if test ! -z "$quiet" ; then
      larch heading --sub --sub --sub "...done\\n"
    fi

  fi
}

ensure_branch()
{
  # make sure that $branch  exists in the "$to_archive"
  # creating it if necessary. 
  #
  # $tmpdir/,,to-branches is a list of same-category branches
  # known to be on the remote archive when we started
  # 

  if test ! -z "`printf '%s\n' \"$branch\" | join -o 1.1 -1 1 -2 1 - ,,to-branches`" ; then

    if test ! -z "$quiet" ; then
      larch heading --sub --sub "branch already there: %s\\n" "$branch"
    fi

  else

    if test ! -z "$quiet" ; then
      larch heading --sub --sub "adding branch %s\\n" "$branch"
    fi

    rm -rf ,,branch
    mkdir ,,branch

    larch branch-readme -A "$from_archive" "$branch" > ,,branch/=README
    cd ,,branch
    larch make-lock version-lock
    cd ..

    if ! larch with-archive -A "$to_archive" larch putdir -e push-mirror -A "$to_archive" ",,branch" "$category/$branch" ; then
      printf "\\n" 1>&2
      printf "push-mirror: unable to create branch\\n" 1>&2
      printf "\\n" 1>&2
      rm -rf ,,branch
      exit 1
    fi

    rm -rf ,,branch

    if test ! -z "$quiet" ; then
      larch heading --sub --sub --sub "...done\\n"
    fi

  fi
}

ensure_version()
{
  # make sure that $version  exists in the "$to_archive"
  # creating it if necessary. 
  #
  # $tmpdir/,,to-versions is a list of same-branch versions
  # known to be on the remote archive when we started
  # 

  if test ! -z "`printf '%s\n' \"$version\" | join -o 1.1 -1 1 -2 1 - ,,to-versions`" ; then

    if test ! -z "$quiet" ; then
      larch heading --sub --sub "version already there: %s\\n" "$version"
    fi

  else

    if test ! -z "$quiet" ; then
      larch heading --sub --sub "adding version %s\\n" "$version"
    fi

    rm -rf ,,version
    mkdir ,,version

    larch version-readme -A "$from_archive" "$version" > ,,version/=README
    cd ,,version
    larch make-lock version-lock
    larch make-lock revision-lock
    cd ..

    if ! larch with-archive -A "$to_archive" \
             larch putlast -e make-version -A "$to_archive"  \
                          lock-branch "$branch" ",,version" "$category/$branch/$version" noop ; then
      printf "\\n" 1>&2
      printf "push-mirror: unable to create version\\n" 1>&2
      printf "\\n" 1>&2
      rm -rf ,,version
      exit 1
    fi

    rm -rf ,,version

    if test ! -z "$quiet" ; then
      larch heading --sub --sub --sub "...done\\n"
    fi

  fi
}


ensure_revision()
{
  # make sure that $version--lvl  exists in the "$to_archive"
  # creating it if necessary. 
  #
  # $tmpdir/,,to-revisions is a list of same-version revisions
  # known to be on the remote archive when we started
  # 

  if test ! -z "`printf '%s\n' \"$lvl\" | join -o 1.1 -1 1 -2 1 - ,,to-revisions`" ; then

    if test ! -z "$quiet" ; then
      larch heading --sub --sub "revision already there: %s\\n" "$revision"
    fi

  else

    if test ! -z "$quiet" ; then
      larch heading --sub --sub "adding revision %s\\n" "$revision"
    fi

    rm -rf ,,revision
    mkdir ,,revision

    cd ,,revision
    copy_revision_files
    larch make-lock revision-lock
    cd ..    

    destdir="$category/$branch/$version/$lvl"

    if ! larch with-archive -A "$to_archive" \
           larch putlast -e push-mirror -A "$to_archive" \
                        lock-revision "$version--$lvl" \
                        ",,revision" "$destdir" \
                         noop \
    ; then
      printf "\\n" 1>&2
      printf "push-mirror: unable to create revision\\n" 1>&2
      printf "\\n" 1>&2
      rm -rf ,,revision
      exit 1
    fi

    rm -rf ,,revision

    if test ! -z "$quiet" ; then
      larch heading --sub --sub --sub "...done\\n"
    fi

  fi
}

################################################################
# Make Sure the Remote Site Agrees that it is a Mirror
# 

remote_mirror="`larch with-archive -A \"$to_archive\" wftp-get \"=meta-info/mirror\" || true`"

if test "$remote_mirror" != "$from_archive" ; then
  printf "\\n" 1>&2
  printf "push-mirror: remote site is not a mirror of %s\\n" "$from_archive" 1>&2
  printf "\\n" 1>&2
  exit 1
fi


################################################################
# Update for Each Item in Limits 
# 

cd "$tmpdir"

larch categories -A "$to_archive" | sort > ,,to-categories

update_readme "."

for l in $limits ; do

  if test ! -z "$quiet" ; then
    larch heading --sub "updating: %s\\n" "$l"
  fi

  # 
  # categories
  # 
  category=`larch parse-package-name --basename "$l"`

  ensure_category
  update_readme "$category"

  # 
  # branches  ($category is set)
  # 
  branch=

  if test "$l" != "$category" && larch valid-package-name --tolerant "$l" ; then
    branches=`larch parse-package-name "$l"`
  else
    branches=`larch branches -A "$from_archive" "$category"`
  fi

  larch branches -A "$to_archive" "$category" | sort > ,,to-branches

  for branch in $branches ; do

    ensure_branch
    update_readme "$category/$branch"


    # 
    # versions  ($category and $branch are set)
    # 
    version=

    if larch valid-package-name --tolerant --vsn "$l" ; then
      versions=`larch parse-package-name --package-version "$l"`
    else
      versions=`larch versions -A "$from_archive" "$branch"`
    fi

    larch versions -A "$to_archive" "$branch" | sort > ,,to-versions

    for version in $versions ; do

      ensure_version
      update_readme "$category/$branch/$version"

      # 
      # revisions  ($category, $branch, and $version are set)
      # 
      revision=

      if larch valid-package-name --tolerant --lvl "$l" ; then
        revisions="$l"
      else
        revisions=`larch revisions -A "$from_archive" --full "$version"`
      fi

      larch revisions -A "$to_archive" "$version" | sort > ,,to-revisions

      for revision in $revisions ; do

        lvl=`larch parse-package-name --lvl "$revision"`
        ensure_revision

      done
    done
  done
done


################################################################
# Bye
# 

cd "$here"
rm -rf "$tmpdir"

if test ! -z "$quiet" ; then 
  larch heading --sub "push-mirror finish time: %s\\n" "`date`"
fi

# tag: Tom Lord Tue Jan 22 21:06:09 2002 (archive-transactions/push-mirror.sh)
#
