# 
# update.sh - build a project tree based on a simple update merge
################################################################
# 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 "merge local changes with the latest revision\\n"
                printf "usage: update [options] old-dir new-dir [[archive/]version]\\n"
                printf "usage: update [options] --in-place dir [[archive/]version]\\n"
                printf "\\n"
                printf " -V --version                  print version info\\n"
                printf " -h --help                     display help\\n"
                printf "\\n"
                printf " -R --root root                specify the local archive root\\n"
                printf " -A --archive archive          specify the archive name\\n"
                printf "\\n"
                printf " --in-place                    update in place\\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 " --force                       force update even if tree is up-to-date\\n"
                printf "\\n"
                printf "Construct a new project tree NEW-DIR for the working directory\\n"
                printf "containing OLD-DIR.\\n"
                printf "\\n"
                printf "The new project tree is formed by getting the latest revision\\n"
                printf "of VERSION (or the default version of OLD-DIR) and then applying a\\n"
                printf "patch set of the differences between OLD-DIR and the highest revision\\n"
                printf "with which OLD-DIR is up-to-date.\\n"
                printf "\\n"
                printf "If the merge involves conflicts, a warning message is printed, and the\\n"
                printf "new project tree will contain \".rej\" files.\\n"
                printf "\\n"
                exit 0
                ;;

      *)
                ;;
    esac
  done
fi

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

archroot=
archive=

in_place=

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

force=

while test $# -ne 0 ; do

  case "$1" in 

    --force)    shift
                force=--force
                ;;

    --in-place) shift
                in_place=--in_place
                ;;

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

    --no-lint)  shift
                no_lint=--no-lint
                ;;

    --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 "update: debugging output enabled\\n"
                set -x
                debug_opt=--debug
                ;;


    -R|--root)          shift
                        if test $# -eq 0 ; then
                          printf "update: -R and --root require an argument\\n" 1>&2
                          printf "try --help\\n" 1>&2
                          exit 1
                        fi
                        archroot="$1"
                        shift
                        ;;

    -A|--archive)       shift
                        if test $# -eq 0 ; then
                          printf "update: -A and --archive require an argument\\n" 1>&2
                          printf "try --help\\n" 1>&2
                          exit 1
                        fi
                        archive="$1"
                        shift
                        ;;

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

    *)                  break
                        ;;
  esac

done



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

if test -z "$in_place" ; then
  n_dir_args=2
else
  n_dir_args=1
fi

min_args=$n_dir_args
max_args=$(($n_dir_args + 1))

if test $# -lt $min_args -o $# -gt $max_args ; then
  printf "usage: update [options] old-dir new-dir [[archive/]version]\\n" 1>&2
  printf "       update [options] --in-place dir [[archive/]version]\\n" 1>&2
  printf "try --help\\n" 1>&2
  exit 1
fi

old_dir="$1"
shift

if test -z  "$in_place" ; then
  new_dir="$1"
  shift 
else
  new_dir="$old_dir"
fi

if test $# -ne 0 ; then
  archive_version="$1"
  shift
else
  archive_version=
fi


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

here="`pwd`"

cd "$old_dir"
old_dir="`pwd`"

cd "$here"
cd "`dirname \"$new_dir\"`"
output_dir="`pwd`"
new_base="`basename \"$new_dir\"`"
new_dir="$output_dir/$new_base"

cd "$old_dir"
old_dir="`larch tree-root --accurate`"

if test -z "$archive_version" ; then
  archive_version=`larch tree-version`
else
  larch valid-package-name -e update --tolerant -- "$archive_version"
fi

archive="`larch parse-package-name -R \"$archroot\" -A \"$archive\" --arch \"$archive_version\"`"
spec="`larch parse-package-name --non-arch \"$archive_version\"`"
category="`larch parse-package-name --basename \"$archive_version\"`"
branch="`larch parse-package-name \"$archive_version\"`"

tmpdir="$output_dir/,,update.$$.$new_base"


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

if test "(" -z "$__restart" -a ! -z "$quiet" ")" -o ! -z "$report" ; then
  larch heading "update\\n"
  printf "arguments: %s\\n"  "$command_line" | fold -w 60 | larch body-indent
  larch heading --sub "update start time: %s\\n" "`date`"
  larch heading --sub "old project tree: %s\\n" "$old_dir"
  larch heading --sub "new project tree: %s\\n" "$new_dir"
  larch heading --sub "archive: %s\\n" "$archive"
  larch heading --sub "version spec: %s\\n" "$spec"
fi


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

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

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


  if test -z "$in_place" ; then
    exec larch with-archive -A "$archive" \
                  larch update \
                        $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
                        ----restart \
                        $force \
                        -- "$old_dir" "$new_dir" "$archive/$spec"
  else
    exec larch with-archive -A "$archive" \
                  larch update \
                        $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
                        ----restart \
                        $force \
                        --in-place "$old_dir" "$archive/$spec"
  fi
fi

################################################################
# What Version are We Updating From?
# 

if larch valid-package-name --vsn --tolerant "$spec" ; then
  version="`larch parse-package-name --package-version \"$spec\"`"
else
  version="`larch versions --reverse \"$archive/$spec\" | head -n 1`"
fi

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

################################################################
# What Is the Latest Revision for Which Old-Dir has a Patch?
# 

cd "$old_dir" 

old_dir_latest_lvl="`larch log-ls --reverse \"$archive/$version\" | head -n 1`"

if test -z "$old_dir_latest_lvl" ; then
  printf "update: project tree not based on version\\n" 1>&2
  printf "  project tree: %s\\n" "$old_dir" 1>&2
  printf "  has no patch level for version:\\n" 1>&2
  printf "    %s\\n" "$version" 1>&2
  printf "\\n" 1>&2
  exit 1
