# dopatch.sh - apply a patch directory (from mkpatch)
# 
################################################################
# 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 "apply a patch tree to a source tree\\n"
                printf "usage: dopatch [options] patchdir srcdir\\n"
                printf "\\n"
                printf " -h --help                     display help\\n"
                printf " -V --version                  print version info\\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 " --delete-removed              don't save removed files\\n"
                printf "\\n"
                printf " --explicit                    force the explicit tagging method\\n"
                printf " --implicit                    force the implicit tagging method\\n"
                printf " --names                       force the name-based tagging method\\n"
                printf "\\n"
                printf " --forward                     pass the --forward option to patch\\n"
                printf "\\n"
                printf " --reverse                     apply the patch in reverse\\n"
                printf "\\n"
                printf "Apply the patches in PATCHDIR to the source tree SRCDIR.\\n"
                printf "\\n"
                printf "See also \"mkpatch --help\".\\n"
                printf "\\n"
                exit 0
                ;;

      *)
                ;;
    esac
  done
fi

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

quiet=--quiet
report=--report
verbose=
debug=
debugx=
method=
forward=
reverse=

delete_removed=

while test "$#" -gt 0 ; do

  case "$1" in

    --forward)          shift
                        forward=--forward
                        ;;

    --reverse)          shift
                        reverse=--reverse
                        ;;

    --explicit)         shift
                        method=--explicit
                        ;;

    --implicit)         shift
                        method=--implicit
                        ;;

    --names)            shift   
                        method=--names
                        ;;

    --silent)   shift
                quiet=
                report=
                verbose=
                ;;

    --quiet)    shift
                quiet=--quiet
                report=
                verbose=
                ;;

    --report)   shift
                quiet=--quiet
                report=--report
                verbose=
                ;;

    --verbose)  shift
                quiet=--quiet
                report=--report
                verbose=--verbose
                ;;

    --debug)    shift
                larch heading "dopatch: debugging output enabled\\n"
                set -x
                debug=--debug
                debugx=-x
                ;;

    --delete-removed)   shift
                        delete_removed=--delete-removed
                        ;;

    --)         shift
                break
                ;;

    -*)         printf "mkpatch: unrecognized option (%s)\\n" "$1" 1>&2
                printf "\\n" 1>&2
                printf "Try \"larch mkpatch --help\"\\n" 1>&2
                exit 1
                ;;

    *)          break
                ;;

  esac


done

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

if test $# != 2 ; then
  printf "usage: dopatch [options] patchdir srcdir\\n" 1>&2
  printf "try --help\\n"  
  exit 2
fi

delta="$1"
tree="$2"

################################################################
# Make the directory names absolute
# 
start="`pwd`"

cd "$delta"
delta="`pwd`"

cd "$start"
cd "$tree"
tree="`pwd`"

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

if test ! -z "$quiet" ; then
  larch heading "dopatch\\n"
  printf "arguments: %s\\n" "$command_line" | fold -w 60 | larch body-indent
  larch heading --sub "dopatch start time: %s\\n" "`date`"
  larch heading --sub "project tree: %s\\n" "$tree"
  larch heading --sub "delta: %s\\n" "$delta"
  if test ! -z "$reverse" ; then
    larch heading --sub "reverse patch application\\n"
  fi
fi


################################################################
# Create a Place to Save Removed Files and Directories
# 

if test -z "$delete_removed" ; then
  archive="$tree/++removed-by-dopatch-`basename \"$patchdir\"`--`date +%Y%m%d.%H:%M:%S`"
  mkdir "$archive"

  if test ! -z "$quiet" ; then
    larch heading --sub "files removed by patch will be saved\\n"
    printf "%s\\n" "$archive" | larch body-indent --sub
  fi

else
  if test ! -z "$quiet" ; then
    larch heading --sub "files removed by patch will be deleted\\n"
  fi
fi


################################################################
# Make a Temporary Directory 
# 

tmpdir="$tree/,,dopatch.$$"

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

finish()
{
  cd "$tree"
  rm -rf "$tmpdir"
  exit 0
}

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

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




################################################################
# Consider --reverse
# 

if test -z "$reverse" ; then

  mod_files_index=mod-files-index
  mod_dirs_index=mod-dirs-index
  orig_files_index=orig-files-index
  orig_dirs_index=orig-dirs-index
  new_files_archive=new-files-archive
  new_dirs_metadata=modified-only-dir-metadata
  patches="$delta/patches"

else

  mod_files_index=orig-files-index
  mod_dirs_index=orig-dirs-index
  orig_files_index=mod-files-index
  orig_dirs_index=mod-dirs-index
  new_files_archive=removed-files-archive
  new_dirs_metadata=original-only-dir-metadata
  patches="$tmpdir/,,reversed-patches"

  # since this is a --reverse application, build a link tree
  # to $delta/patches, but using orig rather than mod paths,
  # and swapping orig and mod files.
  # 
  # diff output is left alone, but `--reverse' is passed along to `patch'.
  # 

  mkdir -p "$patches"
  cd "$tmpdir"

  all_patches_backwards="$tmpdir/,,all-patches-backwards"
  rev_patch_script="$tmpdir/,,rev-patch-script"

  ( cd "$delta/patches" && find . -type f ) \
  > "$all_patches_backwards"

  cat "$all_patches_backwards" \
  | awk -v orig_dirs_index="$delta/orig-dirs-index" \
        -v orig_files_index="$delta/orig-files-index" \
        -v mod_dirs_index="$delta/mod-dirs-index" \
        -v mod_files_index="$delta/mod-files-index" \
        -v rev_patches_root="$patches" \
        -v patches_root="$delta/patches" \
      \
      'BEGIN {
               while ((getline < mod_files_index) > 0)
                 {
                   mod_tag[$1] = $2;
                 }
               errchk();
               while ((getline < mod_dirs_index) > 0)
                 {
                   mod_tag[$1] = $2;
                 }
               errchk();
               while ((getline < orig_files_index) > 0)
                 {
                   orig_name[$2] = $1;
                 }
               errchk();
               while ((getline < orig_dirs_index) > 0)
                 {
                   orig_name[$2] = $1;
                 }
               errchk();

               close(mod_files_index);
               close(mod_dirs_index);
               close(orig_files_index);
               close(orig_dirs_index);

             }

       {
         patch_file = $0;


         # compute the name of the mod file or directory 
         # that corresponds to this patch file.
         # 
         # Also compute the (textual) suffix that derives
         # the patch name from the mod file or dir name.
         #
         mod_target = patch_file;
         mod_suffix = patch_file;
         if (match(mod_target, "/=dir-meta-(orig|mod)$"))
           {
             sub("/=dir-meta-(orig|mod)$", "", mod_target);
             sub(".*/", "/", mod_suffix);
           }
         else
           {
             sub("\\.[^./]*$", "", mod_target);
             sub(".*\\.", ".", mod_suffix);
           }


         # map the mod file or dir name into an orig name
         #
         orig_target = orig_name[mod_tag[mod_target]];


         # map the mod file or dir textual suffix into the
         # reversed suffix.
         # 
         orig_suffix = mod_suffix;

         if (orig_suffix == ".link-orig")
           orig_suffix = ".link-mod";
         else if (orig_suffix == ".link_mod")
           orig_suffix = ".link_orig";
         else if (orig_suffix == ".original")
           orig_suffix = ".modified";
         else if (orig_suffix == ".modified")
           orig_suffix = ".original";
         else if (orig_suffix == ".meta-orig")
           orig_suffix = ".meta-mod";
         else if (orig_suffix == ".meta-mod")
           orig_suffix = ".meta-orig";
         else if (orig_suffix == "/=dir-meta-orig")
           orig_suffix = "/=dir-meta-mod";
         else if (orig_suffix == "/=dir-meta-mod")
           orig_suffix = "/=dir-meta-orig";


         rev_patch_file = orig_target orig_suffix;

         rev_patch_dir = rev_patch_file;
         sub("/[^/]*$", "", rev_patch_dir);

         printf "mkdir -p \"%s/%s\"\n", rev_patches_root, rev_patch_dir;
         printf "ln \"%s/%s\" \"%s/%s\" 2>/dev/null || cp \"%s/%s\" \"%s/%s\" \n", patches_root, patch_file, rev_patches_root, rev_patch_file, patches_root, patch_file, rev_patches_root, rev_patch_file;
           
       }

       END {
           }

       function errchk()
       {
          if (ERRNO)
            {
              print ERRNO | "cat 1>&2";
              exit 1;
            }
       }

       ' \
  > "$rev_patch_script"

  $ARCH_SHELL -e "$rev_patch_script"

