#!/bin/sh
# 
# replay.sh - build a project tree based on a replay 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 the latest revsion with local changes\\n"
                printf "usage: replay [options] old-dir new-dir [[archive/]version]\\n"
                printf "usage: replay --exact [options] old-dir new-dir [[archive/]revision]\\n"
                printf "usage: replay --in-place [options] dir [[archive/]version]\\n"
                printf "usage: replay --list FILE [options] old-dir new-dir\\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 " --exact                       apply one patch specifically\\n"
		printf " --in-place                    modify an existing directory\\n"
		printf " --list FILE                   read a list of patches to apply\\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 applying patches in the latest\\n"
                printf "revision of VERSION (or the default version of OLD-DIR) to the old working\\n"
		printf "directory, stopping after the first patch that causes conflicts.\\n"
                printf "\\n"
                printf "With the --exact option, the VERSION argument is mandatory, and must\\n"
                printf "explicitly specify a patch level.  That patch set, and no other will\\n"
                printf "be applied.\\n"
                printf "\\n"
                printf "With the --in-place option, only one directory is needed -- that directory\\n"
                printf "is modified rather than making a copy.\\n"
                printf "\\n"
                printf "With the --list option, read a list of patches to apply from FILE (- for\\n"
		printf "standard input).  Complete revision names should be listed, one per line.\\n"
		printf "replay will stop at the first patch in the list that causes a merge conflict,\\n"
		printf "leaving behind files with names of the form:\\n"
                printf "\\n"
                printf "    ,,replay.conflicts-in --    the name of the patch that\\n"
                printf "                                  caused conflicts\\n"
                printf "\\n"
                printf "    ,,replay.remaining    --    the list of patches not yet\\n"
                printf "                                  applied\\n"
                printf "\\n"
                printf "Options --list and --exact cancel each other.\\n"
                printf "\\n"
                exit 0
                ;;

      *)
                ;;
    esac
  done
fi

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

archroot=
archive=

exact=
list=
in_place=


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


while test $# -ne 0 ; do

  case "$1" in 

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


    -R|--root)          shift
                        if test $# -eq 0 ; then
                          printf "replay: -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 "replay: -A and --archive require an argument\\n" 1>&2
                          printf "try --help\\n" 1>&2
                          exit 1
                        fi
                        archive="$1"
                        shift
                        ;;

    --exact)		shift
    			exact=--exact
			list=
			list_file=
			;;

    --list)		shift
		        if test $# -eq 0 ; then
			  printf "replay: --list requires an argument\\n" 1>&2
			  printf "try --help\\n" 1>&2
			  exit 1
			fi
    			exact=
			list=--list
			list_file="$1"
			shift
			;;

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

    --)			shift
    			break
			;;
			
    -*)                 printf "replay: 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

if test ! -z "$exact" ; then
  min_args=$(($n_dir_args + 1))
  max_args=$(($n_dir_args + 1))
elif test ! -z "$list" ; then
  min_args=$(($n_dir_args))
  max_args=$(($n_dir_args))
else
  min_args=$(($n_dir_args))
  max_args=$(($n_dir_args + 1))
fi

if test $# -lt $min_args -o $# -gt $max_args; then
  printf "usage: replay [options] old-dir new-dir [[archive/]version]\\n" 1>&2
  printf "       replay --exact [options] old-dir new-dir [archive/]revision\\n" 1>&2
  printf "       replay --in-place [options] dir [archive/]revision\\n" 1>&2
  printf "       replay --list FILE [options] old-dir new-dir\\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`"

if test ! -z "$list" -a "$list_file" != "-" ; then
  cd "$here"
  cd "`dirname \"$list_file\"`"
  list_file="`pwd`/`basename \"$list_file\"`"