fi

################################################################
# What Revision are we Updating To
# 

if larch valid-package-name --patch-level "$spec" ; then
  latest_lvl="`larch parse-package-name --lvl \"$spec\"`"
else
  latest_lvl="`larch revisions --reverse \"$archive/$version\" | head -n 1`"

  if test -z "$latest_lvl" ; then
    printf "update: no revisions in version\\n" 1>&2
    printf "  version: %s/%s" "$archive" "$version" 1>&2
    printf "\\n"
    exit 1
  fi
fi

################################################################
# If Old-Dir is Already Up-to-Date, We're Done
# 

if test -z "$force" -a  "$latest_lvl" = "$old_dir_latest_lvl" ; then
  if test ! -z "$quiet" ; then
    larch heading --sub "tree already up-to-date (use --force if necessary)\\n" 
    larch heading --sub "update finish time: %s\\n" "`date`"
  fi
  exit 0
fi


################################################################
# For an --in-place Update, Move the old_dir
# 

if test ! -z "$in_place" ; then
  old_dir_base="`basename \"$old_dir\"`"
  old_dir_parent="`dirname \"$old_dir\"`"
  cd "$old_dir_parent"
  saved_old_dir_base="++saved-$old_dir_base.`date +%Y-%m-%d`.$$.bak"
  mv "$old_dir_base" "$saved_old_dir_base"
  old_dir="$old_dir_parent/$saved_old_dir_base"
fi


################################################################
# Make Temp Dir
# 

bail()
{
  cd "$output_dir"
  rm -rf "$tmpdir"
  exit 1
}

trap "printf \"update: interrupted -- cleaning up\\n\" 1>&2 ; bail" INT

rm -rf "$tmpdir"
mkdir "$tmpdir"


################################################################
# Get A Pristine Copy of Old-Dir's Latest Patch Level
# 

cd "$tmpdir"

if test ! -z "$quiet" ; then
  larch heading --sub "obtaining immediate ancestor revision of old-dir for comparison\\n"
fi

if larch library-find --silent -A "$archive" "$version--$old_dir_latest_lvl" > /dev/null ; then

  ancestor="`larch library-find -A \"$archive\" \"$version--$old_dir_latest_lvl\"`"

  if test ! -z "$quiet" ; then
    larch heading --sub --sub "found in library\\n"
  fi

else

  cache_dir="`dirname \"$old_dir\"`"
  set +e
  ancestor="`larch find-in-cache \"$cache_dir\" $version--$old_dir_latest_lvl`"
  status=$?
  set -e

  if test $status = 0 ; then
    if test ! -z "$quiet" ; then
      larch heading --sub --sub "found in cache\\n"
    fi
  else
    ancestor="$tmpdir/,,old-pristine"  
    mkdir "$ancestor"
    cd "$ancestor" 
    larch nested --sub larch build-revision --cache "$cache_dir" \
                                          $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
                                          "$archive/$version--$old_dir_latest_lvl"
  fi

fi


################################################################
# Get a Pristine Copy of the Latest Revision
# 

cd "$tmpdir"

if test ! -z "$quiet" ; then
  larch heading --sub "getting latest revision\\n"
fi

# Why `get' instead of build-revision?
# 
# The revision we're getting will be the latest ancestor of
# the new working directory.  `get' saves a copy of that revision
# in the pristine cache of the new working directory.  That will speed
# up subsequent udpates, commits, and diffs.
# 
larch nested --sub larch get --cache "`dirname \"$new_dir\"`" \
                           $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
                           "$archive/$version--$latest_lvl" ,,new-dir


################################################################
# We're Done Talking to the Archive
# 

# could kill with-archive session and unset some environment
# variables here


################################################################
# Compute Diff from Old-Dir to its Parent Revision
# 

if test ! -z "$quiet" ; then
  larch heading --sub "making patch from ancestor to old tree\\n"
fi

cd "$tmpdir"
larch nested --sub larch mkpatch \
                       $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
                       "$ancestor" "$old_dir" ,,old-patches

################################################################
# Apply Patches to New Dir
# 

if test ! -z "$quiet" ; then
  larch heading --sub "applying patch to latest revision\\n"
fi

cd "$tmpdir"
set +e
larch nested --sub larch dopatch \
                       $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
                       ,,old-patches ,,new-dir
patch_stat=$?
set -e

status=0

if test $patch_stat -eq 1 ; then

  status=1

elif test $patch_stat -ne 0 ; then

  printf "update: internal error applying patch\\n" 1>&2
  printf "  scratch dir: %s\\n" "$tmpdir" 1>&2
  printf "\\n" 1>&2
  exit 2

fi


################################################################
# Copy Goodies from Old Working Dir to New
# 

if test $patch_stat -eq 1 ; then
  larch heading --sub "copying non-source goodies from old project tree\\n"
fi

larch copy-tree-precious "$old_dir" ,,new-dir


###############################################################
# Set the Default Version In the New Directory
# 

cd "$tmpdir"
cd ,,new-dir/{arch}
rm -f ./++default-version
if test -e "$old_dir/{arch}/++default-version" ; then
  cp  "$old_dir/{arch}/++default-version" ++default-version
fi


################################################################
# Put the New Directory In Place
# 

cd "$tmpdir"
mv ,,new-dir "$new_dir"
cd "$new_dir"
rm -rf "$tmpdir"


###############################################################
# Bye
# 
if test $patch_stat -eq 1 -a ! -z "$quiet" ; then
  larch heading --sub "NOTICE: merge conflicts occured\\n"
  printf "  Look for \".rej\" files in %s\\n" "`basename \"$new_dir\"`" | larch body-indent --sub
fi

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

exit "$status"