fi


################################################################
# Compute Many Things
# 

if test ! -z "$verbose" ; then
  larch heading --sub "inventory the tree being patched\\n"
  larch heading --sub --sub "start time: %s\\n" "`date`"
fi

cd "$tree"

# inventory tree
# 
tree_dir_index="$tmpdir/,,tree-dir-index"
tree_file_index="$tmpdir/,,tree-file-index"

larch inventory $method --source --all --directories --tags | sort -k 2 > "$tree_dir_index"
larch inventory $method --source --all --files --tags | sort -k 2 > "$tree_file_index"

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

# provide alternate sorts of mod indexes
# 
mod_file_index_by_name="$tmpdir/,,mod-file-index-by-name"
mod_dir_index_by_name="$tmpdir/,,mod-dir-index-by-name"

sort -k 1 "$delta/$mod_files_index" > "$mod_file_index_by_name"
sort -k 1 "$delta/$mod_dirs_index" > "$mod_dir_index_by_name"

original_only_file_ids="$tmpdir/,,original-only-file-ids"
modified_only_file_ids="$tmpdir/,,modified-only-file-ids"
original_only_dir_ids="$tmpdir/,,original-only-dir-ids"
modified_only_dir_ids="$tmpdir/,,modified-only-dir-ids"

join -v 1 -o 1.2 -1 2 -2 2 "$delta/$orig_files_index" "$delta/$mod_files_index" \
> "$original_only_file_ids"

join -v 1 -o 1.2 -1 2 -2 2 "$delta/$mod_files_index" "$delta/$orig_files_index" \
> "$modified_only_file_ids"

join -v 1 -o 1.2 -1 2 -2 2 "$delta/$orig_dirs_index" "$delta/$mod_dirs_index" \
> "$original_only_dir_ids"

join -v 1 -o 1.2 -1 2 -2 2 "$delta/$mod_dirs_index" "$delta/$orig_dirs_index" \
> "$modified_only_dir_ids"


# Figure out which removed files we actually have.
# 
# 
removed_files_index="$tmpdir/,,removed-files-index"

join -o 2.1,2.2 -1 1 -2 2 "$original_only_file_ids" "$tree_file_index" \
> "$removed_files_index"

# various lists about renamed files
# 
renamed_files="$tmpdir/,,renamed-files"
renamed_file_ids="$tmpdir/,,renamed-file-ids"
renamed_files_index="$tmpdir/,,renamed-files-index"
missing_renamed_files_index="$tmpdir/,,missing-renamed-files-index"

cat "$delta/$mod_files_index" \
| awk -f "$ARCH_SUBCMD_ROOT/patch-sets/compute-renamed.awk" \
      -v orig_dir_index="$delta/$orig_dirs_index" \
      -v mod_dir_index="$delta/$mod_dirs_index" \
      -v orig_X_index="$delta/$orig_files_index" \
| cut -s -d ' ' -f 1,2 \
| sort -k 2,2 \
> "$renamed_files"

join -o 2.2 -1 2 -2 1 "$renamed_files" "$mod_file_index_by_name" \
| sort \
> "$renamed_file_ids"

join -o 2.1,2.2 -1 1 -2 2 "$renamed_file_ids" "$tree_file_index" \
> "$renamed_files_index"

join -v 1 -o 1.1 -1 1 -2 2 "$renamed_file_ids" "$tree_file_index" \
| join -o 2.1,2.2 -1 1 -2 2 - "$delta/$orig_files_index" \
> "$missing_renamed_files_index"

renamed_dirs="$tmpdir/,,renamed-dirs"
renamed_dir_ids="$tmpdir/,,renamed-dir-ids"
renamed_dirs_index="$tmpdir/,,renamed-dirs-index"
missing_renamed_dirs_index="$tmpdir/,,missing-renamed-dirs-index"
removed_dirs_index="$tmpdir/,,removed-dirs-index"
missing_removed_dirs_index="$tmpdir/,,missing-removed-dirs-index"
shuffled_dirs_temp_names="$tmpdir/,,shuffled-dirs-temp-names"
shuffled_dirs_temp_names_forward_sort="$tmpdir/,,shuffled-dirs-temp-names-forward-sort"
shuffled_dirs_new_names="$tmpdir/,,shuffled-dirs-new-names"
shuffled_removed_dir_names="$tmpdir/,,shuffled-removed-dir-names"

cat "$delta/$mod_dirs_index" \
| awk -f "$ARCH_SUBCMD_ROOT/patch-sets/compute-renamed.awk" \
      -v orig_dir_index="$delta/$orig_dirs_index" \
      -v mod_dir_index="$delta/$mod_dirs_index" \
      -v orig_X_index="$delta/$orig_dirs_index" \
| cut -s -d ' ' -f 1,2 \
| sort -k 2,2 \
> "$renamed_dirs"

join -o 2.2 -1 2 -2 1 "$renamed_dirs" "$mod_dir_index_by_name" \
| sort \
> "$renamed_dir_ids"

join -o 2.1,2.2 -1 1 -2 2 "$renamed_dir_ids" "$tree_dir_index" \
> "$renamed_dirs_index"

join -v 1 -o 1.1 -1 1 -2 2 "$renamed_dir_ids" "$tree_dir_index" \
| join -o 2.1,2.2 -1 1 -2 2 - "$delta/$orig_dirs_index" \
> "$missing_renamed_dirs_index"

join -o 2.1,2.2 -1 1 -2 2 "$original_only_dir_ids" "$tree_dir_index" \
> "$removed_dirs_index"

join -v 1 -o 1.1 -1 1 -2 2 "$original_only_dir_ids" "$tree_dir_index" \
| join -o 2.1,2.2 -1 1 -2 2 - "$delta/$orig_dirs_index" \
> "$missing_removed_dirs_index"

cat "$renamed_dirs_index" "$removed_dirs_index" \
| cut -s -d ' ' -f 1 \
| sort -r \
| awk ' BEGIN { counter = 0 }
        {
          print  $1 "\t" counter ;
          counter = counter + 1
        } ' \
> "$shuffled_dirs_temp_names"

sort -k 1 "$shuffled_dirs_temp_names" > "$shuffled_dirs_temp_names_forward_sort"

join -o 1.1,2.1 -1 2 -2 2 "$renamed_dirs_index" "$delta/$mod_dirs_index" \
| sort -k 1 \
| join -o 1.2,2.2 -1 1 -2 1  - "$shuffled_dirs_temp_names_forward_sort" \
| sort -k 2 \
> "$shuffled_dirs_new_names"

cat  "$removed_dirs_index" \
| sort -k 1 \
| join -o 1.1,2.2 -1 1 -2 1 - "$shuffled_dirs_temp_names_forward_sort" \
| sort -k 1 \
> "$shuffled_removed_dir_names"

needed_new_directories="$tmpdir/,,needed-new-directories"
install_dirs_plan_raw="$tmpdir/,,install-dirs-plan-raw"
dirs_moved_in_tree="$tmpdir/,,dirs_moved_in_tree"
install_dirs_plan_cooked="$tmpdir/,,install-dirs-plan-cooked"
dirs_moved_sed_script="$tmpdir/,,dirs_moved_sed_script"

join -v 1 -o 1.1 -1 1 -2 2 "$modified_only_dir_ids" "$tree_dir_index" \
| join -o 2.1 -1 1 -2 2 - "$delta/$mod_dirs_index" \
| sort \
> "$needed_new_directories"