fi


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 "$list" ; then

  if test "$list_file" = "-" ; then
    patches="`cat`"
  else
    patches="`cat -- \"$list_file\"`"
  fi

  for patch in $patches ; do

    larch valid-package-name -e replay --patch-level -- "$patch"

    if test -z "$archive_version" ; then
      archive_version="$patch"
      archive="`larch parse-package-name -R \"$archroot\" -A \"$archive\" --arch \"$archive_version\"`"
      version="`larch parse-package-name --package-version \"$archive_version\"`"
    else
      a="`larch parse-package-name -R \"$archroot\" -A \"$archive\" --arch \"$patch\"`"
      v="`larch parse-package-name --package-version \"$patch\"`"

      if test "$archive" != "$a" -o "$version" != "$v" ; then
	printf "\\n" 1>&2
        printf "replay: heterogenous patch list\\n" 1>&2
	printf "  all patches in the list for --list must be from\\n" 1>&2
	printf "  the same archive and version.\\n" 1>&2
	printf "\\n" 1>&2
	printf "  archive of first patch: %s\\n" "$archive" 1>&2
	printf "  version of first patch: %s\\n" "$version" 1>&2
	printf "\\n" 1>&2
	printf "  problematic patch: %s\\n" "$patch" 1>&2
	printf "\\n" 1>&2
      fi
      
      leftover_patches="$leftover_patches $patch"
    fi

  done

  if test -z "$archive_version" ; then
    exit 0
  fi

  exact=--exact

fi

if test ! -z "$exact" ; then
  larch valid-package-name -e replay --patch-level -- "$archive_version"
elif test -z "$archive_version" ; then
  archive_version=`larch tree-version`
else
  larch valid-package-name -e replay --tolerant -- "$archive_version"
  if larch valid-package-name --patch-level "$archive_version" ; then
    printf "\\n" 1>&2
    printf "replay: patch level specification not allowed without --exact\\n" 1>&2
    printf "  version specifier: %s\\n" "$archive_version" 1>&2
    printf "\\n" 1>&2
  fi
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\"`"

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

if test "(" -z "$__restart" -a ! -z "$quiet" ")" -o ! -z "$report" ; then
  larch heading "replay\\n"
  printf "arguments: %s\\n"  "$command_line" | fold -w 60 | larch body-indent
  larch heading --sub "replay start time: %s\\n" "`date`"
  if test -z "$in_place" ; then
    larch heading --sub "old project tree: %s\\n" "$old_dir"
    larch heading --sub "new project tree: %s\\n" "$new_dir"
  else
    larch heading --sub "NOTE: modifying project tree in place (--in-place)\\n"
    larch heading --sub --sub "project tree: %s\\n" "$old_dir"
  fi
  if test ! -z "$list" ; then
    larch heading --sub "this is a list replay (--list)\\n"
  elif test ! -z "$exact" ; then
    larch heading --sub "this is an exact replay (--exact)\\n"
    larch heading --sub --sub "archive: %s\\n" "$archive"
    larch heading --sub --sub "revision: %s\\n" "$spec"
  else
    larch heading --sub "archive: %s\\n" "$archive"
    larch heading --sub "version spec: %s\\n" "$spec"
  fi
fi


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

with_archive_tail_call()
{
  list_option="$1"

  if test -z "$in_place" ; then
    exec larch with-archive -A "$archive"  \
	  	  larch replay \
			  -R "$archroot" -A "$archive" \
			  $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
			  ----restart \
			  $exact \
			  $list_option \
			  -- "$old_dir" "$new_dir" $archive_version
  else
    exec larch with-archive -A "$archive"  \
	  	  larch replay \
			  -R "$archroot" -A "$archive" \
			  $silent_opt $quiet_opt $report_opt $verbose_opt $debug_opt \
			  ----restart \
			  $exact \
			  $list_option \
			  --in-place \
			  -- "$old_dir" $archive_version
  fi
}


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

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


  if test -z "$leftover_patches" ; then
    with_archive_tail_call ""
  else
    x="$archive_version"
    archive_version=
    ( printf "%s\\n" "$x" ; 
      for patch in $leftover_patches ; do \
        printf "%s\\n" "$patch" ; \
      done ) \
    | with_archive_tail_call "--list -"
  fi

  exit 0
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 -1`"
fi

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


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

if test -z "$in_place" ; then
  tmpdirparent="$output_dir"
  tmpdir="$output_dir/,,replay.$$.$new_base"
else
  tmpdirparent="$new_dir"
  tmpdir="$new_dir" 
fi

bail()
{
  cd "$tmpdirparent"
  rm -rf "$tmpdir/,,patch"
  if test -z "$in_place" ; then
    rm -rf "$tmpdir"
  fi
  exit 1
}

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

if test -z "$in_place" ; then
  mkdir "$tmpdir"
fi


################################################################
# What Is the Latest Revision for Which Old-Dir has a Patch?
# 
# dead code
# 
# cd "$old_dir"
# 
# old_dir_latest_lvl="`larch log-ls -r $version | head -1`"
# 
# if test -z "$old_dir_latest_lvl" ; then
#   printf "replay: 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 Is the Latest Revision?
# 
# dead code
# 
# 
# latest_lvl="`larch revisions -R \"$archroot\" -A \"$archive\" -r \"$version\" | head -1`"
# 
# if test -z "$latest_lvl" ; then
#   printf "replay: no revisions in version\\n" 1>&2
#   printf "  version: %s/%s" "$archive" "$version" 1>&2
#   printf "\\n"
#   bail
# fi
# 