( sed -e "s/\$/ c --/" "$needed_new_directories" && \
  (  cat "$shuffled_dirs_new_names" \
   | sort -k 1 \
   | join -o 1.1,1.2,2.2 -1 1 -2 1 - "$mod_dir_index_by_name" ) ) \
| ( sed -e "s/\\([^ ]*\\)\\(.*\\) \\([^ ]*\\)$/\\1 \\2 \\1 \\3/" \
        -e "s,\\([^ ]*\\)/\\([^ /]*\\),\\1/ \\2," )\
| sort -k 1 -k 2 \
> "$install_dirs_plan_raw"  

join -o 1.1,2.1 -1 2 -2 1 "$delta/$mod_dirs_index"  "$tree_dir_index" \
| awk '{ if ($1 != $2) print $1 "\t" $2 }' \
| sort -k 1 -k 2 \
> "$dirs_moved_in_tree"

( sed -e '{
            s,[:.$*[\\],\\&,g
            s/^/s:^/
            s,$, ./:,
            s![ 	]!/:!
            s/$/\
                t/
          }' \
    "$dirs_moved_in_tree" ; \
  printf 's:^./:. ./:' \
) \
> "$dirs_moved_sed_script"

rm -f "$tmpdir/,tmp"
sed -f "$dirs_moved_sed_script" "$install_dirs_plan_raw" \
> "$tmpdir/,tmp"

( cat "$tmpdir/,tmp" \
  | sort -k 6 \
  | join -o 1.1,1.2,1.3,1.4,1.5,2.1 -1 6 -2 2 \
          - "$tree_dir_index" ; \
  cat "$tmpdir/,tmp" | grep -E -e '--$' ) \
| sort -k 2 \
> "$install_dirs_plan_cooked"

needed_new_files_raw="$tmpdir/,,needed-new-files-raw"
needed_new_files_cooked="$tmpdir/,,needed-new-files-cooked"
needed_new_files_script="$tmpdir/,,needed-new-files-script"

join -v 1 -o 1.1 -1 1 -2 2 "$modified_only_file_ids" "$tree_file_index" \
| join -o 2.1 -1 1 -2 2 - "$delta/$mod_files_index" \
| sort \
| sed -e 's,\(.*\)/\([^/]*\),\1/ \2 &,' \
> "$needed_new_files_raw"

sed -f "$dirs_moved_sed_script" "$needed_new_files_raw" \
> "$needed_new_files_cooked"

cat "$needed_new_files_cooked" \
| awk -v source="$delta/$new_files_archive" \
      -v dest="$tree" \
      '{
         if ($2 == "./")
            rel_path= "/"
         else
            rel_path= "/" substr($2,3) 

         print "printf \"%s\\\\n\" \"" $1 rel_path $3 "\""
         print "if test -h \"" source "/" $4 "\" ; then"
         print "  ln -s \"`read-link " source "/" $4 "`\" \"" dest "/" $1 rel_path $3 "\""
         print "else"
         print "  cp -p \"" source "/" $4 "\" \"" dest "/" $1 rel_path $3 "\""
         print "fi"
       }' \
> "$needed_new_files_script"

install_dirs_soft_conflicts="$tmpdir/,,install-dirs-soft-conflicts"
install_dirs_hard_conflicts="$tmpdir/,,install-dirs-hard-conflicts"

cat "$install_dirs_plan_cooked" \
| awk '{
         n_components = split (substr($2,3), components, "/");
         path = "" ; 
         for ( x = 1 ; x <= n_components ; ++x )
           {
             path = path components[x] "/";
             print $1 "/" path ;
           }
         if ( $4 == "c" )
           print $1 "/" $2 "/" $3
       } ' \
| sort -r -u \
> "$install_dirs_soft_conflicts"

cat "$install_dirs_plan_cooked" \
| awk '{
         if ( $4 != "c" )
           {
             if ($2 == "./")
               rel_path= "/"
             else
               rel_path= "/" substr($2,3) 

             print $1 rel_path $3
           }
       } ' \
| sort -r -u \
> "$install_dirs_hard_conflicts"


installed_dirs_script="$tmpdir/,,installed-dirs-script"

cat "$install_dirs_plan_cooked" \
| awk -v shuffle="$tmpdir/shuffled-dirs" \
      '{
         if ($2 == "./")
            rel_path= "/"
         else
            rel_path= "/" substr($2,3)

         if ($4 == "c")
           {
             print "printf \"%s\\\\n\" \"" $1 rel_path $3 "\""
             print "mkdir -p \"" $1 rel_path $3 "\""
           }
         else
           {
             print "printf \"%s\\\\n => %s\\\\n\" \"" $6 "\" \"" $1 rel_path $3 "\""
             print "mkdir -p \"" $1 rel_path "\""
             print "mv \"" shuffle "/" $4 "\" \"" $1 rel_path $3 "\""
           }
       }' \
> "$installed_dirs_script"

removed_dirs_script="$tmpdir/,,removed-dirs-script"

if test -z "$delete_removed" ; then
  cat "$shuffled_removed_dir_names" \
  | awk -v shuffle="$tmpdir/shuffled-dirs" \
        -v archive="$archive" \
        '{
           print "printf \"%s\\\\n\" \"" $1 "\""
           print "mkdir -p \"" archive "/`dirname \\\"" $1 "\\\"`\"" ;
           print "mv \"" shuffle "/" $2 "\" \"" archive "/" $1 "\""
         }' \
  > "$removed_dirs_script"
fi


renamed_files_plan_raw="$tmpdir/,,renamed-files-plan-raw"
renamed_files_plan_cooked="$tmpdir/,,renamed-files-plan-cooked"

join -o 2.1 -1 1 -2 2 "$renamed_file_ids" "$delta/$mod_files_index" \
| sort \
| sed -e 's,\(.*\)/\(.*\),\1/ \2 &,' \
> "$renamed_files_plan_raw"

sed -f "$dirs_moved_sed_script" "$renamed_files_plan_raw" \
> "$renamed_files_plan_cooked"


install_files_soft_dir_conflicts="$tmpdir/,,install-files-soft-dir-conflicts"
install_files_hard_conflicts="$tmpdir/,,install-files-hard-conflicts"

cat "$needed_new_files_cooked" "$renamed_files_plan_cooked" \
| awk '{
         n_components = split (substr($2,3), components, "/");
         path = "" ;
         for ( x = 1 ; x <= n_components ; ++x )
           {
             path = path components[x] "/";
             print $1 "/" path ;
           }
        }' \
| sort -u  \
> "$install_files_soft_dir_conflicts"

cat "$needed_new_files_cooked" "$renamed_files_plan_cooked" \
| awk '{
         if ($2 == "./")
            rel_path= "/"
         else
            rel_path= "/" substr($2,3) 

         print $1 rel_path $3
        } ' \
| sort -u  \
> "$install_files_hard_conflicts"

renamed_files_mod_old_tree="$tmpdir/,,renamed-files-mod-old-tree"

join -o 2.1,1.1 -1 2 -2 2 "$renamed_files_index" "$delta/$mod_files_index" \
| sort -k 1 \
> "$renamed_files_mod_old_tree"

renamed_files_plan_plated="$tmpdir/,,renamed-files-plan-plated"

join -o 2.1,2.2,2.3,1.2 -1 1 -2 4 \
     "$renamed_files_mod_old_tree" \
     "$renamed_files_plan_cooked" \
| sort -k 2 \
> "$renamed_files_plan_plated"



renamed_files_script="$tmpdir/,,renamed-files-script"

cat "$renamed_files_plan_plated" \
| awk -v source="$tmpdir/renamed-files" \
      -v tree="$tree" \
      '{
         if ($2 == "./")
            rel_path= "/"
         else
            rel_path= "/" substr($2,3) 

         print "printf \"%s\\\\n => %s\\\\n\" \"" $4 "\" \"" $1 rel_path $3 "\""

         print "mv \"" source "/" $4 "\" \"" tree "/" $1 rel_path $3 "\""
       }' \
> "$renamed_files_script"


tree_file_renames="$tmpdir/,,tree-file-renames"
tree_dir_renames="$tmpdir/,,tree-dir-renames"

join -o 2.1,2.2,2.3,1.2 -1 1 -2 4 \
     "$mod_file_index_by_name" "$renamed_files_plan_cooked" \
| sort -k 4 \
| join -o 1.1,1.2,1.3,2.1 -1 4 -2 2 - "$tree_file_index" \
| awk '{
         if ($2 == "./")
            rel_path= "/"
         else
            rel_path= "/" substr($2,3)

         print $4 " " $1 rel_path $3
       }' \
| sort -r -k 1 \
> "$tree_file_renames"


cat "$install_dirs_plan_cooked" \
| awk '{
         if ($4 != "c")
           {
             if ($2 == "./")
               rel_path= "/"
             else
               rel_path= "/" substr($2,3)

             print $6 " " $1 rel_path $3
           }
       }' \
| sort -r -k 1 \
> "$tree_dir_renames"

tree_dir_renames_sed_script="$tmpdir/,,tree-dir-renames-sed-script"

cat "$tree_dir_renames" \
| sed -e '{
            s,[:.$*[\\],\\&,g
            s/^/s:^/
            s, ,\\([/	]\\):,
            s,$,\\1:\
                t,
          }' \
> "$tree_dir_renames_sed_script"


rearranged_tree_dir_index="$tmpdir/,,rearranged-tree-dir-index"

join -v 1 -o 1.1,1.2 -1 2 -2 2 "$tree_dir_index" "$removed_dirs_index" \
| sed -e "s/ /	/" \
| sed -f "$tree_dir_renames_sed_script" \
> "$rearranged_tree_dir_index"


rearrangements_summary="$tmpdir/,,rearrangements-summary"

(  cat "$tree_dir_renames" "$tree_file_renames" \
   | sed -e 's/^/r /' ; \
   \
   cut -s -d ' ' -f 1 "$removed_dirs_index" \
   | sed -e 's/^/d /' ) \
| sort -r -k 2 \
> "$rearrangements_summary"

rearrangements_summary_sed_script=",,rearrangements-summary-sed-script"

tree_dir_and_file_renames_sed_script="$tmpdir/,,tree-dir-and-file-renames-sed-script"

cat "$rearrangements_summary" \
| sed -e 's,[/.$*[\\],\\&,g' \
      -e '/^r / { 
                  s,^r ,s/^,
                  s, ,\\([/	]\\)/,
                  s,$,\\1/\
                          t,
                }' \
      -e '/^d / {
                  s,^d ,/,
                  s,$,\\([/	]\\)/d,
                }' \
> "$tree_dir_and_file_renames_sed_script"

rearranged_tree_file_index="$tmpdir/,,rearranged-tree-file-index"

join -v 1 -o 1.1,1.2 -1 2 -2 2 "$tree_file_index" "$removed_files_index" \
| sed -e "s/ /	/" \
| sed -f "$tree_dir_and_file_renames_sed_script" \
> "$rearranged_tree_file_index"


final_tree_file_index="$tmpdir/,,final-tree-file-index"
final_tree_dir_index="$tmpdir/,,final-tree-dir-index"
final_tree_dir_index_by_name="$tmpdir/,,final-tree-dir-index-by-name"

(  join -o 1.1,1.2,1.3,2.2 -1 4 -2 1 "$needed_new_files_cooked" "$mod_file_index_by_name" \
 | awk '{
          if ($2 == "./")
            rel_path= "/"
          else
            rel_path= "/" substr($2,3)

          print $1 rel_path $3 "\t" $4
        }' ; \
 cat "$rearranged_tree_file_index" ) \
| sort -k 2 \
> "$final_tree_file_index"

(  cat "$install_dirs_plan_cooked" \
 | awk '{ if ($4 == "c") print $1 " " $2 " " $3 " " $5 }' \
 | join -o 1.1,1.2,1.3,2.2 -1 4 -2 1 - "$mod_dir_index_by_name" \
 | awk '{
          if ($2 == "./")
            rel_path= "/"
          else
            rel_path= "/" substr($2,3)

          print $1 rel_path $3 "\t" $4
        }' ; \
 cat "$rearranged_tree_dir_index" ) \
| sort -k 2 \
> "$final_tree_dir_index"

cat "$final_tree_dir_index" | sort -k 1,1 > "$final_tree_dir_index_by_name"

final_tree_auto_changelogs_index="$tmpdir/,,final-tree-auto-changelogs-index"

archive_re="[-a-zA-Z0-9]+(\\.[-a-zA-Z0-9]*)*@[-a-zA-Z0-9.]*(--[-a-zA-Z0-9.]*)?" 
base_re="([a-zA-Z]([a-zA-Z0-9]*(-[a-zA-Z0-9]+)?)*)"
vsn_re="([0-9]+\\.[0-9]+)"
changelog_tag_re="^[ix]_automatic-ChangeLog--($archive_re)/($base_re)(--$base_re)?(--$vsn_re)$"

cat "$final_tree_file_index" \
| awk '{ print $2; }' \
| grep -E -e "$changelog_tag_re" \
| sed -e 's%^\([ix]_automatic-ChangeLog--\)\(.*\)/\(.*\)--\(.*\)--\(.*\)%./{arch}/\3/\3--\4/\3--\4--\5/\2/patch-log	&%' \
      -e t \
      -e 's%^\([ix]_automatic-ChangeLog--\)\(.*\)/\(.*\)--\(.*\)%./{arch}/\3/\3/\3--\4/\2/patch-log	&%' \
| sort -k 1 \
| join -o 1.2 -1 1 -2 1 - "$final_tree_dir_index_by_name" \
| sort \
| join -o 2.1,2.2 -1 1 -2 2 - "$final_tree_file_index" \
> "$final_tree_auto_changelogs_index"

final_tree_missing_changelog_tags="$tmpdir/,,final-tree-missing-changelogs-tags"

cat "$delta/$mod_files_index" \
| awk '{ print $2; }' \
| grep -E -e "$changelog_tag_re" \
| join -v 1 -o 1.1 -1 1 -2 2 - "$final_tree_file_index" \
> "$final_tree_missing_changelog_tags"


all_patches="$tmpdir/,,all-patches"

( cd "$patches" && find . -type f ) \
> "$all_patches"

raw_patch_list="$tmpdir/,,raw-patch-list"
patch_list="$tmpdir/,,patch-list"
patch_map="$tmpdir/,,patch-map"

grep -E -e '\.patch$' "$all_patches" \
| sed -e 's/\.patch$//' \
| sort \
> "$raw_patch_list"

# remove patches for changelogs that are automatic in the final tree
# or missing from the final tree.
# 
cat "$raw_patch_list" \
| join -o 2.1,2.2 -1 1 -2 1 - "$mod_file_index_by_name" \
| sort -k 2 \
| join -v 1 -o 1.1,1.2 -1 2 -2 1 - "$final_tree_missing_changelog_tags" \
| join -v 1 -o 1.1 -1 2 -2 2 - "$final_tree_auto_changelogs_index" \
| sort \
> "$patch_list"

join -o 1.1,2.2 -1 1 -2 1 "$patch_list" "$mod_file_index_by_name" \
| sort -k 2 \
| join -o 2.1,1.1 -1 2 -2 2 - "$rearranged_tree_file_index" \
| sort -k 1 \
> "$patch_map"

link_patch_list="$tmpdir/,,link-patch-list"
link_patch_map="$tmpdir/,,link-patch-map"

grep -E -e '\.link-mod$' "$all_patches" \
| sed -e 's/.link-mod$//' \
| sort \
> "$link_patch_list"

join -o 1.1,2.2 -1 1 -2 1 "$link_patch_list" "$mod_file_index_by_name" \
| sort -k 2 \
| join -o 2.1,1.1 -1 2 -2 2 - "$rearranged_tree_file_index" \
| sort -k 1 \
> "$link_patch_map"

binary_patch_list="$tmpdir/,,binary-patch-list"
binary_patch_map="$tmpdir/,,binary-patch-map"