################################################################
# If Old-Dir is Already Up-to-Date, We're Done (with an error)
# 
# No... fill in missing patches.
# 
# if test "$latest_lvl" = "$old_dir_latest_lvl" ; then
#   printf "replay: already up-to-date\\n" 1>&2
#   printf "  project tree: %s\\n" "$old_dir" 1>&2
#   printf "\\n" 1>&2
#   bail
# fi
# 

################################################################
# Make a Copy of the Working Directory
# 

if test -z "$in_place" ; then
  if test ! -z "$quiet" ; then
    larch heading --sub "copying the project tree\\n"
    larch heading --sub --sub "copying source\\n"
  fi
  
  cd "$old_dir"
  larch inventory --source --all \
  | copy-file-list -- - . "$tmpdir"
  
  if test ! -z "$quiet" ; then
    larch heading --sub --sub "copying non-source goodies from old project tree\\n"
  fi
  
  larch copy-tree-precious "$old_dir" "$tmpdir"
fi


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

status=0

if test ! -z "$list" ; then
  #
  # Bad variable name: archive_version is guaranteed to be a fully qualified 
  # revision name here.  The rest of the leftover_patches might not include
  # a patch level, which isn't detected until "lvl=", below.  (bug?)
  #
  patch_list="$archive_version $leftover_patches"
elif test ! -z "$exact" ; then
  patch_list="`larch parse-package-name --lvl \"$archive_version\"`"
else
  patch_list="`larch whats-missing $archive/$version`"
fi

empty()
{
  test $# -eq 0
}

first()
{
  printf "%s\\n" "$1"
}

rest()
{
  shift
  printf "%s\\n" "$*"
}


while true ; do

  if empty $patch_list ; then
    break
  else
    lvl=`first $patch_list`
    patch_list=`rest $patch_list`
    conflict_name="$lvl"

    if test ! -z "$list" ; then
      lvl="`larch parse-package-name --lvl $lvl`"
    fi
  fi

  cd "$tmpdir"

  rm -rf ,,patch
  mkdir ,,patch
  cd ,,patch

  wftp-home
  wftp-cd $category/$branch/$version/$lvl

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

  if ! wftp-get $version--$lvl.patches.tar.gz > $version--$lvl.patches.tar.gz ; then
    printf "replay: unable to retrieve patch set\\n" 1>&2
    printf "  revision: %s\\n" $version--$lvl.patches.tar.gz 1>&2
    printf "\\n"
    bail
  fi

  tar -m -zxf $version--$lvl.patches.tar.gz 

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

  cd "$tmpdir"
  set +e
  larch nested --sub --sub larch dopatch ,,patch/$version--$lvl.patches .
  patch_stat=$?
  set -e

  if test $patch_stat -eq 1 ; then

    if test ! -z "$quiet" ; then
      larch heading --sub --sub "merge conflicts occured\\n"
      ( printf "  Look for \".rej\" files in %s\\n" "`basename \"$new_dir\"`" ; \
        printf "  Stopping after patch %s\\n" $version--$lvl ; \
        printf "\\n" ) \
      | larch body-indent --sub
    fi

    status=1

    rm -rf ,,replay.conflicts-in
    printf "%s\\n" "$conflict_name" > ,,replay.conflicts-in

    rm -rf ,,replay.remaining
    for p in $patch_list ; do
      printf "%s\\n" "$p" >> ,,replay.remaining
    done

    break

  elif test $patch_stat -ne 0 ; then

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

  fi

done

cd "$tmpdir"
rm -rf ,,patch


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

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



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

if test -z "$in_place" ; then
  cd "$output_dir"
  mv "$tmpdir" "$new_dir"
  cd "$new_dir"
fi