grep -E -e '\.modified$' "$all_patches" \
| sed -e 's/.modified$//' \
| sort \
> "$binary_patch_list"

join -o 1.1,2.2 -1 1 -2 1 "$binary_patch_list" "$mod_file_index_by_name" \
| sort -k 2 \
| join -o 2.1,1.1 -1 2 -2 2 - "$rearranged_tree_file_index" \
| sort -k 1 \
> "$binary_patch_map"


metadata_patch_list="$tmpdir/,,metadata-patch-list"
metadata_patch_map="$tmpdir/,,metadata-patch-list"

grep -E -e '\.meta-mod$' "$all_patches" \
| sed -e 's/\.meta-mod$//' \
| sort \
> "$metadata_patch_list"

join -o 1.1,2.2 -1 1 -2 1 "$metadata_patch_list" "$mod_file_index_by_name" \
| sort -k 2 \
| join -o 2.1,1.1 -1 2 -2 2 - "$rearranged_tree_file_index" \
| sort -k 1 \
> "$metadata_patch_map"

dir_metadata_patch_list="$tmpdir/,,dir-metadata-patch-list"
dir_metadata_patch_map="$tmpdir/,,dir-metadata-patch-map"

grep -E -e '/=dir-meta-mod$' "$all_patches" \
| sed -e 's,/=dir-meta-mod$,,' \
| sort \
> "$dir_metadata_patch_list"

join -o 1.1,2.2 -1 1 -2 1 "$dir_metadata_patch_list" "$mod_dir_index_by_name" \
| sort -k 2 \
| join -o 2.1,1.1 -1 2 -2 2 - "$rearranged_tree_dir_index" \
| sort -k 1 \
> "$dir_metadata_patch_map"


missing_file_patch_list="$tmpdir/,,missing-file-patch-list"

( ( \
    join -o 2.1,2.2 -1 1 -2 1 "$patch_list" "$mod_file_index_by_name" ; \
    join -o 2.1,2.2 -1 1 -2 1 "$link_patch_list" "$mod_file_index_by_name" ; \
    join -o 2.1,2.2 -1 1 -2 1 "$binary_patch_list" "$mod_file_index_by_name" ; \
    join -o 2.1,2.2 -1 1 -2 1 "$metadata_patch_list" "$mod_file_index_by_name"  \
  ) \
  | sort -k 2 -u \
  | join -v 1 -o 1.1 -1 2 -2 2 - "$tree_file_index" ; \
  \
  join -o 2.1,2.2 -1 1 -2 1 "$dir_metadata_patch_list" "$mod_dir_index_by_name" \
  | sort -k 2 -u \
  | join -v 1 -o 1.1 -1 2 -2 2 - "$tree_dir_index"  \
) \
| awk '{
         printf "test -e \"%s.link-orig\" && echo \"%s.link-orig\"\n", $0, $0;
         printf "test -e \"%s.link-mod\" && echo \"%s.link-mod\"\n", $0, $0;
         printf "test -e \"%s.original\" && echo \"%s.original\"\n", $0, $0;
         printf "test -e \"%s.modified\" && echo \"%s.modified\"\n", $0, $0;
         printf "test -e \"%s.patch\" && echo \"%s.patch\"\n", $0, $0;
         printf "test -e \"%s.meta-orig\" && echo \"%s.meta-orig\"\n", $0, $0;
         printf "test -e \"%s.meta-mod\" && echo \"%s.meta-mod\"\n", $0, $0;
         printf "test -e \"%s/=dir-meta-orig\" && echo \"%s/=dir-meta-orig\"\n", $0, $0;
         printf "test -e \"%s/=dir-meta-mod\" && echo \"%s/=dir-meta-mod\"\n", $0, $0;
       }' \
| ( cd "$patches" && $ARCH_SHELL ) \
| cat \
> "$missing_file_patch_list"



################################################################
# Archive and Delete Removed Files
# 

if test ! -z "`head -n 1 \"$removed_files_index\"`" ; then
  if test ! -z "$report" ; then
    if test -z "$delete_removed" ; then 
      larch heading --sub "archive and delete removed files\\n"
    else
      larch heading --sub "delete removed files\\n"
    fi
    if test ! -z "$verbose" ; then
      larch heading --sub --sub "start time: %s\\n" "`date`"
    fi
  fi
  
  if test -z "$delete_removed" ; then
    cd "$tree" ;

    if test ! -z "$report" ; then
      if test ! -z "$verbose" ; then
        larch heading --sub --sub "file list\\n"
        verb_sub=--sub
      else
        verb_sub=
      fi
      cut -s -d ' ' -f 1 "$removed_files_index" \
      | sort \
      | tar -T - "-vcf" "$tmpdir/,,lost-files.tar" \
      | larch body-indent --sub $verb_sub
    else
      cut -s -d ' ' -f 1 "$removed_files_index" \
      | sort \
      | tar -T - "-cf" "$tmpdir/,,lost-files.tar"
    fi

  fi
  
  cd "$tree" 
  cut -s -d ' ' -f 1 "$removed_files_index" | xargs need-args 'rm -rf'
  
  if test ! -z "$verbose" ; then
    larch heading --sub --sub "finish time: %s\\n" "`date`"
  fi
fi

################################################################
# Set Aside Renamed Files
# 

if test ! -z "$verbose" ; then
  larch heading --sub "set aside renamed files\\n"
  larch heading --sub --sub "start time: %s\\n" "`date`"
fi

# set aside the renamed files
# 
mkdir "$tmpdir/renamed-files"

cut -s -d ' ' -f 1 "$renamed_files_index" \
| copy-file-list -- - "$tree" "$tmpdir/renamed-files"

# remove the renamed files
# 
cd "$tree"
cut -s -d ' ' -f 1 "$renamed_files_index" | xargs need-args 'rm -rf'

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

################################################################
# Set Aside Renamed and Removed Directories
# 

if test ! -z "$verbose" ; then
  larch heading --sub "set aside renamed and removed directories\\n"
  larch heading --sub --sub "start time: %s\\n" "`date`"
fi

mkdir "$tmpdir/shuffled-dirs"

OLD_IFS="$IFS"

IFS="
"
for pair in `cat "$shuffled_dirs_temp_names"` ; do

  IFS="$OLD_IFS"

  dir_name=${pair%	*}
  tmp_name=${pair##*	}

  mv "$tree/$dir_name" "$tmpdir/shuffled-dirs/$tmp_name"

done

IFS="$OLD_IFS"

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

################################################################
# How to Deal With Pre-existing .orig and .rej Files
# 

made_rej_files=

preserve_old_patch_spew()
{
  here="`pwd`"

  cd "$tree"
  file="$1"

  if test -h "$file.orig" -o -e "$file.orig" ; then
    if test -z "$delete_removed" ; then 
      mkdir -p "$archive/`dirname \"$file\"`"
      mv "$file.orig" "$archive/`dirname \"$file\"`"
    else
      rm "$file.orig"
    fi
  fi

  if test -h "$file.rej" -o -e "$file.rej" ; then
    if test -z "$delete_removed" ; then
      mkdir -p "$archive/`dirname \"$file\"`"
      mv "$file.rej" "$archive/`dirname \"$file\"`"
    else
      rm "$file.rej"
    fi
  fi

  cd "$here"
}

################################################################
# How to Deal With Conflicts for New and Renamed Directories
# 

deferred_conflict()
{
  made_rej_files=yes

  here="`pwd`"

  cd "$tree"
  conflict_file="$1"

  preserve_old_patch_spew "$conflict_file"

  mv "$1" "$1.orig"
  printf "%s\\n" "$conflict_file" >> "$tmpdir/,,deferred_conflicts"

  cd "$here"
}


################################################################
# Eliminate Directory Conflicts for New and Renamed Directories
# 


any_dir_conflicts=

dir_dir_conflicts_heading()
{
  if test ! -z "$report" ; then
    larch heading --sub "check for conflicts with new or renamed directories\\n"
  fi
  any_dir_conflicts=yes
}

cd "$tree"


for dir in `cat "$install_dirs_hard_conflicts"` ; do

  if test -h "$dir" -o  -e "$dir" ; then
    deferred_conflict "$dir"
    dir_dir_conflicts_heading
    if test ! -z "$report" ; then
      printf "%s\\n" "$dir" | larch body-indent --sub --nonl
    fi
  fi

done

for dir in `cat "$install_dirs_soft_conflicts"` ; do

  if test "(" -h "$dir" -o -e "$dir" ")" -a ! -d "$dir" ; then
    deferred_conflict "$dir"
    dir_dir_conflicts_heading
    if test ! -z "$report" ; then
      printf "%s\\n" "$dir" | larch body-indent --sub --nonl
    fi
  fi

done

if test ! -z "$report" -a ! -z "$any_dir_conflicts" ; then
  printf "\\n"
fi


################################################################
# Put the Renamed and New Directories In New Locations
# 

if test ! -z "$report" -a ! -z "`head -n 1 \"$installed_dirs_script\"`" ; then
  larch heading --sub "put the renamed and new directories in new locations\\n"
  cat "$installed_dirs_script" | $ARCH_SHELL | larch body-indent --sub
else
  cat "$installed_dirs_script" | $ARCH_SHELL > /dev/null
fi


################################################################
# Put Removed Dirs in the Archive
# 

if test -z "$delete_removed" ; then

  if test ! -z "$report" -a ! -z "`head -n 1 \"$removed_dirs_script\"`" ; then
    larch heading --sub "put removed dirs in the archive\\n"
    cat "$removed_dirs_script" | $ARCH_SHELL | larch body-indent --sub
  else
    cat "$removed_dirs_script" | $ARCH_SHELL > /dev/null
  fi

fi

################################################################
# Put Removed Files in the Archive
# 

if test -z "$delete_removed" -a ! -z "`head -n 1 \"$removed_files_index\"`" ; then

  cd "$archive"

  if test ! -z "$verbose" ; then
    larch heading --sub "put removed files in the archive\\n"
    tar -m -xvpf "$tmpdir/,,lost-files.tar" | larch body-indent --sub
  else
    tar -m -xpf "$tmpdir/,,lost-files.tar"
  fi

fi

################################################################
# Eliminate Directory Conflicts for New and Renamed Files
# 

any_dir_conflicts=

dir_file_conflicts_heading()
{
  if test ! -z "$report" ; then
    larch heading --sub "check for conflicts with new or renamed files\\n"
  fi
  any_dir_conflicts=yes
}

cd "$tree"

for file in `cat "$install_files_hard_conflicts"` ; do

  if test -h "$file" -o -e "$file" ; then
    deferred_conflict "$file"
    dir_file_conflicts_heading
    if test ! -z "$report" ; then
      printf "%s\\n" "$file" | larch body-indent --sub --nonl
    fi
  fi

done

for dir in `cat "$install_files_soft_dir_conflicts"` ; do

  if test "(" -h "$dir" -o -e "$dir" ")" -a ! -d "$dir" ; then
    deferred_conflict "$dir"
    dir_file_conflicts_heading
    if test ! -z "$report" ; then
      printf "%s\\n" "$file" | larch body-indent --sub --nonl
    fi
    any_dir_conflicts=yes
  fi

done

for dir in `cat $install_files_hard_conflicts | xargs -n 1 need-args dirname` ; do
  mkdir -p "$tree/$dir"
done

if test ! -z "$report" -a ! -z "$any_dir_conflicts" ; then
  printf "\\n"
fi


################################################################
# Put Renamed Files in New Locations
# 


if test ! -z "$report" -a ! -z "`head -n 1 \"$renamed_files_script\"`" ; then
  larch heading --sub "install renamed files\\n"
  cat "$renamed_files_script" | $ARCH_SHELL | larch body-indent --sub
else
  cat "$renamed_files_script" | $ARCH_SHELL > /dev/null
fi


################################################################
# Add New Files
# 

if test ! -z "$report" -a ! -z "`head -n 1 \"$needed_new_files_script\"`" ; then
  larch heading --sub "add new files\\n"
  cat "$needed_new_files_script" | $ARCH_SHELL | larch body-indent --sub
else
  cat "$needed_new_files_script" | $ARCH_SHELL > /dev/null
fi



################################################################
# Apply Regular File Patches
# 

has_body=
apply_patch_msg()
{
  if test ! -z "$report" ; then
    if test -z "$has_body" ; then
      has_body=yes
      larch heading --sub "apply patches\\n"
    fi
    printf "%s %s\\n" "$1" "$2" | larch body-indent --sub --nonl
  fi
}

apply_patch_msg_finish()
{
  if test ! -z "$report" -a ! -z "$has_body" ; then
    printf "\\n"
  fi
}

OLD_IFS="$IFS"
IFS="
"
for m in `cat "$patch_map"` ; do

  IFS="$OLD_IFS"

  tree_file="${m%% *}"
  mod_file="${m#* }"

  tree_file_base="`basename \"$tree_file\"`"
  tree_file_dir="`dirname \"$tree_file\"`"

  cd "$tree"
  cd "$tree_file_dir"

  preserve_old_patch_spew "$tree_file"

  if test -h "$tree_file_base" ; then

    made_rej_files=yes
    mv "$tree_file_base" "$tree_file_base.orig"
    cp "$patches/$mod_file.patch" "$tree_file_base.rej"
    apply_patch_msg "C " "$tree_file"

  elif test ! -e "$tree_file_base" ; then

    # this really shouldn't ever be reached!
    #

    made_rej_files=yes
    touch "$tree_file_base.orig"
    chmod ugo-rwx "$tree_file_base.orig"
    cp "$patches/$mod_file.patch" "$tree_file_base.rej"
    apply_patch_msg "??" "$tree_file"
    
  else

    # 
    # TODO: insert long humorous flame about how GNU patch doesn't 
    #       come close to a reasonable interpretation of the Posix
    #       standard, even with POSIXLY_CORRECT and how there doesn't
    #       seem to be a free replacement that does better.  Problem:
    #       it wouldn't be all that funny.
    # 
    #       Alternative: write a new version of patch just for arch.
    # 

    # CRITICAL: dopatch must *not* modify the original file in place.  
    # It *must* create a new output file.  This is in case the original
    # file is a hard link to a file we don't want to modify.
    # 
    mv "$tree_file_base" ",,dopatch.$$.$tree_file_base"

    # given an empty patch file, patch does the wrong thing
    # (creates an empty output file).   Some patch sets 
    # contain empty patches for automatic ChangeLogs.
    # If those ChangeLogs aren't automatic in the final tree,
    # we wind up here, so:
    # 
    if test -s "$patches/$mod_file.patch" ; then
      set +e
      rm -f "$tmpdir/,,patch-output"
      patch $forward $reverse -f -s --posix -i "$patches/$mod_file.patch" -o "$tree_file_base" ",,dopatch.$$.$tree_file_base" > "$tmpdir/,,patch-output" 2>&1 < /dev/null
      patchstat=$?
      set -e
    else
      cp ",,dopatch.$$.$tree_file_base" "$tree_file_base"
      patchstat=0
    fi

    if test $patchstat -eq 0 ; then

      set-file-metadata `file-metadata --symlink --permissions ",,dopatch.$$.$tree_file_base"` "$tree_file_base"
      rm ",,dopatch.$$.$tree_file_base"
      apply_patch_msg "M " "$tree_file"

    elif test $patchstat -eq 1 ; then

      made_rej_files=yes
      set-file-metadata `file-metadata --symlink --permissions ",,dopatch.$$.$tree_file_base"` "$tree_file_base"
      mv ",,dopatch.$$.$tree_file_base" "$tree_file_base.orig"
      apply_patch_msg "C " "$tree_file"
      if test ! -z "$verbose" ; then
        cat "$tmpdir/,,patch-output" | larch body-indent --sub --nonl
      fi

    else

      printf "dopatch: internal error (patch)\\n" 1>&2
      printf "  patch exited with status > 1\\n" 1>&2
      printf "  command: %s" "patch -f -s --posix -c -i \"$patches/$mod_file.patch\" -o \"$tree_file_base\" \",,dopatch.$$.$tree_file_base\"" 1>&2
      printf "\\n" 1>&2
      cat "$tmpdir/,,patch-output" 1>&2
      printf "\\n" 1>&2
      exit 2

    fi
  fi

done
IFS="$OLD_IFS"


# 
# Fix up all automated ChangeLogs -- even those not in the 
# patch.
# 
for log_pair in `cat "$final_tree_auto_changelogs_index"  | awk '{ printf "%s////%s\n", $1, $2; }'` ; do

  log="${log_pair%%////*}"
  cl_version="${log_pair#*////}"
  cl_version="${cl_version#[ix]_automatic-ChangeLog--}"


  # CRITICAL: the output file must not be modified in place since it  
  # may be a hard link to a file we don't want to modify.  I don't
  # think there is anything that prevents shell redirction (>) from
  # opening an existing file with O_TRUNC.
  # 
  apply_patch_msg "Ch" "$log"
  perms="`file-metadata --symlink --permissions \"$tree/$log\"`"
  
  if head -n 5 "$tree/$log" | grep -E -q "^(# non-tag: automatic-ChangeLog--|# line of development: )" ; then
    rm "$tree/$log"
    larch changelog --untagged --dir "$tree" "$cl_version" > "$tree/$log"
  else
    rm "$tree/$log"
    larch changelog --dir "$tree" "$cl_version" > "$tree/$log"
  fi
      
  set-file-metadata $perms "$tree/$log"

done



################################################################
# Patch Symbolic Links
# 

cd "$tree"

OLD_IFS="$IFS"
IFS="
"
for m in `cat "$link_patch_map"` ; do

  IFS="$OLD_IFS"

  tree_file="${m%% *}"
  mod_file="${m#* }"

  preserve_old_patch_spew "$tree_file"

  new_target="`cat \"$patches/$mod_file.link-mod\"`"

  if test ! -h "$tree_file" -a ! -e "$tree_file" ; then

    # this shouldn't really be reached
    # 

    made_rej_files=yes
    touch "$tree_file.orig"
    chmod ugo-rwx "$tree_file.orig"
    ln -s "$new_target" "$tree_file.rej"
    apply_patch_msg "??" "$tree_file"


  elif test -e "$patches/$mod_file.link-orig" ; then

    # patch specifies retargetting a symbolic link
    #

    orig_target="`cat \"$patches/$mod_file.link-orig\"`"

    if test -e "$patches/$mod_file.meta-orig" ; then
      orig_metadata="`cat \"$patches/$mod_file.meta-orig\"`"
    else
      orig_metadata=
    fi

    made_link_rej=

    if test ! -h "$tree_file" ; then

      made_rej_files=yes
      made_link_rej=yes
      mv "$tree_file" "$tree_file.orig"
      ln -s "$new_target" "$tree_file.rej"
      set-file-metadata `file-metadata --symlink --permissions "$tree_file.orig"` "$tree_file.rej"
      apply_patch_msg "C>" "$tree_file"

    else      

      old_target_tree="`read-link \"$tree_file\"`"
      
      if test "$old_target_tree" = "$orig_target" ; then

        metadata="`file-metadata --symlink --permissions \"$tree_file\"`"
        rm "$tree_file"
        ln -s "$new_target" "$tree_file"
        set-file-metadata $metadata "$tree_file"
        apply_patch_msg "->" "$tree_file"

      else

        made_rej_files=yes
        made_link_rej=yes
        mv "$tree_file" "$tree_file.orig"
        ln -s "$new_target" "$tree_file.rej"
        set-file-metadata `file-metadata --symlink --permissions "$tree_file.orig"` "$tree_file.rej"
        apply_patch_msg "C>" "$tree_file"

      fi

      if test ! -z "$made_link_rej" ; then 
        ln -s "$orig_target" "$tree_file.patch-orig"
        if test ! -z "$orig_metadata" ; then
          set-file-metadata $orig_metadata "$tree_file.patch-orig"
        fi
      fi

    fi

  else
    # patch must contain a .original file
    # 

    if cmp "$tree_file" "$patches/$mod_file.original" ; then

      apply_patch_msg "->" "$tree_file"
      metadata="`file-metadata --symlink --permissions \"$tree_file\"`"
      rm "$tree_file"
      ln -s "$new_target" "$tree_file"
      set-file-metadata $metadata "$tree_file"

    else

      apply_patch_msg "C>" "$tree_file"
      made_rej_files=yes
      mv "$tree_file" "$tree_file.orig"
      ln -s "$new_target" "$tree_file.rej"
      set-file-metadata `file-metadata --symlink --permissions "$tree_file.orig"` "$tree_file.rej"
      cp -p "$patches/$mod_file.original" "$tree_file.patch-orig"

    fi

  fi

done
IFS="$OLD_IFS"

################################################################
# Patch Binary Files and Symlinks Replaced by Regular Files 
# 

cd "$tree"

OLD_IFS="$IFS"
IFS="
"
for m in `cat "$binary_patch_map"` ; do

  IFS="$OLD_IFS"

  tree_file="${m%% *}"
  mod_file="${m#* }"

  preserve_old_patch_spew "$tree_file"

  if test ! -h "$tree_file" -a ! -e "$tree_file" ; then

    # shouldn't actually be reached
    # 
    made_rej_files=yes
    touch "$tree_file.orig"
    chmod ugo-rwx "$tree_file.orig"
    cp -p "$patches/$mod_file.modified" "$tree_file.rej"
    apply_patch_msg "??" "$tree_file" 


  elif test -e "$patches/$mod_file.link-orig" ; then

    # patch specifies replacing a symbolic link
    #

    orig_target="`cat \"$patches/$mod_file.link-orig\"`"

    if test -e "$patches/$mod_file.meta-orig" ; then
      orig_metadata="`cat \"$patches/$mod_file.meta-orig\"`"
    else
      orig_metadata=
    fi

    made_bin_rej=

    if test ! -h "$tree_file" ; then

      made_rej_files=yes
      made_bin_rej=yes
      mv "$tree_file" "$tree_file.orig"
      cp "$patches/$mod_file.modified" "$tree_file.rej"
      set-file-metadata `file-metadata --symlink --permissions "$tree_file.orig"` "$tree_file.rej"
      apply_patch_msg "CB" "$tree_file" 

    else      

      old_target_patch="`read-link \"$tree_file\"`"
      old_target_source="`cat \"$patches/$mod_file.link-orig\"`"
      
      if test "$old_target_patch" = "$old_target_source" ; then

        metadata="`file-metadata --symlink --permissions \"$tree_file\"`"
        rm "$tree_file"
        cp "$patches/$mod_file.modified" "$tree_file"
        set-file-metadata $metadata "$tree_file"
        apply_patch_msg "B " "$tree_file" 

      else

        made_rej_files=yes
        made_bin_rej=yes
        mv "$tree_file" "$tree_file.orig"
        cp "$patches/$mod_file.modified" "$tree_file.rej"
        set-file-metadata `file-metadata --symlink --permissions "$tree_file.orig"` "$tree_file.rej"
        apply_patch_msg "CB" "$tree_file" 
      fi

      if test ! -z "$made_bin_rej" ; then 
        ln -s "$orig_target" "$tree_file.patch-orig"
        if test ! -z "$orig_metadata" ; then
          set-file-metadata $orig_metadata "$tree_file.patch-orig"
        fi
      fi

    fi

  else
    # patch must contain a .original file
    # 

    if cmp "$tree_file" "$patches/$mod_file.original" ; then

      metadata="`file-metadata --symlink --permissions \"$tree_file\"`"
      rm "$tree_file"
      cp "$patches/$mod_file.modified" "$tree_file"
      set-file-metadata $metadata "$tree_file"
      apply_patch_msg "B " "$tree_file" 

    else

      made_rej_files=yes
      mv "$tree_file" "$mod_file.orig"
      cp "$patches/$mod_file.modified" "$tree_file.rej"
      set-file-metadata `file-metadata --symlink --permissions "$tree_file.orig"` "$tree_file.rej"
      cp -p "$patches/$mod_file.original" "$tree_file.patch-orig"
      apply_patch_msg "CB" "$tree_file" 
    fi

  fi

done
IFS="$OLD_IFS"

################################################################
# Patch File Metadata
# 

OLD_IFS="$IFS"
IFS="
"
for m in `cat "$metadata_patch_map"` ; do

  IFS="$OLD_IFS"

  tree_file="${m%% *}"
  mod_file="${m#* }"

  orig_perms="`cat \"$patches/$mod_file.meta-orig\"`"
  mod_perms="`cat \"$patches/$mod_file.meta-mod\"`"

  file_to_patch=

  if test -h "$tree/$tree_file" -o -e "$tree/$tree_file" ; then
    if test ! -h "$tree/$tree_file" ; then
      # CRITICAL: don't modify the meta-data of a file that might
      # be a hard-link to a file we don't want to modify.
      #
      rm -f ,,tmp 2> /dev/null
      find "$tree/$tree_file" -links +2 \
      | awk '{
               print "mv \"" $1 "\" ,,tmp";
               print "cp ,,tmp \"" $1 "\"";
               print "rm -f ,,tmp";
             }' \
      | $ARCH_SHELL -e
    fi
    file_to_patch="$tree/$tree_file"
  elif test ! -e "$patches/$mod_file.patch" ; then
    file_to_patch="$tree/$tree_file.rej"
  fi

  if test ! -z "$file_to_patch" ; then

    has_perms="`file-metadata --symlink --permissions \"$tree/$tree_file\"`"

    if test "$orig_perms" = "$has_perms" ; then
      set-file-metadata $mod_perms "$file_to_patch"
      has_perms="$mod_perms"
    fi

    if test "$has_perms" != "$mod_perms" ; then
      made_rej_files=yes
      cp "$patches/$mod_file.meta-mod" "$tree/$tree_file.meta.rej"
      apply_patch_msg "C-" "$tree_file"
    else
      apply_patch_msg "--" "$tree_file"
    fi

  fi

done
IFS="$OLD_IFS"

################################################################
# Patch Dir Metadata
# 

OLD_IFS="$IFS"
IFS="
"
for m in `cat "$dir_metadata_patch_map"` ; do

  IFS="$OLD_IFS"

  tree_file="${m%% *}"
  mod_file="${m#* }"

  orig_perms="`cat \"$patches/$mod_file/=dir-meta-orig\"`"
  mod_perms="`cat \"$patches/$mod_file/=dir-meta-mod\"`"
  has_perms="`file-metadata --permissions \"$tree/$tree_file\"`"

  if test "$orig_perms" = "$has_perms" ; then
    set-file-metadata $mod_perms "$tree/$tree_file"
    has_perms="$mod_perms"
  fi

  if test "$has_perms" != "$mod_perms" ; then
    made_rej_files=yes
    cp "$patches/$mod_file.meta-mod" "$tree/$tree_file/=dir-meta.rej"
    apply_patch_msg "c-" "$tree_file"
  else
    apply_patch_msg "d-" "$tree_file"
  fi

done
IFS="$OLD_IFS"

# and for new directories
# 
if test -e "$delta/$new_dirs_metadata" ; then 
  cd "$tree"
  cat "$mod_dir_index_by_name" \
  | sed -e 's/ /	/' \
  | join -t'	'  -o 2.1,1.2 -1 1 -2 2 - "$delta/$new_dirs_metadata" \
  | sort -t'	' -k 2 \
  | join -t'	' -o 1.1,2.1 -1 2 -2 2  - "$final_tree_dir_index" \
  | sed -e s'/^/set-file-metadata /' \
  | $ARCH_SHELL -e $debugx
fi



################################################################
# Finish Up List of Patched Files
# 

apply_patch_msg_finish


################################################################
# Put Patches for Missing Files in Tree
# 

if test ! -z "`head -n 1 \"$missing_file_patch_list\"`" ; then

  if test ! -z "$report" ; then
    larch heading --sub "copying patches for missing files to tree\\n"
  fi

  missing_file_patches="$tree/==missing-file-patches-`basename \"$patchdir\"`--`date +%Y%m%d.%H:%M:%S`"
  mkdir "$missing_file_patches"

  cat "$missing_file_patch_list" \
  | copy-file-list -- - "$patches" "$missing_file_patches"

fi


################################################################
# Some Conflict Files Still Need to be Renamed to .rej Files
# 

if test ! -z "report" -a -e "$tmpdir/,,deferred_conflicts" ; then
  larch heading --sub "renaming conflicting dirs to .rej files\\n"
fi

cd "$tree"

if test -e "$tmpdir/,,deferred_conflicts" ; then

  for f in `cat "$tmpdir/,,deferred_conflicts" | sort -r` ; do
    mv "$f" "$f.rej"
    if test ! -z "$report" ; then
      printf "%s\\n" "$f" | larch body-indent --sub --nonl
    fi
  done

  if test ! -z "$report" ; then
    printf "\\n"
  fi

fi


################################################################
# Do We Really Need the Removed Files Archive? 
# 

if test -z "$delete_removed" ; then
  set +e
  rmdir "$archive" 2> /dev/null
  set -e
fi

################################################################
# Tell the User if Conflicts Occurred
# 

status=0

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


if test ! -z "$made_rej_files" ; then
  if test ! -z "$quiet" ; then
    larch heading --sub --sub "patch conflicts occured\\n"
    printf "look for .rej files\\n" | larch body-indent --sub --sub
  fi
  status=1
fi

if test -e "$missing_file_patches" ; then
  if test ! -z "$quiet" ; then
    larch heading --sub --sub  "patch contained modifications for missing files\\n" 
    printf "examine %s\\n" "$missing_file_patches" | larch body-indent --sub --sub
  fi
  status=1
fi

missing_renamed_files_log="$tree/++missing-renamed-files-`basename \"$patchdir\"`--`date +%Y%m%d.%H:%M:%S`"

if test ! -z "`head -n 1 \"$missing_renamed_files_index\"`" ; then

  if test ! -z "$quiet" ; then
    larch heading --sub --sub "patch wanted to rename some missing files\\n"
  fi

  saved_log=
  if cp "$missing_renamed_files_index" "$missing_renamed_files_log" ; then
    if test ! -z "$quiet" ; then
      larch heading --sub --sub --sub "log file: %s\\n" "$missing_renamed_files_log"
    fi
    saved_log=yes
  fi

  if test -z "$saved_log" -o ! -z "$report" ; then
    larch heading --sub --sub --sub "file list\\n"
    cut -s -d ' ' -f 1 "$missing_renamed_files_log" | larch body-indent --sub --sub --sub
  fi
  status=1
fi

missing_renamed_dirs_log="$tree/++missing-renamed-dirs-`basename \"$patchdir\"`--`date +%Y%m%d.%H:%M:%S`"

if test ! -z "`head -n 1 \"$missing_renamed_dirs_index\"`" ; then

  if test ! -z "$quiet" ; then
    larch heading --sub --sub "patch wanted to rename some missing dirs\\n" 1>&2
  fi

  saved_log=
  if cp "$missing_renamed_dirs_index" "$missing_renamed_dirs_log" ; then
    if test ! -z "$quiet" ; then
      larch heading --sub --sub --sub  "log file: %s\\n" "$missing_renamed_dirs_log"
    fi
    saved_log=yes
  fi

  if test -z "$saved_log" -o ! -z "$report" ; then
    larch heading --sub --sub "file list\\n"
    cat "$missing_renamed_dirs_log" | larch body-indent --sub --sub --sub
  fi
  status=1
fi

if test ! -z "$quiet" -a  $status -eq 0 ; then
  larch heading --sub --sub "patch cleanly applied\\n"
fi

if test -z "$debug" ; then
 cd "$tree"
 rm -rf "$tmpdir"
fi


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


exit "$status"

